Summary
with_background_gray(u16) / with_background_rgb(u16, u16, u16) take sample values in the input's depth. Since #338, with_auto_reduce(true) may write the image at a lower depth than the input — 16-bit demoted to 8-bit when every sample is k·257, 8-bit grey packed to 1/2/4 bits when every sample is a multiple of the depth's scale — and the bKGD sample is not rescaled with the samples.
PR #485 makes bKGD and sBIT follow the colour type actually written and omits a sample the written depth cannot hold (libpng rejects value >= 1 << depth), so the chunk libpng drops is no longer emitted. What remains is the semantic gap inside the accepted range:
- a 16-bit source whose samples all demote, with a background of
(200, 200, 200) (16-bit, near black), is written at depth 8 with bKGD = (200, 200, 200) — now 78% grey;
- an 8-bit grey source packed to depth 2 with a background of
170 (a legal sample, 2 × 85) has its bKGD omitted rather than written as code 2, because the writer cannot tell an 8-bit 170 that needs scaling from a depth-2 170 that is out of range.
Fix shape
ancillary::bkgd_for needs the source depth as well as the written one, so it can apply exactly the sample mapping reduce applied: v / 257 when v % 257 == 0 for 16→8, v / scale(depth) when divisible for sub-byte grey, and omission otherwise. That is a source_depth: u8 threaded through write_reduced_or_native → write_reduced → write_png (the callers all know it: encode_8bit/encode_alpha8 are 8, encode_16bit/encode_alpha16 are 16, encode_indexed8 is 8). sBIT does not need it — a significant-bit count is absolute, and the depth check already covers it.
Why not in #485
The review finding that PR repaired names the colour type; this is the depth axis of the same defect, pre-dating the race (#338), and its repair widens four signatures the finding does not name. Recorded there as a residual.
Test
Oracle-free exact-byte: encode a demotable Rgb16 image with a k·257 background and assert bKGD carries k; encode a packable Gray8 image with a background of 170 and assert bKGD = [0, 2] at depth 2. Both through libpng's decode for the file around the chunk.
Summary
with_background_gray(u16)/with_background_rgb(u16, u16, u16)take sample values in the input's depth. Since #338,with_auto_reduce(true)may write the image at a lower depth than the input — 16-bit demoted to 8-bit when every sample isk·257, 8-bit grey packed to 1/2/4 bits when every sample is a multiple of the depth's scale — and thebKGDsample is not rescaled with the samples.PR #485 makes
bKGDandsBITfollow the colour type actually written and omits a sample the written depth cannot hold (libpng rejectsvalue >= 1 << depth), so the chunk libpng drops is no longer emitted. What remains is the semantic gap inside the accepted range:(200, 200, 200)(16-bit, near black), is written at depth 8 withbKGD = (200, 200, 200)— now 78% grey;170(a legal sample,2 × 85) has itsbKGDomitted rather than written as code2, because the writer cannot tell an 8-bit170that needs scaling from a depth-2170that is out of range.Fix shape
ancillary::bkgd_forneeds the source depth as well as the written one, so it can apply exactly the sample mappingreduceapplied:v / 257whenv % 257 == 0for 16→8,v / scale(depth)when divisible for sub-byte grey, and omission otherwise. That is asource_depth: u8threaded throughwrite_reduced_or_native→write_reduced→write_png(the callers all know it:encode_8bit/encode_alpha8are 8,encode_16bit/encode_alpha16are 16,encode_indexed8is 8).sBITdoes not need it — a significant-bit count is absolute, and the depth check already covers it.Why not in #485
The review finding that PR repaired names the colour type; this is the depth axis of the same defect, pre-dating the race (#338), and its repair widens four signatures the finding does not name. Recorded there as a residual.
Test
Oracle-free exact-byte: encode a demotable
Rgb16image with ak·257background and assertbKGDcarriesk; encode a packableGray8image with a background of170and assertbKGD = [0, 2]at depth 2. Both through libpng's decode for the file around the chunk.