Search code examples
pythonpygamepython-imaging-library

How to display an image in Pygame using list of RGBA values of a image


I am trying to make a pygame project which involves some images. In these images some are very similar and there is just a change of colour. So I came up with idea that why not to use only one image and change its corresponding colour using Python from this article.

from PIL import Image
import pygame as pg

img = Image.open("Assets/image.png")
img = img.convert("RGBA")
d = img.getdata()
new_image = []
for item in d:
    if item[:3] == (0,0,0):
        new_image.append((255,255,255,0))
    if item[:3] == (23,186,255):
        new_image.append((255,38,49,item[3]))
    else:
        new_image.append(item)
img.putdata(new_image)
img.save("a.png","PNG")

But in the last two lines of the above code its saving the image and I don't want that!
I want to use it in the Pygame code for displaying and then when the program exits the image is gone. So how can I use a list of RGBA values new _image to display an image in Pygame.
Any help would be appreciated.


Solution

  • Use pygame.image.frombuffer() to create a pygame.Surface. However you have to convert the list to a byte array. Use the ctypes module to make a byte array from a list:

    flat_list = [e for c in new_image for e in c]
    bute_array = (ctypes.c_ubyte * len(flat_list))(*flat_list)
    surf = pg.image.frombuffer(bute_array, img.size, img.mode).convert_alpha()
    

    See also PIL and pygame.image.

    Notice that there is a bug in your algorithm. You need to use elif instead of if in the middle case:

    if item[:3] == (0,0,0):
        new_image.append((255,255,255,0))
    
    #if item[:3] == (23,186,255):
    elif item[:3] == (23,186,255):              # <---
    
        new_image.append((255,38,49,item[3]))
    else:
        new_image.append(item)
    

    Note: if you want to change the background to a white color, you need to make the color opaque:

    new_image.append((255,255,255,0))

    new_image.append((255, 255, 255, 255))
    

    Minimal example:

    The left image is the test image and the right image is the result:

    from PIL import Image
    import pygame as pg
    import ctypes
    
    img = Image.open("Assets/image.png")
    img = img.convert("RGBA")
    d = img.getdata()
    new_image = []
    for item in d:
        if item[:3] == (0, 0, 0):
            new_image.append((255, 255, 255, 0))
            #new_image.append((255, 255, 255, 255))
        elif item[:3] == (23, 186, 255):
            new_image.append((255, 38, 49, item[3]))
        else:
            new_image.append(item)
    
    pg.init()
    window = pg.display.set_mode(img.size)
    
    flat_list = [e for c in new_image for e in c]
    bute_array = (ctypes.c_ubyte * len(flat_list))(*flat_list)
    surf = pg.image.frombuffer(bute_array, img.size, img.mode).convert_alpha()
    
    run = True
    while run:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                run = False 
    
        window.fill(0)
        window.blit(surf, (0, 0))
        pg.display.flip()
    
    pg.quit()
    

    Side note:

    You don't need to load the image using PLI you can access the pixels of a pygame.Surface directly. There are different options: