Color Mathematics & Formats
August 20, 2026 · View on GitHub
Understanding bit layout, endianness swapping, and color space conversions in palettes.
1. RGB565 Bit Layout (16-bit Color)
Microcontroller displays (such as ST7789, ILI9341, GC9A01, SSD1351) commonly use 16-bit RGB565, packing 5 bits of red, 6 bits of green, and 5 bits of blue into a single 16-bit integer:
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
[ RED ] [ GREEN ] [ BLUE ]
5 bits 6 bits 5 bits
Conversion Formula from 24-bit RGB888:
Given 8-bit values :
def rgb888_to_rgb565(r: int, g: int, b: int) -> int:
"""Convert 8-bit R, G, B components to a 16-bit RGB565 integer."""
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
2. SPI Byte Swapping (swapped=True)
Most SPI TFT display controllers expect pixels in Big Endian format (High byte first over SPI), whereas ARM Cortex-M / RISC-V / x86 processors store 16-bit integers in Little Endian format.
If colors appear inverted or garbled on your panel:
from palettes import get_palette
# Without byte swap (native little-endian integers)
p_native = get_palette("wheel", color_depth=16, swapped=False)
# With byte swap (ready for direct SPI transmission)
p_swapped = get_palette("wheel", color_depth=16, swapped=True)
The Byte Swap Operation:
3. HSV to RGB Interpolation
The WheelPalette computes color sweeps by converting Hue (), Saturation (), and Value () to RGB:
- (Chroma)
Depending on the sextant , is assigned:
| Interval | |
|---|---|
| $0 \le H' < 1$ | |
| $1 \le H' < 2$ | |
| $2 \le H' < 3$ | |
| $3 \le H' < 4$ | |
| $4 \le H' < 5$ | |
| $5 \le H' < 6$ |
Final 8-bit components:
4. Depth Constants
| Constant | Bit Depth | Bytes / Pixel | Memory (320×240) |
|---|---|---|---|
1 | 1-bit monochrome | 1/8 byte | 9.6 KB |
2 | 2-bit grayscale | 1/4 byte | 19.2 KB |
4 | 4-bit grayscale | 1/2 byte | 38.4 KB |
8 | 8-bit grayscale | 1 byte | 76.8 KB |
16 | 16-bit RGB565 | 2 bytes | 153.6 KB |
24 | 24-bit RGB888 | 3 bytes | 230.4 KB |
32 | 32-bit ARGB8888 | 4 bytes | 307.2 KB |