Converting Cairo image surfaces to GDK pixbufs
- From: Chong Kai Xiong <descender phreaker net>
- To: pjdavis engineering uiowa edu, jonathon jongsma gmail com
- Cc: gtkmm-list gnome org
- Subject: Converting Cairo image surfaces to GDK pixbufs
- Date: Wed, 09 May 2007 17:05:06 +0800
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
using:
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;
}
void
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],
src_pixel[3]);
dst_pixel[1] = convert_color_channel (src_pixel[1],
src_pixel[3]);
dst_pixel[2] = convert_color_channel (src_pixel[0],
src_pixel[3]);
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.
[1] http://lists.freedesktop.org/archives/cairo/2005-January/002633.html
Regards,
Kai Xiong ("descender")
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]