1

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.

Michael Stevens
  • 220
  • 1
  • 10

2 Answers2

1

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:

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • 1
    @Rabbid76 thanks for your answer. I have tested it works also about the bug you said seems like a bug but is not a bug I want to change the blue colour `(23,186,255)` to red colour `(255,38,49)` and I want to make the background `(0,0,0)` transparent. Also thank you for your side note I will try that one. :) – Michael Stevens Oct 17 '21 at 08:59
0

I may be completely missing the point of your question, but as I understand it, you want to load an image and change some colours. I would do it like this:

#!/usr/bin/env python3

from PIL import Image
import pygame as pg
import numpy as np

# Open image and ensure RGBA mode, not palette image
img = Image.open("image.png").convert('RGBA')

# Define some colours for readability
black = [0,0,0]
white = [255,255,255]
red   = [255,0,0]
blue  = [0,0,255]

# Make image into Numpy array for vectorised processing
na  = np.array(img)

# Make re-usable mask of black pixels then change them to white
mBlack = np.all(na[...,:3] == black, axis=-1)
na[mBlack,:3] = white
# Make re-usable mask of red pixels then change them to blue
mRed  = np.all(na[...,:3] == red, axis=-1)
na[mRed,:3] = blue

pg.init()
window = pg.display.set_mode(img.size)

surf = pg.image.frombuffer(na.tobytes(), img.size, img.mode)

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()

Which makes this image:

enter image description here

display like this:

enter image description here

So you can see the power of the masks I made, you can do things like this:

# Make any pixels that were either red or black become magenta
na[mRed|mBlack,:3] = [255,0,255]

Or:

# Make all pixels that were not black into cyan
na[~mBlack,:3] = [0,255,255]
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432