Warning: run time for large images (> 256 × 256?) is awful.
Input Image
Encoded Regions
Flat Rectangles/LinesBitmapped
Output Data
Format:
Sort flats by size (default: by color)
File name:
Output File Format
Header: DWORD, equals 0x371e5453 ("ST", 7735).
Width: BYTE. Width of the image, except for 0 → 256 pixels.
Height: BYTE. Height of the image, except for 0 → 256 pixels.
Colors: BYTE. Number of indexed colors (2-255) in the image, except for 0 → 256 colors, or 1 → full color (suppresses palette section; use only high-color commands).
Palette: if Colors = 1, zero bytes; else, (Colors) WORDs (Colors = 0 → 256 WORDs).
Each color entry: 16 bits, 5-6-5 RGB format.
Commands: remainder of file (until EOF, or termination command). If EOF is reached during a command, assume zeroes for any remaining parameter bytes in that command.
0x00: No Operation. Length: 1 byte. No effect.
0x01: Point From Palette. Length: 4 bytes. Parameters: Color (BYTE), xStart (BYTE), yStart (BYTE). Draws a single pixel at (xStart, yStart).
0x02: Horizontal Line From Palette. Length: 5 bytes. Parameters: Color (BYTE), xStart (BYTE), yStart (BYTE), Len (BYTE). Draws a horizontal line, starting at (xStart, yStart), to the right for a total Len length. If xStart + Len > Width, only the visible segment is drawn.
0x03: Vertical Line From Palette. Length: 5 bytes. Parameters: Color (BYTE), xStart (BYTE), yStart (BYTE), Len (BYTE). Draws a vertical line, starting at (xStart, yStart), downwards for a total Len length. If yStart + Len > Height, only the visible segment is drawn.
0x04: Diagonal line From Palette. Length: 6 bytes. Parameters: Color (BYTE), xStart (BYTE), yStart (BYTE), xW (BYTE), yH (BYTE). Draws a diagonal line (any slope). Not yet implemented.
0x05: Solid Rectangle From Palette. Length: 6 bytes. Parameters: Color (BYTE), xStart (BYTE), yStart (BYTE), xW (BYTE), yH (BYTE). Draws a filled rectangle (xW*yH pixels total). If xStart + xW > Width or yStart + yH > Height, only the visible segment is drawn.
0x12: Raw Bitmap Horizontal Line From Palette. Length: 4+Len bytes. Parameters: xStart (BYTE), yStart (BYTE), Len (BYTE), Data (BYTE dup Len). Draws a horizontal line, starting at (xStart, yStart), to the right for a total Len length, with the specified sequence of pixels. If xStart + Len > Width, only the visible segment is drawn.
0x13: Raw Bitmap Vertical Line From Palette. Length: 4+Len bytes. Parameters: xStart (BYTE), yStart (BYTE), Len (BYTE), Data (BYTE dup Len). Draws a vertical line, starting at (xStart, yStart), downwards for a total Len length, with the specified sequence of pixels. If yStart + Len > Height, only the visible segment is drawn.
0x15: Raw Bitmap Rectangle From Palette. Length: 5+xW*yH bytes. Parameters: xStart (BYTE), yStart (BYTE), xW (BYTE), yH (BYTE), Data (BYTE dup xW*yH). Draws a rectangle (xW*yH pixels total) filled with the specified sequence of pixels. If xStart + xW > Width or yStart + yH > Height, only the visible segment is drawn.
Bitmap note: add 0x10 to command byte value to specify a bitmap command. These commands are listed separately above for clarity.
High Color: add 0x40 to command byte value to specify a high color command. Color parameter becomes WORD size (5-6-5 RGB format) (+1+Len bytes command length). Not yet implemented.
Long format: Set Header to 0x371f5354 ("ST" 7735+1). Replace Widths, Heights and locations (xStart, yStart) with WORDs; maximum image size 65536 x 65536. Not yet implemented.
Text format: Full 24-bit color is given, exact to the original image. The other formats use 16-bit (5-6-5 RGB); the extra bits in the original image are discarded (rounded down).
C header format: Header DWORD, Width and Height are #define'd. All BYTEs are uint8_t, WORDs are uint16_t, etc. Termination byte is obligatory, but array lengths are provided to easily calculate offsets.
Only the header is given above; the accompanying C code to use it is as follows (as an avr-gcc ROM declaration):
#include "Header_Name.h"
#ifdef HEADER_NAME_H_INCLUDED
// Place this at the top, or in the common header file:
const uint8_t ImageData[HEADER_NAME_TOTAL_LEN];
// Reference this with: drawImage(ImageData, x, y);
const uint8_t ImageData[] PROGMEM = {
(HEADER_NAME_PALETTE_LEN & 0x00ff),
HEADER_NAME_PAL,
HEADER_NAME_CMDS
};
#endif // HEADER_NAME_H_INCLUDED
Execution speed: Without a locate command on the ST7735 display controller, and the set-region command being fairly lengthy, the best writing option is to fill rectangular regions. Regions should generally be non-overlapping to avoid redraw, but some is acceptable. Each set-region command takes 10 bytes of SPI transfers—make the most of it. This makes drawing transparent images, diagonal lines, etc. fairly painstaking.
Compression/Encoding: This "compressor" tool only generates line and rectangle commands, when they are of adequate size. Everything else is considered "random" data and expressed as bitmap regions. This is effective on mostly-flat images—line drawings and such, and ineffective on high color images which generate mostly bitmap regions. A hand-written image (or a much smarter encoder..) could take better advantage of the command set.
Transparency is implicitly part of the format: any pixels that aren't drawn by command, are left unchanged.
Future Improvements, Speculation
I would love to be wrong about the ST7735's limited command set... (On that note, it appears there is one pair of undocumented commands, but they're not very useful here.)
Depending on implementation, palette data may not be checked for bounds. In case of un-checked overrun, most likely some instruction data will be read as colors. This suggests interesting opportunities for highly optimized files...
If a few more shapes are implemented, this could become a proper vector format of sorts. Downside: antialiasing would require a canvas (and a lot of processing), or video memory, both impractical on a small MCU.
An RLE Bitmap Rectangle command might be nice. This would RLE compress a given block, using a scheme similar to, say, Windows RLE. This gives another step between encoding large flat regions, and giving up and encoding whole raw bitmap regions. The fill-rectangle threshold would be higher, and RLE vs. raw would be decided on a per-block basis. Alternately, RLE can be used for much, or all of, the image, simplifying the command set.
Adding a set-color command or flag may prove useful. In that case, the decoder sets a state variable (current color), and draws flat-colored commands in that color. Maybe this can be switched too, so you can go back to using per-command colors on single pixel writes. Or just use even more command slots, etc.
A few bits could be saved by packing the Command, Len, xW and yH bytes, and maybe others. Unused bits could be packed away, or Huffman coding used. For larger, more complicated (high color) images, just using a zlib format is probably better.
For the C header format, the palette could be generated as a separate array, referenced by label. This would allow common palette(s) to be used by many images; then, the "image", as such, is just a string of commands. Similarly, an "indirect bitmap" command could be used, which references a separate bitmap array. This would allow patterns or sprites to be reused by many images, without incurring overhead—something of a 2D zip compression method.
Commands could also be packed in a different way. All draw commands except Point have the redundant information that zero width or height is a no-op. This should probably be wrapped to 256, as done with Width and Height. They could however be used as a flag to read one of (x, y, color) as a command byte, and that command byte is repeated going forward until changed again. This would save significantly on command bytes, in the same way that runs of colors can save using a color-change command. Point itself can't hold any such info, though; maybe it could be left until the end, with the remaining (x, y, color) triples assumed to be Points, and EOF is required for termination. Header format would have to include an array length #define, or use sizeof.
Output File Format
Options, Notes
Long format: Set Header to 0x371f5354 ("ST" 7735+1). Replace Widths, Heights and locations (xStart, yStart) with WORDs; maximum image size 65536 x 65536. Not yet implemented.
Text format: Full 24-bit color is given, exact to the original image. The other formats use 16-bit (5-6-5 RGB); the extra bits in the original image are discarded (rounded down).
C header format: Header DWORD, Width and Height are #define'd. All BYTEs are uint8_t, WORDs are uint16_t, etc. Termination byte is obligatory, but array lengths are provided to easily calculate offsets.
Only the header is given above; the accompanying C code to use it is as follows (as an avr-gcc ROM declaration):
Execution speed: Without a locate command on the ST7735 display controller, and the set-region command being fairly lengthy, the best writing option is to fill rectangular regions. Regions should generally be non-overlapping to avoid redraw, but some is acceptable. Each set-region command takes 10 bytes of SPI transfers—make the most of it. This makes drawing transparent images, diagonal lines, etc. fairly painstaking.
Compression/Encoding: This "compressor" tool only generates line and rectangle commands, when they are of adequate size. Everything else is considered "random" data and expressed as bitmap regions. This is effective on mostly-flat images—line drawings and such, and ineffective on high color images which generate mostly bitmap regions. A hand-written image (or a much smarter encoder..) could take better advantage of the command set.
Transparency is implicitly part of the format: any pixels that aren't drawn by command, are left unchanged.
Future Improvements, Speculation
I would love to be wrong about the ST7735's limited command set... (On that note, it appears there is one pair of undocumented commands, but they're not very useful here.)
Depending on implementation, palette data may not be checked for bounds. In case of un-checked overrun, most likely some instruction data will be read as colors. This suggests interesting opportunities for highly optimized files...
If a few more shapes are implemented, this could become a proper vector format of sorts. Downside: antialiasing would require a canvas (and a lot of processing), or video memory, both impractical on a small MCU.
An RLE Bitmap Rectangle command might be nice. This would RLE compress a given block, using a scheme similar to, say, Windows RLE. This gives another step between encoding large flat regions, and giving up and encoding whole raw bitmap regions. The fill-rectangle threshold would be higher, and RLE vs. raw would be decided on a per-block basis. Alternately, RLE can be used for much, or all of, the image, simplifying the command set.
Adding a set-color command or flag may prove useful. In that case, the decoder sets a state variable (current color), and draws flat-colored commands in that color. Maybe this can be switched too, so you can go back to using per-command colors on single pixel writes. Or just use even more command slots, etc.
A few bits could be saved by packing the Command, Len, xW and yH bytes, and maybe others. Unused bits could be packed away, or Huffman coding used. For larger, more complicated (high color) images, just using a zlib format is probably better.
For the C header format, the palette could be generated as a separate array, referenced by label. This would allow common palette(s) to be used by many images; then, the "image", as such, is just a string of commands. Similarly, an "indirect bitmap" command could be used, which references a separate bitmap array. This would allow patterns or sprites to be reused by many images, without incurring overhead—something of a 2D zip compression method.
Commands could also be packed in a different way. All draw commands except Point have the redundant information that zero width or height is a no-op. This should probably be wrapped to 256, as done with Width and Height. They could however be used as a flag to read one of (x, y, color) as a command byte, and that command byte is repeated going forward until changed again. This would save significantly on command bytes, in the same way that runs of colors can save using a color-change command. Point itself can't hold any such info, though; maybe it could be left until the end, with the remaining (x, y, color) triples assumed to be Points, and EOF is required for termination. Header format would have to include an array length #define, or use sizeof.