py-sdl / py-sdl2

Python ctypes wrapper around SDL2
Other
296 stars 49 forks source link

SDL_CreateRGBSurfaceWithFormatFrom silently failing when passed a sub-array of a `bytes` object #275

Open TTimo opened 6 months ago

TTimo commented 6 months ago

What doesn't work?

I'm finding that SDL_CreateRGBSurfaceWithFormatFrom either throws or fails to take in the pixel data (but creates a valid empty SDL_Surface) when passed in a sub-array of a bytes object directly. But it works if I setup an intermediate python object first.

How To Reproduce

This test case stems from difficulties I ran into when adding support for setting the application's window icon:

#!/usr/bin/env python

import sys
import sdl2

if __name__ == '__main__':
    sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
    window = sdl2.SDL_CreateWindow(
        'test'.encode(),
        sdl2.SDL_WINDOWPOS_CENTERED,
        sdl2.SDL_WINDOWPOS_CENTERED,
        640,
        480,
        0
        )

    assert sys.byteorder == 'little'
    # A dummy 4 bytes header, followed by an little endian ARGB solid green fill
    # For instance, say we loaded a TGA file with 18 bytes worth of header
    w = h= 256
    pixels = b'\xff\xff\xff\xff' b'\x00\xff\x00\xff' * w * h

    # Arch Linux, Python 3.11.7:       pixels don't get through, yields a blank/fully transparent icon
    # Arch Linux, Python 3.12.2 (AUR): pixels don't get through, yields a blank/fully transparent icon
    # Windows, Python 3.11.8:          OSError: exception: access violation reading [..]
    # Windows, Python 3.12.2:          works fine!
    # (all with PySDL2 0.9.16, current atm and SDL 2.30.0)
    icon = sdl2.SDL_CreateRGBSurfaceWithFormatFrom(pixels[4:4+w*h*4], w, h, 32, w*4, sdl2.SDL_PIXELFORMAT_ARGB8888)
    assert icon is not None
    assert icon.contents.format.contents.format == sdl2.SDL_PIXELFORMAT_ARGB8888

    sdl2.SDL_SetWindowIcon(window, icon)

    import time
    time.sleep(4)

    # Workaround: this..
    pixels2 = pixels[4:4+w*h*4]
    icon = sdl2.SDL_CreateRGBSurfaceWithFormatFrom(pixels2, w, h, 32, w*4, sdl2.SDL_PIXELFORMAT_ARGB8888)
    assert icon is not None
    assert icon.contents.format.contents.format == sdl2.SDL_PIXELFORMAT_ARGB8888

    sdl2.SDL_SetWindowIcon(window, icon)

    import time
    time.sleep(4)

On Windows with Python 3.12 the first SDL_CreateRGBSurfaceWithFormatFrom call works. Elsewhere it'll either ignore the pixels and yield a transparent/empty surface, or throw an OSErrror.

The workaround is what bugs me the most .. just .. pixels2 = pixels[4:4+w*h*4] and all platforms work.

Platform:

Additional context Add any other context about the problem here.

TTimo commented 6 months ago

For a bit more context, this is the initial in-situ investigation of this problem. I only found the workaround later on when writing a test case: https://gitlab.steamos.cloud/devkit/steamos-devkit/-/commit/7e89557f692b7a1437ebbf4f51464a95d4f8f52f#f42379bd6d31600aba6eff88f0c3311df57ccea9