将 PIL 图像转换为 Cairo ImageSurface

发布于 2024-12-07 09:53:38 字数 485 浏览 1 评论 0原文

我正在尝试从 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.

我不太明白为什么会这样,也许我对图像格式的理解不够好。

我正在使用开罗 1.10。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

樱&纷飞 2024-12-14 09:53:38

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

我建议你告诉 PIL 添加一个 alpha 通道,然后将 PIL 缓冲区转换为 numpy 数组以获得开罗的可写缓冲区。

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)

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)
可是我不能没有你 2024-12-14 09:53:38

如果出现以下情况,则接受的版本无法正常工作:

  • 您的图像有颜色
  • 您的图像不是不透明
  • 您的图像处于与 RGB(A) 不同的模式

在 cairo 中,图像颜色的值预先乘以 alpha 值,并且存储为使用本机 CPU 字节顺序的 32 位字。这意味着 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)中,此功能需要大约 1 秒来转换 6000x6000 像素的图像,并需要 5 毫秒来转换 500x500 像素的图像。

The accepted version doesn't work correctly if:

  • Your image has colors
  • Your image is not opaque
  • Your image is in a mode different from RGB(A)

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文