Drawing PPM images on the Tildagon in MicroPython
The Tildagon has 2MB of RAM. That's not enough to do... well, most things you'd want to do with a computer! There's not much processing power, so running complex image decoding algorithms might be a bit beyond it.
Is there a simple image format which can be parsed and displayed? Yes! The ancient Portable PixMap (PPM) format.
The standard is beautiful in its simplicity. Here's the header:
PPMP6
# Created by GIMP version 2.10.38 PNM plug-in
120 120
255
���t�{...
The P6
identifies it as a PPM file. The #
is a comment. 120 120
says that the image's dimensions are 120 pixels horizontal, and 120 vertical. 255
is the maximum value for each colour.
Then comes a big blob of binary data. Each byte is a value from 0 to 255.
To find the Red, Green, and Blue values of the first pixel, read the first 3 bytes. The next 3 bytes are the RGB of the next pixel. And so on.
There's no compression. Just pure pixel values.
Because of the low memory limits of the Tildagon, I found it impossible to load the entire file into memory and then paint it on the screen. Instead, I read it in chunks.
First, load the file as a read-only binary. Then skip the header and get straight to the pixel data.
Python 3# Open a 120px x 120px Raw / Binary PPM file
with open('/apps/ppm/chrome120.ppm', 'rb') as ppm_file:
print("Skipping Header")
# Skip the header
header = b''
while True:
line = ppm_file.readline()
header += line
if header.endswith(b'\n255\n'):
break
Images on the Tildagon are drawn from the top left, which has co-ordinates -120,-120
Python 3 # Start at the top left
x, y = -120, -120
Next, read in a line of pixels. The image is 120px wide, each pixel has 3 values, so that's 360 bytes. Grab the pixel values and draw them to screen:
Python 3 while True:
# Read in 1 line at a time (3 bytes * 120px)
chunk = ppm_file.read(360)
if not chunk:
break # End of file
# Read the RGB, convert to float
for i in range(0, len(chunk), 3):
r = chunk[i] /255
g = chunk[i + 1]/255
b = chunk[i + 2]/255
# Draw the pixel in a 2x2 square
ctx.rgb(r, g, b).rectangle(x, y, 2, 2).fill()
The screen's resolution is 240x240, so each pixel from the 120x120 image needs to be drawn as 2x2 rectangle.
Once that's done, move to the next square to be drawn. Once a full line has been drawn, move down to drawing the next line.
Python 3 # Move the the next square
x += 2
# If a complete line has been drawn
# Move down a line (2px) and reset the x coordinate
if x >= 120:
x = -120
y += 2
# Clear the chunk from memory
del chunk
# Perform garbage collection
gc.collect()
A bit of manual garbage collection doesn't hurt! And then a bit more for good luck!
Python 3del(ppm_file)
# Final collection
print("Collecting")
gc.collect()
gadgetoid said on fosstodon.org:
@Edent two whole megabytes???? What’s in that thing a freakin’ Pentium? 🤣
Phil Ashby :marmite: 4/7 VOTE! said on mastodon.me.uk:
@Edent Ah crap. I was blithely assuming it had at least as much PSRAM as the previous badge (8M) so I could run a game on it... gonna have to pull some serious tricks now to memory map game data from the flash instead of actually loading into RAM - nothing quite like a challenge eh?
More comments on Mastodon.