Skip to content

Scale PGM and PPM samples to the full range before converting them to the pixel type - #3227

Merged
JimBobSquarePants merged 4 commits into
SixLabors:mainfrom
jafin:jf/pbm-maxval-scaling
Oct 28, 2026
Merged

JimBobSquarePants merged 4 commits into
SixLabors:mainfrom
jafin:jf/pbm-maxval-scaling

Conversation

@jafin

@jafin jafin commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

PGM and PPM samples were packed into the destination pixel format before a separate brightness pass applied Maxval. Narrow destinations lost precision before that pass could normalize the values. The initial fix also rounded low-Maxval byte samples to bytes before converting them to higher-precision pixels.

Non-standard ranges now normalize into a reusable float row before the existing bulk pixel-packing API stores the result. Integer rounding occurs at the destination precision. Full-range inputs retain the existing byte and ushort packing paths. The separate brightness pass is removed. Traversal remains scalar; SIMD work is deferred to integration into the ICC branch.

Sample saturation is independent of SegmentIntegrityHandling. Early EOF returns partial output. Available channels in an incomplete pixel are retained where possible, and missing bytes are zero-filled rather than read from pooled memory. Decimal sample overflow saturates instead of wrapping. Overflowing header fields remain fatal before image allocation.

Binary header parsing also stops at the raster boundary. The PGM specification and PPM specification require one separator before binary samples. Whitespace-valued raster bytes and # must remain sample data.

Tests load stored inputs. No headers or rasters are generated at runtime. Cases cover P2/P3/P5/P6 at Maxval 100 and 1000, exact byte and ushort output, saturation in every integrity mode, partial rows and pixels, decimal overflow, and raster bytes that resemble header whitespace. Existing reference images and tolerances are unchanged; the new precision cases use exact assertions.

All 140 PBM tests pass in Release on .NET 10 and .NET 11, with no failures or skips.

Fixes #3239
V4 backport: #3260.

… the pixel type

A non-standard maxval was applied with a brightness pass after the samples had been
converted to the destination pixel type. A 16-bit sample with a small maxval was
narrowed to 8 bits first, so decoding to an 8-bit pixel type such as the default
Rgb24 kept only a few distinct levels (maxval 1000 gave five).

The plain and binary decoders now scale every sample from [0, maxval] to the full
8-bit or 16-bit range with rounded integer arithmetic before the pixel conversion,
and the extra brightness pass over the image is removed.
@JimBobSquarePants
JimBobSquarePants merged commit 269a6d0 into SixLabors:main Oct 28, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PGM/PPM images with a 16-bit maxval other than 65535 decode to only a few gray levels

2 participants