将PIL图像转换为Cairo图像表面

5 投票
2 回答
4250 浏览
提问于 2025-04-17 03:25

我正在尝试从一个PIL图像创建一个cairo的ImageSurface,目前我写的代码是:

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)

但是这段代码给我的结果是

TypeError: buffer is not long enough.

我不太明白为什么会这样,可能是我对图像格式的理解不够。

我使用的是cairo 1.10版本。

2 个回答

5

接受的版本在以下情况下无法正常工作:

  • 你的图片有颜色
  • 你的图片不是不透明的
  • 你的图片使用的颜色模式不是RGB(A)

在cairo中,图像的颜色值是经过预乘处理的,预乘的值是与透明度(alpha)相乘的。它们以32位的格式存储,并且根据CPU的字节序来决定存储方式。这意味着,PIL图像:

r1 g1 b1 a1 r2 g2 b2 a2 ...

在小端字节序的CPU中存储为:

b1*a1 g1*a1 r1*a1 a1 b2*a2 g2*a2 r2*a2 a2 ...

在大端字节序的CPU中存储为:

a1 r1*a1 b1*a1 g1*a1 a2 r2*a2 g2*a2 b2*a2 ...

这里有一个在小端机器上可以正常工作的版本,不需要依赖NumPy:

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

在这里,我使用cairo进行预乘处理。我也尝试过用NumPy进行预乘处理,但结果反而更慢。在我的电脑上(Mac OS X,2.13GHz Intel Core 2 Duo),转换一个6000x6000像素的图像大约需要1秒,而转换一个500x500像素的图像只需要5毫秒。

5

Cairo的create_for_data()需要一个可写的缓冲区对象(字符串可以作为缓冲区对象,但它是不可写的),而且它只支持每像素32位的数据(RGBA,或者RGB后面跟一个未使用的字节)。另一方面,PIL提供的是一个24位每像素的RGB只读缓冲区对象。

我建议你让PIL添加一个透明通道,然后把PIL的缓冲区转换成numpy数组,这样就能得到一个可写的缓冲区,供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)

撰写回答