elixir-image/image

Using `Image.avatar` with PNGs

jarrodmoldrich opened this issue · 4 comments

Hi @kipcole9 ,

I was trying the avatar function lately and wondering why it wasn't working for me. It turned out that when I switch the input to jpeg it worked. I looked a little deeper and it's because the PNG already has an alpha channel which is not removed before the bandjoin. Compositing the alpha channels is way too complex for the use case, so simply removing this channel in the vips code below fixes the problem.

Mix.install([{:image, "~> 0.24.0"}])

defmodule Test do

    def vips_avatar(in_file, out_file) do
        {:ok, image} = Vix.Vips.Image.new_from_file(in_file)

        # mask
        {:ok, {circle, _}} = Vix.Vips.Operation.svgload_buffer("<svg viewBox=\"0 0 512 512\"><circle style=\"fill: black; stroke: none\" cx=\"256\" cy=\"256\" r=\"256\"/></svg>")
        true = Vix.Vips.Image.has_alpha?(circle)
        {:ok, mask} = Vix.Vips.Operation.extract_band(circle, Vix.Vips.Image.bands(circle) - 1)

        # write
        {:ok, image_without_alpha} = ensure_alpha_removed(image) # <-------------------
        {:ok, final_image} = Vix.Vips.Operation.bandjoin([image_without_alpha, mask])
        Vix.Vips.Image.write_to_file(final_image, out_file)
    end

    def ensure_alpha_removed(image) do
        band_count = Vix.Vips.Image.bands(image)
        if band_count == 4,
            do: Vix.Vips.Operation.extract_band(image, 0, n: band_count - 1),
            else: {:ok, image}
    end


    def image_avatar(in_file, out_file) do
        Image.open!(in_file)
        |> Image.avatar!()
        |> Image.write!(out_file)
    end

end

Test.vips_avatar("broken_example.png", "test_output_vips_png.png")
Test.vips_avatar("broken_example.jpeg", "test_output_vips_jpeg.png")

Test.image_avatar("broken_example.png", "test_output_image_png.png")
Test.image_avatar("broken_example.jpeg", "test_output_image_jpeg.png")

Script is here with test images.

avatar-test.zip

I've switched to jpeg, so it's not a problem for me, but it may confuse future developers, so I'm leaving this here. Let me know if you'd like a PR.

Cheers,

Jarrod

@jarrodmoldrich thanks very much for getting to the bottom of this. I feel as if the right approach is to flatten out the image before compositing (ie with Image.flatten) so I'll experiment with that first and then seek your review to check its ok.

I've pushed a commit that flattens the image before compositing the mask which I think should resolve this issue. Would you mind confirming by configuring {:image, github: "elixir-image/image"} and checking with your test image?

That's fixed, thank you!

Thanks @jarrodmoldrich, closing for now and publish an update in the next hour.