Images
A guide to constructing and drawing images on the Arduboy.
The native image format
The native image format for the Arduboy is driven by the organization of the software-side screen buffer, whose format is driven by the hardware defaults. By default, the hardware reads information one byte at a time to draw eight pixels vertically, wrapping for rows, and repeats the process until the entire buffer has been read and displayed. Sound confusing? That's okay. We're going to break it down.
The Arduboy has a black and white OLED screen so each pixel's on or off state can be represented by a bit. As there is no native bit type in C/++, it would be quite wasteful to dedicate a full integer to a single pixel. Therefore the format uses bytes in the form of the uint8_t
type to store eight pixels, each taking a single bit.
Most image conversion tools represent these groups of pixels in hex, like 0x3F
, but you can also write out values in "long form" using the binary format, B00111111
. For the purposes of this page we'll use the binary format as it is easier to explain.
uint8_t value = 0x3F;
uint8_t value2 = B00111111;
On the Arduboy (Arduino) platform numbers are "little-endian" which means the left most bit, as shown above in the binary format, is called the most significant bit (MSB) and the right most bit is the least significant bit (LSB). Correspondingly the LSB stores the lowest y-value's pixel state and the MSB stores the greatest y-value's state.
For an example, let's take a look at the image data for a simple 8x8 pixel smiley face:
(This image has been blown up to make it easier to see.)
const uint8_t PROGMEM smiley[] = { B01111110, B10000001, B10010101, B10100001, B10100001, B10010101, B10000001, B01111110 };
Take a look at the third byte. If we start from the rightmost bit and read leftwards, the values 1 and 0 correspond to the on or off state of the third vertical line in the original image: on, off, on, off, on, off, off, on.
Now if we have an image taller than eight pixels, as many are likely to be, how do we handle that? If we add a frowny face below the smiley face, we would end up with image data as follows.
const uint8_t PROGMEM smilies[] = { B01111110, B10000001, B10010101, B10100001, B10100001, B10010101, B10000001, B01111110,
B01111110, B10000001, B10100101, B10010001, B10010001, B10100101, B10000001, B01111110 };
Many tools and people like to format image data in this manner to add a line break after every row of bytes.
Drawing native images
The core library offers a function called drawBitmap
for transferring image data from program memory (PROGMEM
) to the software screen buffer. The function takes a location (x, y), pointer to image data, width, height, and color. The width and height values are important for drawing bitmaps as the function drawBitmap
needs to know how big the image is so it doesn't read past the end of the data or draw it all on one row.
Using the last image data from the previous section, we can draw it to the screen using drawBitmap
like so:
arduboy.drawBitmap(0, 0, smilies, 8, 16, WHITE);
Horizontally-oriented (slow) images
The core library also has support for what it calls "slow bitmaps." These are images with pixels stored horizontally first instead of vertically. This means the first byte of data contains the first eight pixels of the first row, all with the same y-offset. This data is stored in "big-endian" format so the leftmost bit stores the leftmost x value. The rightmost bit stores the rightmost x value. This can be easier to grasp and is potentially easier to generate. With proper formatting, ones and zeros in the bit notation we've been using visually map to pixels as seen in the original image. As a result of these changes, however, it takes longer to draw as it must be converted to the native format for the display buffer.
Using the same smilies image data from the last example, we can rewrite it as follows:
const uint8_t PROGMEM smiliesSlow[] = { B01111110,
B10000001,
B10100101,
B10000001,
B10100101,
B10011001,
B10000001,
B01111110,
B01111110,
B10000001,
B10100101,
B10000001,
B10011001,
B10100101,
B10000001,
B01111110 };
Depending on the formatting used, this image format can take up more visual space in a program and may be better suited to includes than the base .ino file.
Drawing this image using the core library calls a differently named function taking the same arguments as the one for native images:
arduboy.drawSlowXYBitmap(0, 0, smiliesSlow, 8, 16, WHITE);