1

I have a c++ program in which a gdk-pixbuf is created. I want to output it as an image, so I call gdk_pixbuf_save_to_stream(pixbuf,stream,type,NULL,&err,NULL). This works fine when "type" is png or tiff, but with jpeg or bmp it just produces a black square. The original pixbuf consists of black-on-transparent (and gdk_pixbuf_get_has_alpha returns true) so I'm guessing that the problem is with the alpha mask.

GdkPixbuf has a function to add an alpha channel, but I can't see one that removes it again, or (which might be as good) to invert it.

Is there a simple way to get the jpeg and bmp formats to work properly?

(I should say that I'm very new to proper programming like this.)

Andrew Stacey
  • 1,004
  • 8
  • 17
  • Not sure about the `gdk-pixbuf`, but you may want to try `libcairo`,w hich is generally based on `gdk-pixbuf` and has all this jpeg, bmp, ... related stuff routines. – M. Williams Apr 30 '10 at 14:16
  • Thanks, but scanning through the docs doesn't show up any functions for _saving_ to jpeg &c, only for importing from them. – Andrew Stacey Apr 30 '10 at 20:02

2 Answers2

2

JPEG doesn't have any notion of an alpha channel, or transparency at all. The alpha channel is stripped during the conversion to JPEG. BMP has the same restriction.

Since transparency is important to you, your program should stick to generating PNGs.

As far as the question you've posed in the title, removing an alpha channel can be done manually. The trick is understanding how the data in a GdkPixbuf is stored. When you have an RGB pixbuf with an alpha channel (also called RGBA), the pixels are stored as 32-bit values: 4 bytes, one byte per color, the fourth being the alpha channel. RGB pixbufs are stored as 24-bit values, one byte per color.

So, if you create a temporary byte buffer and copy over the first three bytes of each RGBA pixel and drop the fourth, that temporary buffer is then pure RGB. To diagram it a little:

[R][G][B][A][R][G][B][A]... => [R][G][B][R][G][B]...

Note that you have to pack the temporary buffer; there's no spare byte between the [B] byte and the next [R] byte.

You then create a new GdkPixbuf by handing it this RGB buffer, and you've removed the alpha channel.

See gdk_pixbuf_get_pixels() to access the RGBA buffer and gdk_pixbuf_new_from_data() to create the RGB pixbuf. See here for more discussion on how the packed data is stored inside a GdkPixbuf.

Jim Nelson
  • 1,688
  • 3
  • 15
  • 27
  • Are you going for the archaeology badge! I'd completely forgotten about this question. Thanks for the answer - very clear. When I go back to this project, I'll remember this. – Andrew Stacey May 23 '12 at 07:32
-1

Here is (rather inefficient and ugly) Vala application that removes transparency from an image and saves it in the format specified. NOTE: There is a small bug in vala binding for gdk_pixbuf_new_from_data() that causes corruption of the resulting image. I'm going to fix that soon but this is meanly for demonstration purposes for now (besides the question was about C++):

    public static int main (string[] args) {
        if (args.length < 4) {
            print ("Usage: %s SOURCE DESTINATION FORMAT\n", args[0]);
            return -1;
        }

        var src_path = args[1];
        var destination_path = args[2];
        var dest_type = args[3];

        var pixbuf = new Gdk.Pixbuf.from_file_at_scale (src_path, 48, 48, false);

        // Remove alpha channel
        if (pixbuf.get_has_alpha () && pixbuf.get_n_channels () == 4 && pixbuf.get_bits_per_sample () == 8) {
            var width = pixbuf.get_width ();
            var height = pixbuf.get_height ();
            var rowstride = pixbuf.get_rowstride ();
            unowned uint8[] orig_pixels = pixbuf.get_pixels ();
            var pixels = new uint8[rowstride * height];

            for (var i = 0; i < height; i++) {
                for (var j = 0, k = 0; j < width * 4; j += 4, k += 3) {
                    var orig_index = rowstride * i + j;
                    var index = rowstride * i + k;

                    if (orig_pixels[orig_index] == 0 &&
                        orig_pixels[orig_index + 1] == 0 &&
                        orig_pixels[orig_index + 2] == 0 &&
                        orig_pixels[orig_index + 3] == 0) {
                        pixels[index] = 0xFF;
                        pixels[index + 1] = 0xFF;
                        pixels[index + 2] = 0xFF;
                    } else {
                        pixels[index] = orig_pixels[orig_index];
                        pixels[index + 1] = orig_pixels[orig_index + 1];
                        pixels[index + 2] = orig_pixels[orig_index + 2];
                    }
                }
            }

            pixbuf = new Gdk.Pixbuf.from_data (pixels,
                                               pixbuf.get_colorspace (),
                                               false,
                                               8,
                                               width,
                                               height,
                                               rowstride,
                                               null);
        }

        pixbuf.save (destination_path, dest_type);

        return 0;
    }
zeenix
  • 111
  • 1
  • 3