Kozea / CairoSVG

Convert your vector images
https://courtbouillon.org/cairosvg
GNU Lesser General Public License v3.0
752 stars 149 forks source link

Masks use alpha instead of luminance #149

Open emrosenf opened 7 years ago

emrosenf commented 7 years ago

When masking, the color values of all children are not being properly applied bitwise.

To illustrate, here is a simple example. There is a red rectangle at the base of the image with a masked blue rectangle on top of it. The mask has some padding, but is otherwise white, with a black rectangle in the center.

The white part of the mask mixed with blue is white (e.g. 0xFFF & 0x00F = 0x00F). The black part of the mask mixed with blue should be transparent (e.g. we can see the red square in the background). Therefore we should see effectively three rectangles: red background, blue on top, with red at the very center. In 2.0.2 we only see the first two rectangles. The part of the mask with fill #000 is being applied as if the fill were #fff.

Here is how CairoSVG renders this: mask3

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 300 300">
<rect width="100%" height="100%" fill="red"/>
<defs>
  <mask id="theMask">
    <rect x="20" y="20" width="260" height="260" fill="#FFF" />
    <rect x="100" y="100" width="100" height="100" fill="#000" />
  </mask>
</defs>
<rect width="100%" height="100%" fill="blue" mask="url(#theMask)"/>
</svg>
emrosenf commented 7 years ago

@liZe Is this something that is easily fixable within CairoSVG, or is it a Cairo issue? If you could point me in the right direction, I could take a look.

liZe commented 7 years ago

@emrosenf Sorry, I'm late :wink:.

The problem is known and explained in the tests:

SVG relies on the luminance and the alpha channel to apply masks, whereas Cairo only handles alpha. The only way to fix this is to find a "luminance-to-alpha" filter.

I've already spent some time trying to find a solution a long time ago, but the only reliable information I can find about this is an interesting mail archive from 2007. I'm afraid nothing changed in Cairo since this mail, maybe it's time to ask them how we could help :smile:.

microSoftware commented 7 years ago

I have exactly the same problem. But I don't get how to use the luminance-to-alpha filter. @emrosenf @liZe Do you have a simple example of mask using luminance-to-alpha filter ? Thank you

liZe commented 7 years ago

But I don't get how to use the luminance-to-alpha filter.

Nobody does, that's the problem :wink:. (Actually, there's probably no luminance-to-alpha filter yet.)

vqhqu commented 6 years ago

librsvg uses cairo and it can render images that use luminance mask. rsvg-convert -f png -o cairosvgtest.png cairosvgtest.svg cairosvgtest Example that uses black to white gradient mask (source: Mozilla Contributors https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Clipping_and_masking)

<svg width="200" height="200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <linearGradient id="Gradient">
      <stop offset="0" stop-color="black" stop-opacity="1" />
      <stop offset="1" stop-color="white" stop-opacity="1" />
    </linearGradient>
    <mask id="Mask">
      <rect x="0" y="0" width="200" height="200" fill="url(#Gradient)"  />
    </mask>
  </defs>
  <rect x="0" y="0" width="200" height="200" fill="green" />
  <rect x="0" y="0" width="200" height="200" fill="red" mask="url(#Mask)" />
</svg>

Image rendered with cairosvg cairosvg -f png -o gradientmask.png gradientmask.svg gradientmask

Image rendered with librsvg rsvg-convert -f png -o rsvggradientmask.png gradientmask.svg rsvggradientmask

liZe commented 6 years ago

librsvg uses cairo and it can render images that use luminance mask.

Really interesting.

According to librsvg's source, the filter is done using pixel matrices. When generating a PDF, the image is thus probably rasterized at some point (that's why the generated PDF file is much bigger with the filter than without).

My comment should have been:

Actually, there's probably no luminance-to-alpha vector filter in Cairo yet.

If anyone knows how filters work in Cairo and/or in librsvg, please help us :smile:!

dewf commented 6 years ago

Hi all,

I came across a similar problem in a C project I'm working on (which is how I found this github issue) ... just wanted to say that you'll have to manipulate the pixel data directly, until such time as the Cairo developers deem it useful to be able to mask with luminance.

Specifically you'll have to call cairo_image_surface_get_data (or Python wrapper equivalent) to get the raw data pointer (on a surface of format CAIRO_FORMAT_ARGB32), and then iterate through all the 32-bit pixels, calculating luminance and transferring to the high 8 bits. Then you can mask with that surface as normal.

Here's a bit of code in InkScape which inspired my own C implementation: MaskLuminanceToAlpha

liZe commented 6 years ago

@dewf Thanks a lot for your message.

Specifically you'll have to call cairo_image_surface_get_data

Thanks for the example. It will work for raster images (as it's a cairo_imagesurface* function), but as long as there's nothing in Cairo for that we can't do this for vector images.