nxt screenshot
There is no screenshot utility for Linux, and I like automated tools, so I decided to make my own.
As many things in NXT, the current screen content is accessible using a IO map. So, one more time, let's have a look to NXT firmware code. This is the DISPLAY module IO map:
typedef struct { void (*pFunc)(UBYTE,UBYTE,UBYTE,UBYTE,UBYTE,UBYTE); // Simple draw entry ULONG EraseMask; // Section erase mask (executed first) ULONG UpdateMask; // Section update mask (executed next) FONT *pFont; // Pointer to font file UBYTE *pTextLines[TEXTLINES]; // Pointer to text strings UBYTE *pStatusText; // Pointer to status text string ICON *pStatusIcons; // Pointer to status icon collection file BMPMAP *pScreens[SCREENS]; // Pointer to screen bitmap file BMPMAP *pBitmaps[BITMAPS]; // Pointer to free bitmap files UBYTE *pMenuText; // Pointer to menu icon text UBYTE *pMenuIcons[MENUICONS]; // Pointer to menu icon images ICON *pStepIcons; // Pointer to step icon collection file UBYTE *Display; // Display content copied to physical display UBYTE StatusIcons[STATUSICONS]; // Index in status icon collection file UBYTE StepIcons[STEPICONS]; // Index in step icon collection file UBYTE Flags; // Update flags enumerated above UBYTE TextLinesCenterFlags; // Mask to center TextLines UBYTE Normal[DISPLAY_HEIGHT / 8][DISPLAY_WIDTH]; // Raw display memory for normal screen UBYTE Popup[DISPLAY_HEIGHT / 8][DISPLAY_WIDTH]; // Raw display memory for popup screen } IOMAPDISPLAY;
Interesting fields are:
- Normal: display memory, containing the image displayed on screen.
- Flags and Popup: I did not used them, but you could test for the DISPLAY_POPUP bit in Flags to know whether the image should be read from Popup rather than Normal.
As you can see, DISPLAY_HEIGHT is divided by 8. This is because the NXT stores 8 vertical pixels in one byte. In each byte, the LSB (least significant bit) corresponds to the top pixel, and the MSB (most significant bit) corresponds to the bottom pixel.
Here is a illustration of this format:
I have everything I need to implement the screenshot tool:
#!/usr/bin/env python # # screenshot.py - Capture the NXT screen content. # # Copyright (C) 2010 Nicolas Schodet # # <download file for full MIT license> # import nxt.locator import nxt.brick from struct import unpack, pack import Image from optparse import OptionParser # Thoses are extracted from firmware sources. DISPLAY_MODULE_ID = 0x000a0001 DISPLAY_SCREEN_OFFSET = 119 DISPLAY_WIDTH = 100 DISPLAY_HEIGHT = 64 # Read no more than 32 bytes per request. IOM_CHUNK = 32 def screenshot (b): '''Take a screenshot, return a PIL image.''' # Read pixels. pixels = [] for i in xrange (0, DISPLAY_WIDTH * DISPLAY_HEIGHT / 8, IOM_CHUNK): mod_id, n_bytes, contents = b.read_io_map (DISPLAY_MODULE_ID, DISPLAY_SCREEN_OFFSET + i, IOM_CHUNK) pixels += unpack ('32B', contents) # Transform to a PIL format. pilpixels = [] bit = 1 linebase = 0 for y in xrange (0, DISPLAY_HEIGHT): # Read line by line. for x in xrange (0, DISPLAY_WIDTH): if pixels[linebase + x] & bit: pilpixels.append (0) else: pilpixels.append (255) bit <<= 1 # When 8 lines have been read, go on with the next byte line. if bit == (1 << 8): bit = 1 linebase += DISPLAY_WIDTH # Return a PIL image. pilbuffer = pack ('%dB' % DISPLAY_WIDTH * DISPLAY_HEIGHT, *pilpixels) pilimage = Image.frombuffer ('L', (DISPLAY_WIDTH, DISPLAY_HEIGHT), pilbuffer, 'raw', 'L', 0, 1) return pilimage # Get filename from command line. parser = OptionParser (usage = "usage: %prog [options] image_file") (options, args) = parser.parse_args () if len (args) != 1: parser.error ("no image filename provided") filename = args[0] # Find the brick and take the screenshot. sock = nxt.locator.find_one_brick () if sock: image = screenshot (sock.connect ()) image.save (filename) sock.close ()
Here is the result: