Alpha transparency in Cairo
Asked Answered
P

1

6

I have a problem with displaying alpha transparency using GTK and Cairo. I try to display this image 1

If I do the alpha blending my self, everything works.

Manual alpha blending

If I pass the alpha values directly to Cairo, the shadow seems to render fine, but the glow effect is corrupted.

Cairo alpha rendering

Is this a bug in Cairo 1.14.2, or am I missing something?

//Need deprecated API to get background color
GdkColor color = gtk_widget_get_style(widget)->bg[GTK_STATE_NORMAL];
Pixel color_blend
    {
     uint8_t(255*color.red/65535.0f)
    ,uint8_t(255*color.green/65535.0f)
    ,uint8_t(255*color.blue/65535.0f)
    ,255
    };
while(ptr!=ptr_end)
    {
//  TODO: Interpolate
    auto row_src=size_t(row*factor);
    auto col_src=size_t(col*factor);

    auto alpha=ptr_src[row_src*width_in + col_src].v3/255.0f;
    *ptr=
        {
    //  Using manual alpha blend works  
        uint8_t(alpha*ptr_src[row_src*width_in + col_src].v2 + (1-alpha)*color_blend.v2)
        ,uint8_t(alpha*ptr_src[row_src*width_in + col_src].v1 + (1-alpha)*color_blend.v1)
        ,uint8_t(alpha*ptr_src[row_src*width_in + col_src].v0 + (1-alpha)*color_blend.v0)
        ,255
    /*  This appears to be broken 
        ptr_src[row_src*width_in + col_src].v2
        ,ptr_src[row_src*width_in + col_src].v1
        ,ptr_src[row_src*width_in + col_src].v0
        ,ptr_src[row_src*width_in + col_src].v3*/
        };

    ++col;
    if(col==width_out)
        {
        col=0;
        ++row;
        }
    ++ptr;
    }

I push the pixels using

auto surface=cairo_image_surface_create_for_data((uint8_t*)pixels.begin(),CAIRO_FORMAT_ARGB32,width_out,height_out,width_out*sizeof(Pixel));

cairo_set_source_surface(cr, surface, 0.5*(width-width_out), 0.0);
cairo_paint(cr);

cairo_surface_destroy(surface);

Explicitly setting the operator to CAIRO_OPERATOR_OVER does not help, the result is still the same.

Potboy answered 20/2, 2016 at 9:16 Comment(8)
Are you using the correct operator, i.e. CAIRO_OPERATOR_OVER instead of CAIRO_OPERATOR_SOURCE?Cothran
@H. Guijt CAIRO_OPERATOR_OVER is default, and specifying it explicitly gives the same result. Changing to CAIRO_OPERATOR_SOURCE however, has the correct behaviour, that is , I get a semi-transparent window.Potboy
That doesn't make any sense... Uhm, how about the format of the destination surface: is that RGB or ARGB?Cothran
@H.Guijt Do you mean the surface I create, or the supported format of the cairo context provided by GTK?Potboy
The one you are painting to, i.e. 'cr' in your example.Cothran
@H.Guijt How can I get/set the format of the cairo context then?Potboy
What's your pixel format? What are the variable types? All these auto don't say much. How did you come up with your formula for the blending (cairographics.org/operators uses another formula for OVER). Do you know what pre-multiplied alpha means? Which of the following values describes "fully red with 50% transparency"? (1) (0.5,0,0,0.5) or (2) (1,0,0,0.5) (Hint: (1) is correct)Vercelli
@UliSchlachter: The pixels I write are uint8_t, as you can see. Notice that the background is opaque, so the formula Cairo uses simplifies to the one I use. Also Cairo stores pixels in native endian. Therefore ARGB is swapped to BGRA and the correct one is neither (1) or (2), but (0,0,1.0,0.5). Or in my case (0,0,255,128).Potboy
V
4

As you mention in your comment above, your pixel values are wrong. You need to use pre-multiplied alpha. Coming back to my example from the question (and ignoring endianness), fully red with 50% transparency is 0x7f << 24 | 0x7f in Cairo. Pixels with invalid values (some color component is larger than the alpha value) produce undefined results and your 0xff << 24 | 0x7f falls into this category.

See http://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t:

Pre-multiplied alpha is used. (That is, 50% transparent red is 0x80800000, not 0x80ff0000.)

P.S.: In my opinion, the correct way to access pixel data is via a uint32_t and shifting, e.g. uint32_t pixel = (r << 24) | (g << 16) | (b << 8) | a;. That way you don't have to worry about endianness at all.

P.P.S.: For OVER and a fully-opaque target, the formula Cairo uses simplifies to source_color + target_color * (1 - source_alpha) while your code uses source_color * source_alpha + target_color * (1 - source_alpha). See http://www.cairographics.org/operators/. These two formulas clearly are not equivalent.

Edit: Ok, perhaps they are equivalent when using pre-multiplie alpha. Sorry for the confusion there.

Vercelli answered 22/2, 2016 at 18:52 Comment(1)
They are equivalent when using pre-multiplied alpha. And the formula Cairo uses is only correct in that case. So you can remove the perhaps.Potboy

© 2022 - 2024 — McMap. All rights reserved.