Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alpha transparency in Cairo

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.

like image 849
user877329 Avatar asked Feb 20 '16 09:02

user877329


1 Answers

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.

like image 133
Uli Schlachter Avatar answered Nov 19 '22 09:11

Uli Schlachter