I'm trying to create a cairo ImageSurface from a PIL image, the code I have so far is:
im = Image.open(filename)
imstr = im.tostring()
a = array.array('B', imstr)
height, width = im.size
stride = cairo.ImageSurface.format_stride_for_width(cairo.FORMAT_RGB24, width)
return cairo.ImageSurface.create_for_data(a, cairo.FORMAT_ARGB24, width, height, stride)
But this is giving me
TypeError: buffer is not long enough.
I don't really understand why this is, perhaps I don't understand image formats well enough.
I'm using cairo 1.10.
Cairo's create_for_data() is wants a writeable buffer object (a string can be used as a buffer object, but it's not writable), and it only supports 32 bits per pixel data (RGBA, or RGB followed by one unused byte). PIL, on the other hand, provides a 24bpp RGB read-only buffer object.
I suggest you tell PIL to add an alpha channel, then convert the PIL buffer to a numpy array to get a writable buffer for Cairo.
im = Image.open(filename)
im.putalpha(256) # create alpha channel
arr = numpy.array(im)
height, width, channels = arr.shape
surface = cairo.ImageSurface.create_for_data(arr, cairo.FORMAT_RGB24, width, height)
The accepted version doesn't work correctly if:
In cairo image colors have their value premultiplied by the value of alpha, and they are stored as a 32 bit word using the native CPU endianness. That means that the PIL image:
r1 g1 b1 a1 r2 g2 b2 a2 ...
is stored in cairo in a little endian CPU as:
b1*a1 g1*a1 r1*a1 a1 b2*a2 g2*a2 r2*a2 a2 ...
and in a big endian CPU as:
a1 r1*a1 b1*a1 g1*a1 a2 r2*a2 g2*a2 b2*a2 ...
Here is a version that works correctly on a little endian machine without the NumPy dependency:
def pil2cairo(im):
"""Transform a PIL Image into a Cairo ImageSurface."""
assert sys.byteorder == 'little', 'We don\'t support big endian'
if im.mode != 'RGBA':
im = im.convert('RGBA')
s = im.tostring('raw', 'BGRA')
a = array.array('B', s)
dest = cairo.ImageSurface(cairo.FORMAT_ARGB32, im.size[0], im.size[1])
ctx = cairo.Context(dest)
non_premult_src_wo_alpha = cairo.ImageSurface.create_for_data(
a, cairo.FORMAT_RGB24, im.size[0], im.size[1])
non_premult_src_alpha = cairo.ImageSurface.create_for_data(
a, cairo.FORMAT_ARGB32, im.size[0], im.size[1])
ctx.set_source_surface(non_premult_src_wo_alpha)
ctx.mask_surface(non_premult_src_alpha)
return dest
Here I do the premultiplication with cairo. I also tried doing the premultiplication with NumPy but the result was slower. This function takes, in my computer (Mac OS X, 2.13GHz Intel Core 2 Duo) ~1s to convert an image of 6000x6000 pixels, and 5ms to convert an image of 500x500 pixels.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With