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
- Combining
bodyand head `works fine, these images have no overlap: Imgur: The magic of the Internet - For reference, this is the red shirt that should be overlayed: Imgur: The magic of the Internet
- For reference, this is blue hair that should be overlayed: Imgur: The magic of the Internet
- The result has however very white colours and the body shines through: 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
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
There are some examples of how to compose here: Image - an image processing library based upon Vix - #17 by kip
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))







