gamut-png's palette handling does the minimum: build_indexed (src/reduce.rs:250-277) trims
trailing opaque tRNS entries, and that is all.
Missing
Ordering. palette.push(key) (src/reduce.rs:104) assigns indices in raster-scan
first-appearance order. There is no sort in the file at all. Index order directly changes the byte
stream DEFLATE sees, and adjacency changes how well the row filters predict — oxipng uses
modified-Zeng ordering (plus Battiato) and reports it as its best size-per-line-of-code
reduction pass. Sorting transparent entries first would also make the existing trailing-opaque
trim actually pay.
The caller-supplied path. encode_indexed8 (src/encoder.rs:284) only range-checks indices
and writes palette.plte() verbatim. No dedupe, no unused-entry removal, no depth re-derivation.
A caller handing in a 256-entry palette that uses three colours gets 768 wasted PLTE bytes and
depth 8 where depth 2 would do. (The auto-reduce path is fine here by construction — its palette
is built from the pixels, so it can contain neither duplicates nor unused entries.)
Trailing-opaque trim on the caller path. PngPalette::trns() (src/palette.rs:113-119)
returns the whole alpha vector; only the auto-reduce path trims.
Note
Palette overhead is incompressible, so this interacts with the cost model: #224 added
write_reduced_or_native, which races the palette encoding against the unreduced one and keeps
the smaller. A smaller PLTE shifts that crossover, so this work makes the palette win on more
images, not just win by more.
Refs #224
gamut-png's palette handling does the minimum:build_indexed(src/reduce.rs:250-277) trimstrailing opaque
tRNSentries, and that is all.Missing
Ordering.
palette.push(key)(src/reduce.rs:104) assigns indices in raster-scanfirst-appearance order. There is no sort in the file at all. Index order directly changes the byte
stream DEFLATE sees, and adjacency changes how well the row filters predict — oxipng uses
modified-Zeng ordering (plus Battiato) and reports it as its best size-per-line-of-code
reduction pass. Sorting transparent entries first would also make the existing trailing-opaque
trim actually pay.
The caller-supplied path.
encode_indexed8(src/encoder.rs:284) only range-checks indicesand writes
palette.plte()verbatim. No dedupe, no unused-entry removal, no depth re-derivation.A caller handing in a 256-entry palette that uses three colours gets 768 wasted
PLTEbytes anddepth 8 where depth 2 would do. (The auto-reduce path is fine here by construction — its palette
is built from the pixels, so it can contain neither duplicates nor unused entries.)
Trailing-opaque trim on the caller path.
PngPalette::trns()(src/palette.rs:113-119)returns the whole alpha vector; only the auto-reduce path trims.
Note
Palette overhead is incompressible, so this interacts with the cost model: #224 added
write_reduced_or_native, which races the palette encoding against the unreduced one and keepsthe smaller. A smaller
PLTEshifts that crossover, so this work makes the palette win on moreimages, not just win by more.
Refs #224