4

Python 3.4, pygame==1.9.2b8

I want to draw grayscale frame. Now, code below produce blue color, but I want to make color in range (0,255), where 0 - black. 255 -white. How is it possible?!

import pygame 
import numpy as np
s = 300
screen = pygame.display.set_mode((s, s))
screenarray = np.zeros((s,s))
screenarray.fill(200)
pygame.surfarray.blit_array(screen, screenarray)
pygame.display.flip()
input()
  • in fact I have more complex screenarray, where each element lie during (0,65535). So I want to convert It to grayscale.

Many thanks.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
weider
  • 93
  • 1
  • 7
  • pygame use colors as three numbers (red, green, blue) so white is (0,0,0) and black is (255,255,255) – furas Nov 23 '16 at 06:59

4 Answers4

2

There are two ways pygame can recognize integers as colors:

  1. A 3-element sequence of RGB where each element ranges between 0-255.
  2. A mapped integer value.

If you want to be able to have an array where every integer between 0-255 represent a shade of grey, you can create your own grey scale array using this information. You can create your own array by defining a class.


The first way would be to create a numpy array were each element is a 3-element sequence.

class GreyArray(object):

    def __init__(self, size, value=0):
        self.array = np.zeros((size[0], size[1], 3), dtype=np.uint8)
        self.array.fill(value)

    def fill(self, value):
        if 0 <= value <= 255:
            self.array.fill(value)

    def render(self, surface):
        pygame.surfarray.blit_array(surface, self.array)

Creating a class based on the mapped integer value can be a bit abstract. I don't know how the values are mapped, but with a quick test it was easy to determined that every shade of grey was separated with a value of 16843008, starting with black at 0.

class GreyArray(object):

    def __init__(self, size, value=0):
        self.array = np.zeros(size, dtype=np.uint32)
        self.array.fill(value)

    def fill(self, value):
        if 0 <= value <= 255:
            self.array.fill(value * 16843008)  # 16843008 is the step between every shade of gray.

    def render(self, surface):
        pygame.surfarray.blit_array(surface, self.array)

Short demonstration. Press 1-6 to change the shade of grey.

import pygame
import numpy as np
pygame.init()

s = 300
screen = pygame.display.set_mode((s, s))

# Put one of the class definitions here!

screen_array = GreyArray(size=(s, s))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            quit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_1:
                screen_array.fill(0)
            elif event.key == pygame.K_2:
                screen_array.fill(51)
            elif event.key == pygame.K_3:
                screen_array.fill(102)
            elif event.key == pygame.K_4:
                screen_array.fill(153)
            elif event.key == pygame.K_5:
                screen_array.fill(204)
            elif event.key == pygame.K_6:
                screen_array.fill(255)

    screen_array.render(screen)
    pygame.display.update()
Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
  • Very good review, thanks! Unfortunately, with recent pygame 16843008-based approach renders into something yellowish. I really wonder why. – Dmitry Mikushin Mar 01 '20 at 23:58
1

PyGame use 24-bit colors as three bytes (R,G,B) so white is (0,0,0) and black is (255,255,255)

import pygame 
import numpy as np

SIZE = 256

pygame.init()
screen = pygame.display.set_mode((SIZE, SIZE))

screenarray = np.zeros((SIZE, SIZE, 3))

for x in range(SIZE):
    screenarray[x].fill(x)

pygame.surfarray.blit_array(screen, screenarray)

pygame.display.flip()

input()

pygame.quit()

enter image description here

furas
  • 134,197
  • 12
  • 106
  • 148
0

As of python 3.6, I've ended up with the following:

display = pygame.display.set_mode((width, height))
frame = np.array(framebuf.getData(), copy=False, dtype=np.uint8).reshape((height, width, 1))
screenarray = np.repeat(frame, 3, axis = 2).swapaxes(0, 1)
pygame.surfarray.blit_array(display, screenarray)
pygame.display.flip()

In this snippet, frame is a numpy array, initialized with a list coming from framebuf.getData(). So, it could be any grayscale bitmap, not only constant or gradient as in other examples here. We reshape array to the target display dimensions. Now we deploy numpy.repeat to duplicate each grayscale byte into two other channels. Lastly, we have to do swapaxes as the easiest way to invert width and height. Orthogonally to numpy, pygame expects screenarray with width dimension coming first, which makes it look a bit column-wise (huh, what)?

Overall, this gives ~80fps with 640x480, which is quite awful, but still much better than any explicit Python code. I guess the best would be to prepare RGB frame on the native code side...

Dmitry Mikushin
  • 1,478
  • 15
  • 16
0

In the case of a grayscale image, the shape of the array must be changed using numpy.reshape and the gray channel must be expanded to a red-green and blue color channel using numpy.repeat:

cv2Image = np.repeat(cv2Image.reshape(size[1], size[0], 1), 3, axis = 2)

The pygame.Surface object can be generated by pygame.image.frombuffer:

surface = pygame.image.frombuffer(cv2Image.flatten(), size, 'RGB')

The following function converts a numpy.array with shape (y, x) to a pygame.Surface object:

import numpy as np
def numpyGrayscaleToSurface(numpyArray):
    size = numpyArray.shape[1::-1]
    numpyArray= np.repeat(numpyArray.reshape(size[1], size[0], 1), 3, axis = 2)
    surface = pygame.image.frombuffer(numpyArray.flatten(), size, 'RGB')
    return surface.convert()

See also How do I convert an OpenCV (cv2) image (BGR and BGRA) to a pygame.Surface object.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174