Repository navigation
Scaled and region decode for every codec - #10
Merged
Merged
Conversation
… a frozen box filter DecodeOptions.ScaleDenominator is no longer refused with NotSupportedException. A codec decodes at its largest native scale n <= d (JPEG up to 8, JPEG 2000 up to 2^levels, others 1), then reduces by d / n with the frozen box filter, then crops the Region (in scaled coordinates). TryGetClosestSize still reports only native sizes. - CodecSupport.BoxDownscaler (public): a streaming row accumulator (PushRow / TakeRow, column windows via GetSourceRect) plus a one-shot Downscale. Exact integer sums for 8/16-bit (u32 column sums with Vector256/Vector128 kernels and a scalar twin for 8-bit, u64 otherwise; box <= 2^32 pixels), double row-major sums for Float32; alpha-weighted without per-pixel rounding. - DecodeOptionsChecks: Validate(width, height), GetNativeScale(max), Apply(image, options, nativeScale); Validate(w, h, bool) is obsolete; the unshipped Validate(w, h, int) is removed. - Adapters: JPEG (1/8 then box, native region of the boxes' source rect), JPEG 2000 (largest reduction then box), TIFF and HEIF (native region of the source rect, then box; TryDecodeInto via the fallback), the rest via Apply. - Tests: BoxDownscaleTests (reference on every tier, windows, streaming, overflow limits, Magick.NET -scale matches exactly), ScaledDecodeFacadeTests (every format, frames, HEIF); NotSupportedException tests rewritten. CI and AGENTS.md tier filters include BoxDownscale; the AOT / wasm smoke decodes every facade image at 1/16 (whole and region) and its last frame at 1/2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lter) api-design.md defines the frozen box filter; known-gaps.md and each format page say which scales are native and that the rest decode, then box-filter; migrating-to-2.0.md and both READMEs say the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te it DecodeOptions has not shipped yet (v1.4.0 has none), so the overload goes outright, as ImagePixelFormat did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ominator (native, then a frozen box downscale) (#6) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TryDecode and TryDecodeInto with a Region or a ScaleDenominator now decode a window of rows (PngReader.DecodeWindow): rows are inflated and unfiltered only down to the window's last row and only as far right as its last column, the window's columns are expanded to TryDecode's layout, box-filtered as they arrive (BoxDownscaler) and, for TryDecodeInto, converted afterwards. The output equals the generic path (full decode, box, crop, convert) byte for byte. The decode stops after the last needed row, so image data below it is neither inflated nor checked; a window that reaches the bottom row still checks the stream's end. Adam7 keeps the generic path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…6 pixels The 64-bit division per output sample dominated streamed downscales at small factors (1920x1080 RGB, d=2: 36 ms total, 3 ms of it accumulation). Boxes of at most 2^16 pixels now sum in 32 bits (all sums and rounding terms stay below 2^32) and shift for full power-of-two boxes; the results are the same integers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Region / ScaleDenominator of a VP8 still no longer decode the whole frame: rows go from the macroblock-row decoder through full-width chroma upsampling, YUV->RGBA of the window's columns only, the decoder's layout (RGB or RGBA) and the streaming BoxDownscaler, then into the requested pixel format. Decoding stops after the macroblock row that emits the last needed row, so the bitstream below is neither decoded nor validated. Output is byte-identical to the generic path for every PixelFormat. Lossless and animations keep the generic path; the ALPH plane is still decoded whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
quick --scaled and peakmem --scaled time the native path against the baseline's generic one (checked equal first). Also fixes the baseline loader, which looked for the 3-parameter DecodeTo that now takes a CancellationToken. Results on a Ryzen 7 5800X: top region 0.18x, 1/2-1/8 0.68-0.93x, working memory ~0.3 MiB instead of ~15 MiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tills (#7) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`scaled` times TryDecode with a region or a scale against the baseline build's (the generic path on scaled-decode: full decode, box, crop) in the same process, checking identical pixels first; `scaled-peakmem` measures the working set of each in a fresh process. Results on a Ryzen 7 5800X: regions high in the image 0.11-0.49x of the generic path, scale-only 0.75-1.1x (the box filter is the same work), peak working set 8-15 MiB -> 0.05-2.9 MiB; the full-image decode matrix is unchanged (geomean 1.01x vs the baseline). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When every tile's displayed rectangle starts on a box edge and ends on one or at the image's edge (always for a single coded image; for a grid whose seams, through clap / irot / imir, fall on multiples of the factor), each tile converts its pixels to Rgb24 / Rgb48, pushes them in display order through its own BoxDownscaler and converts each finished row of boxes into the destination. No full-size image. Otherwise the existing path (full decode, then the box filter). Byte-identical to it for every PixelFormat, region and transform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d PNG (#8) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ondition, time and peak memory Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le (#9) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sy and HEIF Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
DecodeOptions.ScaleDenominator: native first (JPEG DCT 1/2–1/8, JPEG 2000 resolution levels), then a frozen box downscale (BoxDownscaler: gamma space, alpha-weighted average, integer, identical on every tier), then the region crop.TryGetClosestSizestill reports only native sizes (Scaled decode layer 1: every codec accepts any power of two (native, then frozen box filter) #6).Validate(width, height, bool)removed (2.0 had not shipped it); newValidate(width, height),GetNativeScale,Apply(image, options, nativeScale), publicBoxDownscaler.Benchmarks (Ryzen 7 5800X, same-run baselines) are in each codec's results folder: e.g. WebP 1080p top region 0.18× the time, PNG region peak memory 8–15 MiB → under 3 MiB, HEIF 12.6 MP grid at 1/4 peak 64 → 32 MB.
🤖 Generated with Claude Code