Better screenshots for Gameduino CircuitPython

[The Gameduino 3X 5" screen is now in stock! It's a bright 800x480 display, 100% compatible with all Gameduino 3X software for Arduino, Teensy and CircuitPython. It's shipping same-day (free in the USA, $10 everywhere else) in the Excamera Labs store. Thanks!]
How do you get a copy of the Gameduino's screen onto your PC?
Of course, a lot of people just take a photograph, but for promotional stuff and documentation a digital screen capture looks much better.
The EVE hardware lets you read the screen pixels in as 24-bit R,G,B data, and there is a handy function in the eve library that does the hardware jiggling necessary to output a stream of RGB pixels.
The only remaining task is to write them into an image file. This is easier said than done. The EVE hardware has PNG and JPG readers built-in, but don't do image output at all.
One option is to use the ancient PPM format, which is just a header and a binary dump of the RGB bytes. But ppm isn't widely supported or used - it's a bit of a desperate hack, to be honest. This is 2021 - we ought to be doing better!
The ideal would be to have a PNG writer in pure Python. The full PNG specification involves a compressor and all kinds of complications. But a few years ago I came across this interesting idea: make a PNG writer that just uses a tiny subset of the PNG specification to write pure, uncompressed RGB bytes. That led to me making a C implementation suitable for embedded platform that only needed a few bytes of memory. And now it's a nice clean Python class called PngWriter.
To use it, you tell it the screen dimensions then feed it the RGB pixels. It writes out a legal PNG file, albeit one that doesn't have any compression. To any tools it's a regular PNG file -- there's nothing unusual about it.
So to write an all-green 40x25 pixel image "foo.png", you can:
with open("/sd/foo.png", "wb") as pngf:
p = PngWriter(pngf.write, 40, 25)
for y in range(25):
for x in range(40):
p.rgb(0, 255, 0)
combining this with the EVE screenshot function, the screenshot function is:
def screenshot(filename):
with open(filename, "wb") as pngf:
p = minpng.PngWriter(pngf.write, gd.w, gd.h)
def handle_line(rgb):
for i in range(gd.w):
r = rgb[3 * i + 0]
g = rgb[3 * i + 1]
b = rgb[3 * i + 2]
p.rgb(r, g, b)
gd.screenshot(handle_line)
Running it writes a PNG file to local SD storage:
There's some room for speedup - processing every pixel in CircuitPython means that the screenshot takes a couple of minutes to write out. Here's the photograph of the screen, and the same screen as an all-digital screenshot.


The screenshot demo for Python and CircuitPython is here.
Thanks for reading.