draw_mask Differs from bandjoin using an Alpha Channel
Hi, I noticed that if if I use draw_mask on an empty image using an alpha channel and some color, the resulting image differs from when I take a solid block color and use band extraction and joining to replace the block alpha with the alpha channel I want the image to look like. Why is this? I thought they should produce the same image. I note that the bandjoining produces a color image that conforms to the alpha used whereas draw_mask does not.
On another note is there a quick way to replace an alpha channel in an image? I am currently just extracting the first 3 bands and joining these bands with the alpha channel I want to use. Thanks
Hi @json20,
I'm not sure I follow, could you post some sample code?
Replace alpha: yes, extract RGB and reattach the new alpha, eg.:
new_rgba = rgba[0:3].bandjoin(new_a)
is pretty quick.
Hi @jcupitt, thanks for the alpha replacing code. Here is some code that produces the above description of the alpha replacement differing from draw_mask using the same alpha.
So we have an empty image which can be produced by importing an SVG into libvips with its opacity set to 0 (this empty image has an all black alpha). We have an alpha channel (which contains some transparency), alpha. We also have a fully opaque red block of the same dimensions as empty_image.
new_image = empty_image.draw_mask([255, 0, 0, 255], alpha, 0, 0)
new_image_2 = red_block[0:3].bandjoin(alpha)
Using the 'difference' composition mode to compare new_image and new_image2, I can see that they contain identical alpha bands yet the images differ. Just looking at the two images I see that new_image_2 looks like the alpha channel, alpha, whereas new_image does not, it is a bit dark in the area of transparency. draw_mask does not produce the same image produced by swapping out the alpha of a solid block color. I read on one of the other issues that all draw_ methods are for painting or something?
Thanks
Sorry, it's still not clear what's going on. Where's this alpha coming from?
Could you make a complete, runnable program that shows the problem? Otherwise I'll need to spend 20 minutes making some code to test, and there's no guarantee what I do is what you're actually doing, so it'll be time wasted.
The alpha is from a rasterised SVG with a linear gradient for two stops with the first stop opaque and the second stop transparent. Here is the demo program with the images attached.
import pyvips
empty_image = pyvips.Image.new_from_file('block_empty.png')
block_red = pyvips.Image.new_from_file('block_red.png')
alpha = pyvips.Image.new_from_file('alpha.png')
new_image = empty_image.draw_mask([255, 0, 0, 255], alpha, 0, 0)
new_image_2 = block_red[0:3].bandjoin(alpha)
new_image.write_to_file("new_image.png")
new_image_2.write_to_file("new_image_2.png")

Thanks
Ah, I see, thanks.
Yes, draw_mask is using the alpha image to scale the constant [255, 0, 0, 255] when it draws into the image, so it's scaling both the pixels and the alpha channel. When you view the result, the pixels are multiplied by the alpha again, so in effect, the alpha is applied twice.
If you want to use draw_mask to change the transparency, then you should just draw on the alpha. If you really want to change the RGB values in the image (perhaps you want to modify the colour rather than just change transparency), then you need to draw on the RGB.
However, I wouldn't use the draw_* operations. They work by rendering the entire image to memory, then modifying pixels, then wrapping that huge memory block up as an image again, and then connecting the next stage of the pipeline to the memory area. You lose parallelism, and your memory use will go through the roof.
It's usually much better to use composite to render a image with transparency on top of your background. You'll have nice control over how images are combined, thanks to the blend mode setting, you'll keep parallelism, and memory use should be low.
The draw operations can be useful if you need to render many thousands of small images on top of a base, but unfortunately pyvips does not have good support for this, since it'll do the render and copy for each draw operation. ruby-vips has a new feature to fix this:
https://www.libvips.org/2021/03/08/ruby-vips-mutate.html
But no one has got around to implementing mutate in pyvips yet.
Thanks for explaining