Description
Decoding an indexed PNG with pngdec's PNG_COPY mode on the Inky Frame 7.3"
Spectra display produces unexpected colors. While black (index 0) appears as
black and white (index 1) appears as white, green (index 2) appears as yellow,
blue (index 3) appears as red, red (index 4) renders as a blank white band, and
yellow (index 5) appears as blue.
The same image decoded with PNG_POSTERISE (the default) renders the correct
colors.
Input PNG image (one vertical "stripe" per index; representative colors have been added to aid visualisation):
As it appears on an Inky Frame (Spectra 6) when decoded using PNG_COPY mode:

Environment
- Device: Inky Frame 7.3" Spectra (
DISPLAY_INKY_FRAME_SPECTRA_7)
- Firmware: Pimoroni MicroPython, latest stable release
- Libraries:
pngdec, picographics (bundled with firmware)
Impact
Colors produced by PNG_COPY do not match the documented Inky 7 palette
indices. This also prevents using PNG_COPY as a faster alternative to
PNG_POSTERISE, since the output is incorrect on Spectra displays.
Reproduction steps
-
Create a 6-color indexed PNG (palette-test.png) with one vertical "stripe"
per palette index, 0 through 5, using the standard Inky 7 color order. (Or use the attached image.)
-
Decode and display it with PNG_COPY:
display = PicoGraphics(display=DISPLAY)
png = PNG(display)
png.open_file("palette-test.png")
png.decode(0, 0, mode=PNG_COPY)
display.update()
-
Observe the rendered output.
Expected behavior
Each band renders the color corresponding to its palette index using the
standard Inky 7 scheme:
| Palette index |
Expected color |
| 0 |
Black |
| 1 |
White |
| 2 |
Green |
| 3 |
Blue |
| 4 |
Red |
| 5 |
Yellow |
Actual behavior
Colors are scrambled. The raw palette indices are written directly to the
Spectra framebuffer without remapping, so they hit the Spectra hardware palette
instead of the standard Inky 7 palette:
| Palette index |
Expected (Inky 7) |
Actual (Spectra hardware) |
| 0 |
Black |
Black (correct) |
| 1 |
White |
White (correct) |
| 2 |
Green |
Yellow |
| 3 |
Blue |
Red |
| 4 |
Red |
White (non-functional) |
| 5 |
Yellow |
Blue |
Analysis
The Spectra display uses a different physical color ordering than the standard
Inky Frame 7-color displays. The standard Inky 7 palette assigns index 4 to Red,
but on Spectra hardware index 4 is non-functional (a missing color slot). To
handle this, ModPicoGraphics_set_pen() includes a remapping switch that
translates standard Inky 7 indices to the Spectra hardware layout, deliberately
avoiding index 4:
// picographics.cpp, line 834
if(self->display_type == DISPLAY_INKY_FRAME_SPECTRA_7
&& (PicoGraphicsPenType)self->graphics->pen_type == PEN_INKY7) {
switch(pen) {
case 0: case 1: break; // Black, White — unchanged
case 2: pen = 6; break; // Green → hardware 6
case 3: pen = 5; break; // Blue → hardware 5
case 4: pen = 3; break; // Red → hardware 3
case 5: pen = 2; break; // Yellow → hardware 2
case 6: case 7: pen = 2; break; // Orange/Taupe → Yellow
}
}
This remapping is applied when Python code calls display.set_pen(), so drawing
operations render the correct colors on Spectra displays. However, the same
remapping appears to have been missed in the PNG decode path. When pngdec
decodes an image in MODE_COPY, it calls set_pen() on the C++ PicoGraphics
object directly (pngdec.cpp), bypassing ModPicoGraphics_set_pen() and
its Spectra remapping:
// pngdec.cpp, PNG_PIXEL_INDEXED / MODE_COPY branch:
current_graphics->set_pen(i); // raw index, no Spectra remapping
The raw palette index from the PNG is written to the framebuffer without
translation. This affects both the PNG_PIXEL_INDEXED and PNG_PIXEL_GRAYSCALE
branches of the decode callback.
Description
Decoding an indexed PNG with
pngdec'sPNG_COPYmode on the Inky Frame 7.3"Spectra display produces unexpected colors. While black (index 0) appears as
black and white (index 1) appears as white, green (index 2) appears as yellow,
blue (index 3) appears as red, red (index 4) renders as a blank white band, and
yellow (index 5) appears as blue.
The same image decoded with
PNG_POSTERISE(the default) renders the correctcolors.
Input PNG image (one vertical "stripe" per index; representative colors have been added to aid visualisation):
As it appears on an Inky Frame (Spectra 6) when decoded using
PNG_COPYmode:Environment
DISPLAY_INKY_FRAME_SPECTRA_7)pngdec,picographics(bundled with firmware)Impact
Colors produced by
PNG_COPYdo not match the documented Inky 7 paletteindices. This also prevents using
PNG_COPYas a faster alternative toPNG_POSTERISE, since the output is incorrect on Spectra displays.Reproduction steps
Create a 6-color indexed PNG (
palette-test.png) with one vertical "stripe"per palette index, 0 through 5, using the standard Inky 7 color order. (Or use the attached image.)
Decode and display it with
PNG_COPY:Observe the rendered output.
Expected behavior
Each band renders the color corresponding to its palette index using the
standard Inky 7 scheme:
Actual behavior
Colors are scrambled. The raw palette indices are written directly to the
Spectra framebuffer without remapping, so they hit the Spectra hardware palette
instead of the standard Inky 7 palette:
Analysis
The Spectra display uses a different physical color ordering than the standard
Inky Frame 7-color displays. The standard Inky 7 palette assigns index 4 to Red,
but on Spectra hardware index 4 is non-functional (a missing color slot). To
handle this,
ModPicoGraphics_set_pen()includes a remapping switch thattranslates standard Inky 7 indices to the Spectra hardware layout, deliberately
avoiding index 4:
This remapping is applied when Python code calls
display.set_pen(), so drawingoperations render the correct colors on Spectra displays. However, the same
remapping appears to have been missed in the PNG decode path. When
pngdecdecodes an image in
MODE_COPY, it callsset_pen()on the C++PicoGraphicsobject directly (
pngdec.cpp), bypassingModPicoGraphics_set_pen()andits Spectra remapping:
The raw palette index from the PNG is written to the framebuffer without
translation. This affects both the
PNG_PIXEL_INDEXEDandPNG_PIXEL_GRAYSCALEbranches of the decode callback.