MarcusRiemer

MarcusRiemer

Layering transparent images using Image.Draw.image

I am attempting to stack multiple PNG images with transparency on top of each other. These are pixel art images for game characters and should work similar to “dressing” a paper doll. The pixels in my images are either fully transparent or fully opaque, there is no “partial” transparency going on.

I’m more or less porting code from a JavaScript canvas to a Phoenix Controller generating the image on the fly.

defmodule RenderCharacterController do
  use Web, :controller

  def get(conn, params) do
    result_image = Image.new!(832, 1344, color: [0, 0, 0, 0])

    gender = Map.get(params, "gender", "male")

    layers = [
      Image.open!("priv/static/lpc/body/#{gender}/light.png"),
      Image.open!("priv/static/lpc/head/#{gender}/light.png"),
      Image.open!("priv/static/lpc/hair/buzzcut/adult/blue.png"),
      Image.open!("priv/static/lpc/torso/longsleeve/#{gender}/red.png")
    ]

    result_image = Enum.reduce(layers, result_image, & Image.Draw.image!(&2, &1, 0, 0, mode: :VIPS_COMBINE_MODE_ADD))
    binary = Image.write!(result_image, :memory, suffix: ".png", minimize_file_size: true)

    conn
    |> put_resp_content_type("image/png")
    |> send_resp(200, binary)
  end

end

The result is however not what I expected. I’m sadly not allowed to upload images, so please bare with to imgur. You can also see them all at once: Imgur: The magic of the Internet

So clearly :VIPS_COMBINE_MODE_ADD is not the correct way to blend / draw transparent images on top of each other like a JavaScript Canvas would do. The only other mode is however :VIPS_COMBINE_MODE_SET and this ignores transparency and completely hides the first image behind the second image. I would require something along the line of :VIPS_COMBINE_MODE_SET_UNLESS_FULLY_TRANSPARENT.

Does anyone have an idea how to stack images like this using the Image library? Or any other library for that matter.

Marked As Solved

kip

kip

ex_cldr Core Team

The appropriate way to do this is to use Image.compose/2.

The Image.Draw functions should be avoided since they mutate the image and break image pipelines.

(On my phone, will aim for a more comprehensive example later).

Also Liked

kip

kip

ex_cldr Core Team

There are some examples of how to compose here: Image - an image processing library based upon Vix - #17 by kip

MarcusRiemer

MarcusRiemer

Thanks alot! Swapping out the call made all the difference I wanted. If you are stumbling over this from the future, this is what worked for me:

layers = [
  Image.open!("priv/static/lpc/body/#{gender}/light.png"),
  Image.open!("priv/static/lpc/head/#{gender}/light.png"),
  Image.open!("priv/static/lpc/hair/buzzcut/adult/blue.png"),
  Image.open!("priv/static/lpc/torso/longsleeve/#{gender}/red.png")
]

result_image = Enum.reduce(layers, result_image, & Image.compose!(&2, &1, x: 0, y: 0))

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Other popular topics Top

JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement