Reputation: 3721
I think am doing some trivial standard task: I am converting a (py)cairo
surface to a PIL
(low) image. The original cairo
surface uses ARGB
mode. The target PIL
image uses RGBA
, i.e. I want to maintain all colors and the alpha channel. However, things get really bizarre in the conversion: It appears that cairo
stores its data internally as BGRA
, so I actually need to swap the color channels during the conversion, see here:
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
The above test uses rsvg
, but it can also be reproduced by simply drawing a few colorful lines with cairo
.
Am I terribly misunderstanding something or is this actually the right way to do it?
Upvotes: 5
Views: 1402
Reputation: 9867
From the cairo documentation (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)
So, on little endian, this is actually what PIL calls BGRA, I think.
Not directly related to your question, but this is Pre-multiplied alpha.
According to https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes, the only mode with premultiplied alpha is 'RGBa'
.
or is this actually the right way to do it?
No idea what "right" means. However, my comment would be: there must be some way to do this without going through an intermediate image.
Since Pillow does not support cairo's image mode, perhaps you can instead use something numpy-y to do the conversion. For example, pycairo's test suite contains the following: 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
So, to convert from (in Pillow-speak) BGRa to RGBa, you could do something like this to swap the red and blue channels (where a
is a buffer similar to the above):
(a[:,:,0], a[:,:,2]) = (a[:,:,2], a[:,:,0])
If this is really better than your approach of going through an intermediate Image
... well, I do not know. You have to judge what is the best approach to do this. At least you should now be able to explain why it is necessary (there is no common image format supported by both cairo and PIL)
Upvotes: 3