rfx
rfx

Reputation: 394

Changes made to image surface aren't reflected when painting

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:

Upvotes: 1

Views: 602

Answers (1)

Uli Schlachter
Uli Schlachter

Reputation: 9877

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; } }

Upvotes: 4

Related Questions