Search code examples
pythonsdlpygamecairopycairo

Byte order when using Cairo in PyGame (SDL)


I currently have some code that draws using pycairo and renders to an SDL surface by way of PyGame. This works nicely on Linux and Windows but the Mac is giving me headaches. Everything just comes out blue or pink The byte order seems to be BGRA instead of ARGB and I have tried using pygame.Surface.set_masks and set_shifts to no avail. This is only broken on the mac (osx 10.6):

import cairo
import pygame

width = 300
height = 200

pygame.init()
pygame.fastevent.init()
clock = pygame.time.Clock()
sdl_surface = pygame.display.set_mode((width, height))

c_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
ctx = cairo.Context(c_surface)

while True:
    pygame.fastevent.get()
    clock.tick(30)
    ctx.rectangle(10, 10, 50, 50)
    ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
    ctx.fill_preserve()

    dest = pygame.surfarray.pixels2d(sdl_surface)
    dest.data[:] = c_surface.get_data()
    pygame.display.flip()

I can fix it with array slicing or using PIL but this kills my frame rate. Is there a way to do this in-place or with settings?


Solution

  • After a lot of hair tearing I have a workaround that does not hurt my frame rate too much by simply reversing the array:

    import cairo
    import pygame
    
    width = 300
    height = 200
    
    pygame.init()
    pygame.fastevent.init()
    clock = pygame.time.Clock()
    sdl_surface = pygame.display.set_mode((width, height))
    
    c_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
    ctx = cairo.Context(c_surface)
    
    while True:
        pygame.fastevent.get()
        clock.tick(30)
        ctx.rectangle(10, 10, 50, 50)
        ctx.set_source_rgba(1.0, 0.0, 0.0, 1.0)
        ctx.fill_preserve()
    
        dest = pygame.surfarray.pixels2d(sdl_surface)
        dest.data[:] = c_surface.get_data()[::-1]
        tmp = pygame.transform.flip(sdl_surface, True, True)
        sdl_surface.fill((0,0,0))    #workaround to clear the display
        del dest     #this is needed to unlock the display surface
        sdl_surface.blit(tmp, (0,0))
        pygame.display.flip()
    

    The fact that I have to delete the array and blit from a temporary Surface does not seem right but it was the only way to flip the display. If anyone has a cleaner suggestion here, please comment.