2

I can't find any valuable example of locking a texture and modifying pixels in PySDL2

Here's my code :

def run():
    sdl2.ext.init()
    window = SDL_CreateWindow(b"test" sdl2.SDL_WINDOWPOS_CENTERED, sdl2.SDL_WINDOWPOS_CENTERED, 1024, 768, sdl2.SDL_WINDOW_SHOWN)
    renderer = SDL_CreateRenderer(window, -1, sdl2.SDL_RENDERER_ACCELERATED)
    texture = SDL_CreateTexture(renderer, sdl2.SDL_PIXELFORMAT_RGB888, sdl2.SDL_TEXTUREACCESS_STREAMING, 1024, 768)
    surface = SDL_CreateRGBSurface(0, 128, 128, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000)

    running = True
    while running:
        events = sdl2.ext.get_events()
        for event in events:
            if event.type == sdl2.SDL_QUIT or event.type == sdl2.SDL_MOUSEBUTTONDOWN:
                running = False
                break

        sdl2.SDL_LockTexture(texture, None, ctypes.byref(ctypes.c_void_p(surface.contents.pixels)), ctypes.byref(ctypes.c_int(surface.contents.pitch)))

        #
        # How to change the pixels ???
        # surface.contents.pixels[0][0] = Color(0, 255, 255)


        sdl2.SDL_UnlockTexture(texture)

        # window.refresh()
        sdl2.SDL_RenderPresent(renderer)

    return 0

How to change pixels in surface.contents.pixels ?

Thx !

iliak
  • 61
  • 1
  • 4

1 Answers1

0

Why would you Lock the Texture if you want to modefy the Surface pixeldata? Perhaps you wanted to change the texture and i hope i can help you with that: So sdl2.SDL_LockTexture doesn't work as you would expect. It locks the texture but the last two arguments are for returning the pitch and pixeldata of the texture. Which is weird. You would usually expect the function to look like this:

pixels, pitch = sdl2.SDL_LockTexture(texture, None)

So insead of surface.contents.pixels and surface.contents.pitch, you should pass empty variables, which should be assign as ctype variables. Example:

pixels = ctypes.c_void_p()
pitch  = ctypes.c_int()

sdl2.SDL_LockTexture(texture, None, ctypes.byref(pixels), ctypes.byref(pitch))

So after SDL_LockTexture you got the pixeldata and the pitch in these variables. And if we change the pixels, the texture automaticaly saves the changes when Unlocking the texture.

However the pixels are in are not in the right format. So in order to change it we have to convert it:

actual_pixels = ctypes.cast(pixels, ctypes.POINTER(ctypes.c_ubyte * (pitch.value * size[1]))).contents

Now we have something we can work with.

Note that actual_pixels is now a one dimensional array and to change it you use this:

for x in range(0, size[0] * 4, 4):
    for y in range(0, size[1]):
        actual_pixels[x + (y * pitch.value)    ] = 255 # Red
        actual_pixels[x + (y * pitch.value) + 1] = 255 # Green
        actual_pixels[x + (y * pitch.value) + 2] = 255 # Blue 
        actual_pixels[x + (y * pitch.value) + 3] = 255 # Alpha

You then can just unlock the texture and it saves it. If texture displays something diffrent from what you want, you might change PIXELFORMAT( i have sdl2.SDL_PIXELFORMAT_RGBA32) of the texture or if alpha is not working use sdl2.SDL_SetTextureBlendMode(texture, sdl2.SDL_BLENDMODE_BLEND).

Your funtion would look something like this:

def run():
    sdl2.ext.init()
    window = SDL_CreateWindow(b"test", sdl2.SDL_WINDOWPOS_CENTERED, sdl2.SDL_WINDOWPOS_CENTERED, 1024, 768, sdl2.SDL_WINDOW_SHOWN)
    renderer = SDL_CreateRenderer(window, -1, sdl2.SDL_RENDERER_ACCELERATED)
    size = (100, 100)
    texture = SDL_CreateTexture(renderer, sdl2.SDL_PIXELFORMAT_RGB888, sdl2.SDL_TEXTUREACCESS_STREAMING, size[0], size[1])
    #surface = SDL_CreateRGBSurface(0, 128, 128, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000)

    running = True
    while running:
        events = sdl2.ext.get_events()
        for event in events:
            if event.type == sdl2.SDL_QUIT or event.type == sdl2.SDL_MOUSEBUTTONDOWN:
                running = False
                break


        pixels = ctypes.c_void_p()
        pitch  = ctypes.c_int()

        sdl2.SDL_LockTexture(texture, None, ctypes.byref(pixels), ctypes.byref(pitch))
        actual_pixels = ctypes.cast(pixels, ctypes.POINTER(ctypes.c_ubyte * (pitch.value * size[1]))).contents
        for x in range(0, size[0] * 4, 4):
            for y in range(0, size[1]):
                actual_pixels[x + (y * pitch.value)    ] = randint(0,255) # Red
                actual_pixels[x + (y * pitch.value) + 1] = randint(0,255) # Green
                actual_pixels[x + (y * pitch.value) + 2] = randint(0,255) # Blue 
                actual_pixels[x + (y * pitch.value) + 3] = randint(0,255) # Alpha

        sdl2.SDL_UnlockTexture(texture)

        sdl2.SDL_RenderCopy(
            renderer,
            texture,
            sdl2.SDL_Rect(
                0,
                0,
                size[0],
                size[1]
                ),
            sdl2.SDL_Rect(
                0,
                0,
                size[0],
                size[1]
                ))

        sdl2.SDL_RenderPresent(renderer)

This is just an example but you can modefy the pixels in any way.

Important to note that that it is not recommended to use such function in every frame and the bigger the image, the more performance it needs. In my example i have an Texture with a size of 100, 100 which works fast enought.

If you however wanted to change the surface then there is no reason to do it. Textures are just much faster.