Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a greyscale copy of a Surface in pygame?

Tags:

numpy

pygame

In pygame, I have a surface:

im = pygame.image.load('foo.png').convert_alpha()
im = pygame.transform.scale(im, (64, 64))

How can I get a grayscale copy of the image, or convert the image data to grayscale? I have numpy.

like image 298
Christian Mann Avatar asked Apr 21 '12 17:04

Christian Mann


People also ask

How do you change the color of the surface in pygame?

(Press f, g, h to change the color.)

What does Convert_alpha () do in pygame?

convert and convert_alpha are both used to convert surfaces to the same pixel format as used by the screen. This ensures that you won't lose performance because of conversions when you're blitting them to the screen.

How do you combine surfaces in pygame?

There's no function to combine two surfaces, but you can create another pygame. Surface , pass the sum of the widths of the first two surfaces and then blit them onto the third surface.


3 Answers

Use a Surfarray, and filter it with numpy or Numeric:

def grayscale(self, img):
    arr = pygame.surfarray.array3d(img)
    #luminosity filter
    avgs = [[(r*0.298 + g*0.587 + b*0.114) for (r,g,b) in col] for col in arr]
    arr = numpy.array([[[avg,avg,avg] for avg in col] for col in avgs])
    return pygame.surfarray.make_surface(arr)
like image 104
Christian Mann Avatar answered Nov 05 '22 09:11

Christian Mann


The easiest way is to iterate over all the pixels in your image and call .get_at(...) and .set_at(...).

This will be pretty slow, so in answer to your implicit suggestion about using NumPy, look at http://www.pygame.org/docs/tut/surfarray/SurfarrayIntro.html. The concepts and most of the code are identical.

like image 34
imallett Avatar answered Nov 05 '22 11:11

imallett


After a lot of research, I came up with this solution, because answers to this question were too slow for what I wanted this feature to:

def greyscale(surface: pygame.Surface):
    start = time.time()  # delete me!
    arr = pygame.surfarray.array3d(surface)
    # calulates the avg of the "rgb" values, this reduces the dim by 1
    mean_arr = np.mean(arr, axis=2)
    # restores the dimension from 2 to 3
    mean_arr3d = mean_arr[..., np.newaxis]
    # repeat the avg value obtained before over the axis 2
    new_arr = np.repeat(mean_arr3d[:, :, :], 3, axis=2)
    diff = time.time() - start  # delete me!
    # return the new surface
    return pygame.surfarray.make_surface(new_arr)

I used time.time() to calculate the time cost for this approach, so for a (800, 600, 3) array it takes: 0.026769161224365234 s to run.

As you pointed out, here is a variant preserving the luminiscence:

def greyscale(surface: pygame.Surface):
    arr = pygame.surfarray.pixels3d(surface)
    mean_arr = np.dot(arr[:,:,:], [0.216, 0.587, 0.144])
    mean_arr3d = mean_arr[..., np.newaxis]
    new_arr = np.repeat(mean_arr3d[:, :, :], 3, axis=2)
    return pygame.surfarray.make_surface(new_arr)
like image 3
Juan Ignacio Sánchez Avatar answered Nov 05 '22 10:11

Juan Ignacio Sánchez