Converting Cairo image surfaces to GDK pixbufs

Hi guys,

I was browsing the gtkmm-list archive when the topic of rendering text
to GDK pixbufs came up. The good news is, I have working code that
converts "ARGB32" Cairo image surfaces to RGBA GDK pixbufs.

(The reason why I quote ARGB32 will be clear later)

As you may already know, Cairo image surfaces support several pixel
formats, all of which are set during creation using
cairo_image_surface_create*(). If you read the documentation carefully,
you will realise that on /little endian/ machines, "ARGB32" is really
what most people think of as BGRA [1], where the blue value (LSB) is in
the lower address.  Complicating the matter slightly, the RGB values are
also pre-multiplied with the alpha for performance reasons.

To give an example, the RGB triplet (0xff, 0x80, 0x40) with an alpha
value of 0x80 (50%) is stored in an ARGB32 image surface as 0x20, 0x40,
0x80, 0x80, byte order. The first 3 bytes are the blue, green and red
values multiplied by 50% respectively; the 4th byte is the alpha value
stored as is.

More generally, the RGBA quadruplet (r, g, b, a) is represented in
Cairo's ARGB32 format, byte order, as:

  b * a/255, g * a/255, r * a/255, a

So given a 4-byte sequence (b', g', r', a') representing a pixel in
Cairo ARGB32 on /little endian/ systems, you can obtain the RGBA values

  r = r' * 255/a'
  g = g' * 255/a'
  b = b' * 255/a'
  a = a'

Obviously, if the alpha is 0, we have a problem since we cannot perform
the division. The reason is clear once you realise that anything with
alpha 0 is essentially invisible and therefore, all RGB values are
possible. In my code, I've chosen to set them all to 0 for simplicity.

To speed up the multiplications by 255 a little, you can rewrite x * 255
as (x * 256 - x) or ((x << 8) - x).

And here's the code I wrote for the conversion:

  inline guint8
  convert_color_channel (guint8 src,
                         guint8 alpha)
    return alpha ? ((guint (src) << 8) - src) / alpha : 0;

  convert_bgra_to_rgba (guint8 const* src,
                        guint8*       dst,
                        int           width,
                        int           height)
    guint8 const* src_pixel = src;
    guint8*       dst_pixel = dst;

    for (int y = 0; y < height; y++)
      for (int x = 0; x < width; x++)
        dst_pixel[0] = convert_color_channel (src_pixel[2],
        dst_pixel[1] = convert_color_channel (src_pixel[1],
        dst_pixel[2] = convert_color_channel (src_pixel[0],
        dst_pixel[3] = src_pixel[3];

        dst_pixel += 4;
        src_pixel += 4;

All you need to do to use convert_bgra_to_rgba() is to pass the
Cairo::ImageSurface memory buffer as src and the Gdk::Pixbuf buffer as
dst, together with the dimensions in width and height.

If you need faster still conversions, you can get rid of the division by
using a 65536-entry lookup table which stores every possible result of
convert_color_channel() i.e.

  guint8 table[65536];

  for (unsigned int a = 0; a <= 0xff; a++)
    for (unsigned int x = 0; x <= 0xff; x++)
      table[(a << 8) + x] = convert_color_channel (x, a);
And then modify the computation in convert_bgra_to_rgba() to:

  guint8 const* row = table + (src_pixel[3] << 8);

  dst_pixel[0] = row[src_pixel[2]];
  dst_pixel[1] = row[src_pixel[1]];
  dst_pixel[2] = row[src_pixel[0]];
  dst_pixel[3] = src_pixel[3];

I hope I've been clear.


Kai Xiong ("descender")

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]