Grayscaling an Image with PyGame and Numpy
If you had the chance to play The Alchemist, you probably saw the grayscaling effect applied when you pause the game, this is done at pixel level and is achieved thanks to NumPy arrays, which allows really fast operations and transformations (it takes under 0.027 seconds to process an array with shape [800, 600, 3] ).
How is this being done?
At first, you must understand how the RGB pixels color system works, the basic idea is really simple and it says that you can store the color of a pixel using an integer (from 0 to 255) array of size 3, where each entry represents one color component, Red, Green, and Blue respectively.
https://en.wikipedia.org/wiki/RGB_color_model
The important part here is that, if you set the same value for each component, then you'll get a shade of gray from black to white.
Let's say that you have an image with two colors:
Color | Components in Hex Representation | Components in Decimal Representation |
Blue-ish | (00, 9A, FF) | (0, 154, 255) |
Green-ish | (2A, BB, 85) | (42, 187, 133) |
Color | Original compone | Grayscaled components |
Blue-ish | (0, 154, 255) | (154, 154, 154) |
Green-ish | (42, 187, 133) | (133, 133, 133) |
After applying the transformation, you'll get something like this:
As you can see, the idea is pretty simple, but in this case, there's something weird with the result, this is because the luminescence factor, which refers to how much light a pixel does have. This is widely explained here:
https://en.wikipedia.org/wiki/Luma_(video)
What you need to know is that you can preserve this luminescence factor by applying this formula to each pixel:
Color | Original compone | Grayscaled components |
Blue-ish | (0, 154, 255) | (119, 119, 119) |
Green-ish | (42, 187, 133) | (137, 137, 137) |
Then you can get a result similar to this one:
Which feels more accurate to the eye.
But... how can we code all this stuff, well, here's the code I'm using within the game, commented line by line. Unfortunately, Itch does not have syntax highlight :/
def greyscale(surface: pygame.Surface): # Creates a NumPy array from a given surface, with shape: (width, height, 3) arr = pygame.surfarray.pixels3d(surface) # Calculate the Luma value for each pixel and replaces the RGB value with it. luma_arr = np.dot(arr[:,:,:], [0.216, 0.587, 0.144]) # This last operation leaves you with an array of shape (width, height) # Now we need to restore the array's original shape, by inserting a new axis. luma_arr3d = mean_arr[..., np.newaxis] # Propagate the luma value through this new axis new_arr = np.repeat(mean_arr3d[:, :, :], 3, axis=2) # Return the new Surface return pygame.surfarray.make_surface(new_arr)
An In-game Grayscaling example:
Any questions or feedback are welcome, feel free to comment!
Get The Alchemist
The Alchemist
Things went wrong on the alchemist's house, help him overcome this situation!
More posts
- Version 1.4 released! Levels and new Enemies!Apr 02, 2021
- Hackable levels: 1.4 PreviewMar 23, 2021
- Particles & Grayscale with Pygame and NumpyMar 09, 2021
- Red Challenge: Watch me fail over and over again ~.~Mar 04, 2021
- Version 1.3.5: Banishing VFX/SFX for mobsMar 02, 2021
- Version 1.3.4 Available! new UI and better mechanicsFeb 24, 2021
- Python 3.9.2 available for download!Feb 20, 2021
- Dynamically sized frame box with PyGame: Am I framing everything? YesFeb 19, 2021
- Next steps... Let's walkFeb 17, 2021
- Version 1.3.3 Available! New Mobs, Friction, Swords, and more...Feb 15, 2021
Leave a comment
Log in with itch.io to leave a comment.