Question

I have a small code snippet which loads an image from a PNG file, then modifies the image data in memory by making a specific color transparent (setting alpha to 0 for that color). Here's the code itself:

static gboolean expose (GtkWidget *widget, GdkEventExpose *event, gpointer userdata)
{
    int width, height, stride, x, y;
    cairo_t *cr = gdk_cairo_create(widget->window);
    cairo_surface_t* image;
    char* ptr;

    if (supports_alpha)
        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); /* transparent */
    else
        cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* opaque white */

    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
    cairo_paint (cr);

    image = cairo_image_surface_create_from_png ("bg.png");
    width = cairo_image_surface_get_width (image);
    height = cairo_image_surface_get_height (image);
    stride = cairo_image_surface_get_stride (image);
    cairo_surface_flush (image);

    ptr = (unsigned char*)malloc (stride * height);
    memcpy (ptr, cairo_image_surface_get_data (image), stride * height);
    cairo_surface_destroy (image);

    image = cairo_image_surface_create_for_data (ptr, CAIRO_FORMAT_ARGB32, width, height, stride);
    cairo_surface_flush (image);

    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            char alpha = 0;
            unsigned int z = *((unsigned int*)&ptr [y * stride + x * 4]);

            if ((z & 0xffffff) == 0xffffff) {
                z = (z & ~0xff000000) | (alpha & 0xff000000);
                *((unsigned int*) &ptr [y * stride + x * 4]) = z;
                    }
        }
    }

    cairo_surface_mark_dirty (image);
    cairo_surface_write_to_png (image, "image.png");

    gtk_widget_set_size_request (GTK_OBJECT (window), width, height);
    gtk_window_set_resizable (GTK_OBJECT (window), FALSE);

    cairo_set_source_surface (cr, image, 0, 0);
    cairo_paint_with_alpha (cr, 0.9);
    cairo_destroy (cr);
    cairo_surface_destroy (image);
    free (ptr);

    return FALSE;
}

When I dump the modified data to PNG, transparency is actually there. But when the same data is used as a source surface for painting, there's no transparency. What might be wrong?

Attachments:

  • image.png - modified data dumped to file for debugging purposes,
  • demo.png - actual result
  • bg.png - source image, is omitted due to stackoverflow restrictions, it's simply black rounded rectangle on the white background. Expected result is black translucent rectangle and completely transparent fields, not white, like these on the demo.png.
Was it helpful?

Solution

Setting alpha to 0 means that the color is completely transparent. Since cairo uses pre-multiplied alpha, you have to set the pixel to 0, since otherwise the color components could have higher values than the alpha channels. I think cairo chokes on those super-luminscent pixels.

So instead of this code: if ((z & 0xffffff) == 0xffffff) { z = (z & ~0xff000000) | (alpha & 0xff000000); *((unsigned int*) &ptr [y * stride + x * 4]) = z; } You should try the following: if ((z & 0xffffff) == 0xffffff) { *((unsigned int*) &ptr [y * stride + x * 4]) = 0; } And while we are at it:

  • Doesn't (z & 0xffffff) == 0xffffff check if the green, blue and alpha channels are all at 100% and ignores the red channel? Are you sure that's really what you want? z == 0xffffffff would be opaque white.
  • Instead of using unsigned int, it would be better if you used uint32_t for accessing the pixel data. Portability!
  • Your code assumes that cairo_image_surface_create_from_png() always gives you an image surface with format ARGB32. I don't think that's necessarily always correct and e.g. RGB24 is possible as well.

I think I would do something like this: for (y = 0; y < height; y++) { uint32_t row = (uint32_t *) &ptr[y * stride]; for (x = 0; x < width; x++) { uint32_t px = row[x]; if (is_expected_color(px)) row[x] = 0; } }

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top