Skip to content

pngdec PNG_COPY produces wrong colors on Spectra display #1120

@ithinkihaveacat

Description

@ithinkihaveacat

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):

Image

As it appears on an Inky Frame (Spectra 6) when decoded using PNG_COPY mode:

Image

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

  1. 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.)

  2. 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()
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions