fix(render): evaluate multi-input Type 0 tint transforms for DeviceN - #949
Merged
Merged
Conversation
Separation/DeviceN colour spaces with a Type 0 (sampled) tint transform only ever passed the first scn/SCN component to the evaluator, discarding every other input channel. That was invisible for the common single-channel Separation case, but a multi-channel DeviceN colour space always supplies more than one component: the first-component-only read silently reduced a genuinely N-dimensional sampled function to a 1-D lookup on one axis, which then usually fell outside evaluate_type0_sampled's declared 1-D-only envelope and fell back to the old "gray = 1 - tint" heuristic. Depending on the value of just the first component that produced solid black or solid white (invisible against a white page) regardless of what the other components actually were. Generalise the sampled-function evaluator to N-dimensional multilinear interpolation over the sample grid (the existing linear-interpolation code is exactly the N=1 case), and thread the full component vector through for Type 0. Type 2 (exponential) and Type 3 (stitching) stay single-input, matching their spec definition. Signed-off-by: Yury Fedoseev <yfedoseev@gmail.com>
yfedoseev
force-pushed
the
fix/issue-859-devicen-multi-input-type0-tint
branch
from
July 26, 2026 18:48
d68f6fb to
ed16f36
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #859.
Corrected root cause. The issue title (and its own follow-up triage comment) suspected Type 4 (PostScript calculator) tint transforms. That's stale — the actual repro file (
devicen.pdf, a pdf.js corpus fixture) has an incremental update, so the live content stream (verified withpikepdf, which resolves the xref/generation chain correctly — naive byte-search over the raw file finds a stale first revision) uses a Type 0 (sampled) tint transform on a 3-channel/DeviceN [/Red /Green /Blue]color space,/Domain [0 1 0 1 0 1],/Size [13 13 13]— genuinely 3-dimensional.resolve_separation_or_deviceninsrc/rendering/resolution/color.rsonly ever forwardedcomponents[0](the firstscnoperand) to the Type 0/3 evaluator, discarding every other input channel. That's invisible for the common single-channel Separation case, but this DeviceN space always supplies 3 components. The 1-D-only sampled-function evaluator (evaluate_type0_sampled) then rejected the request (Size.len() == 3 != 1) and fell back to the long-standinggray = 1 - components[0]heuristic. Shape 1's first component was1.0→ black. Shapes 2 and 3's first component was0.0→ pure white, invisible against the white page — the "missing shapes" symptom. Type 4 dispatch (already correctly threading the full component vector, from #849) was never the problem.Fix. Generalized
evaluate_type0_sampledto N-dimensional multilinear interpolation across the2^Nsample-grid corners (ISO 32000-1 §7.10.2's general algorithm, of which the prior 1-D linear interpolation is the N=1 special case), with aMAX_SAMPLED_FUNCTION_DIMS = 8guard against a pathological/Sizearray. Threaded the fullcomponentsslice through for Type 0 (Type 2/3 stay single-input, matching their spec definition).Test plan
cargo build,cargo fmt --check,cargo clippy --features rendering -- -D warnings— clean.cargo test --lib --features rendering rendering::resolution::color— 16/16 pass, including two new tests:type0_sampled_function_single_dimension_matches_prior_linear_interpolation(pins the N=1 case is byte-for-byte unchanged) andtype0_sampled_function_two_dimensional_input_uses_all_components(hand-computed 2-D grid, asserts a non-first-channel-dominant input produces the correct interpolated result).cargo test --features rendering— full suite green.corpus_sigover the full local corpus (3530 PDFs) — zero-diff, identical output betweenmainand this branch. (Expected:corpus_sigmeasures text-extraction signatures; this change only affects rendering-time color resolution, so it's a true negative-control confirmation that nothing else moved, not a direct exercise of the fix.)devicen.pdf, not committed — CONTRIBUTING.md fixture policy): before, one black triangle + two invisible-white ones; after, three distinct green/blue/red triangles, matching poppler's reference rendering described in the issue.