Search code examples
pythonpygamecairo

Odd colours in Cairo conversion to PyGame


When I run this:

import pygame
import cairo

WIDTH, HEIGHT = 640, 480


pygame.display.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), 0, 32)

screen.fill((255, 255, 255))

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
ctx = cairo.Context(surface)

ctx.set_source_rgb(0, 0, 1)

ctx.rectangle(0, 0, 100, 100)
ctx.fill()
buf = surface.get_data()
img = pygame.image.frombuffer(buf, (WIDTH, HEIGHT), "ARGB")

screen.blit(img, (0, 0))

pygame.display.flip()

clock = pygame.time.Clock()
while not pygame.QUIT in [e.type for e in pygame.event.get()]:
    clock.tick(30)

the result is a window with a blue rectangle, as I would expect. However, if I change ctx.set_source_rgb(0, 0, 1) with ctx.set_source_rgb(0, 0, 0), i.e. change the colour from (0, 0, 1) to (0, 0, 0), I get nothing at all.

The expected behaviour would be that the rectangle is black. When I modify the blue value between 0 and 1, the opacity seems to change while the actual blue value seems to stay constant. The r and g values work as expected when b = 1. The same happens with set_source_rgba.


Solution

  • This is a problem of endianness, or byte order.

    Cairo's pixel formats are endian-dependent, and pygame's pixel formats are independent of endianness. See https://github.com/pygame/pygame/issues/2972 for more of a discussion of interoperability.

    So if the bytes are flipped, and you say blue (0, 0, 1), you fill out the ARGB like [1, 0, 0, 1], because the alpha is also 1 by default. Flip [1, 0, 0, 1] and you still have the same thing.

    However if you fill in ARGB for black, it goes to [1, 0, 0, 0]. Interpreting that as BGRA yields R=0, G=0, B=1, A=0. Because alpha is 0, it's completely transparent and nothing is drawn.

    On a little endian system (like yours), Cairo's ARGB is equivalent to pygame's BGRA. This was just added to pygame, so you'll have to update to pygame 2.1.3.dev8or higher. (Right now that's the highest release of pygame, and it's a pre release, so you'd need pip install pygame --upgrade --pre

    Then you can do img = pygame.image.frombuffer(buf, (WIDTH, HEIGHT), "BGRA")