将 ARGB32 (py)cairo 表面转换为 PIL(low) 图像 - 反色?

Converting an ARGB32 (py)cairo surface to a PIL(low) image - inverse colors?

认为 正在做一些琐碎的标准任务:我正在将 (py)cairo 表面转换为 PIL(low) 图像。原来的cairo面使用ARGB模式。目标 PIL 图像使用 RGBA,即我想保留所有颜色和 alpha 通道。然而,在转换过程中事情变得非常奇怪:似乎 cairo 在内部将其数据存储为 BGRA,因此我实际上需要在转换过程中交换颜色通道,请参见此处:

import cairo

import gi
gi.require_version('Rsvg', '2.0')
from gi.repository import Rsvg

from PIL import Image

w, h = 600, 600
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h,)
ctx = cairo.Context(surface)

ctx.set_source_rgba(1.0, 1.0, 1.0, 1.0) # explicitly draw white background
ctx.rectangle(0, 0, w, h)
ctx.fill()

# tested with https://raw.githubusercontent.com/pleiszenburg/abgleich/v0.0.7/src/abgleich/share/icon.svg
layout = Rsvg.Handle.new_from_file('icon.svg')
layout.render_cairo(ctx)

# EXPORT TEST #1: cairo
surface.write_to_png('export_cairo.png') # ok, looks as expected

pil = Image.frombuffer(mode = 'RGBA', size = (w, h), data = surface.get_data(),)

b, g, r, a = pil.split() # Color swap, part 1: Splitting the channels
pil = Image.merge('RGBA', (r, g, b, a)) # Color swap, part 2: Rearranging the channels

# EXPORT TEST #2: PIL
pil.save('export_pil.png') # ok, looks as expected IF COLORS ARE REARRANGED AS ABOVE

上面的测试用的是rsvg,但是用cairo画几条彩线也可以重现。

我是不是误会了什么,或者这实际上是正确的方法吗?

来自开罗文档 (https://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t):

CAIRO_FORMAT_ARGB32

each pixel is a 32-bit quantity, with alpha in the upper 8 bits, then red, then green, then blue. The 32-bit quantities are stored native-endian. Pre-multiplied alpha is used. (That is, 50% transparent red is 0x80800000, not 0x80ff0000.) (Since 1.0)

所以,在小字节序上,我认为这实际上就是 PIL 所说的 BGRA。

与您的问题没有直接关系,但这是 Pre-multiplied alpha。

根据 https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes,唯一具有预乘 alpha 的模式是 'RGBa'

or is this actually the right way to do it?

不知道“正确”是什么意思。但是,我的评论是:必须有一些方法可以在不通过中间图像的情况下做到这一点。

由于 Pillow 不支持 cairo 的图像模式,也许您可​​以改用 numpy-y 来进行转换。例如,pycairo 的测试套件包含以下内容:https://github.com/dalembertian/pycairo/blob/22d29e0820d0dcbe070a6eb6f8f302e8c41b71a7/test/isurface_get_data.py#L37-L42

buf = surface.get_data()
a = numpy.ndarray (shape=(w,h,4), dtype=numpy.uint8, buffer=buf)

# draw a vertical line
a[:,40,0] = 255  # byte 0 is blue on little-endian systems
a[:,40,1] = 0
a[:,40,2] = 0

因此,要从(在 Pillow-speak 中)BGRa 转换为 RGBa,您可以这样做来交换红色和蓝色通道(其中 a 是一个类似于上面的缓冲区) :

(a[:,:,0], a[:,:,2]) = (a[:,:,2], a[:,:,0])

如果这真的比您通过中间过程的方法更好Image...好吧,我不知道。您必须判断执行此操作的最佳方法是什么。至少你现在应该能够解释为什么它是必要的(cairo 和 PIL 都没有支持通用的图像格式)