Skip to content

feat(content): add ContentInterpreter::with_image_downscale - #17

Open
lFLouSel wants to merge 1 commit into
Xero-Team:mainfrom
lFLouSel:feat/with-image-downscale
Open

feat(content): add ContentInterpreter::with_image_downscale#17
lFLouSel wants to merge 1 commit into
Xero-Team:mainfrom
lFLouSel:feat/with-image-downscale

Conversation

@lFLouSel

@lFLouSel lFLouSel commented Aug 5, 2026

Copy link
Copy Markdown

Motivation

CAD sheets and scans embed very large rasters (e.g. a 2769x3019 background, or an A0 scan) that occupy a far smaller area on the page. The decoded image is cached at full source resolution -- tens to hundreds of MB held in the image cache even though the renderer immediately minifies it on draw, and re-minifies on every repeated Do.

Change

with_image_downscale(render_scale) opts in: after decode (and mask fold), an image whose device footprint under the current CTM is below 0.5x per axis -- exactly the point where the CPU backend already box-downscales on draw -- is pre-shrunk to that target with the same box filter (box_downscale_rgba, mirroring the renderer's own).

The rasterized output is therefore byte-identical; only the cached image (and repeated per-draw downscaling) shrinks. 0.0 (default) disables the pass, so behaviour is unchanged unless a caller opts in with the same scale it hands the renderer.

Test

image_downscale_to_footprint_shrinks_oversized_and_is_a_noop_otherwise exercises the resize helper directly: a 0.2x-minified 1000px image shrinks to its 200px footprint (and its RGBA buffer length matches), a 0.8x image (above the threshold) is left alone, and scale 0 disables the pass.

Testing notes (please read)

This test targets the maybe_downscale_to_footprint helper directly, which is deterministic and reliable. It does not assert the full decode -> cache path end-to-end (interpreting a real PDF with an oversized image and inspecting the cached image dimensions), because that needs a decodable image fixture and access to the ImageCache internals, which is awkward to assert headlessly. If you'd prefer an integration test at that level (or a different threshold/behaviour), I'm happy to adjust.

@lFLouSel
lFLouSel requested a review from YUZHEthefool as a code owner August 5, 2026 13:46
Pre-downscale decoded images to their on-page device footprint before caching.

Motivation: CAD sheets and scans embed very large rasters (e.g. a 2769x3019
background, or an A0 scan) that occupy a far smaller area on the page. The
decoded image is cached at full source resolution -- tens to hundreds of MB
held in the image cache even though the renderer immediately minifies it on
draw, and re-minifies on every repeated `Do`.

with_image_downscale(render_scale) opts in: after decode (and mask fold), an
image whose device footprint under the current CTM is below 0.5x per axis --
exactly the point where the CPU backend already box-downscales on draw -- is
pre-shrunk to that target with the same box filter. The rasterized output is
therefore byte-identical; only the cached image (and repeated per-draw
downscaling) shrinks. 0.0 (default) disables the pass, so behaviour is
unchanged unless a caller opts in with the same scale it hands the renderer.

Test: image_downscale_to_footprint_shrinks_oversized_and_is_a_noop_otherwise
exercises the resize helper directly -- a 0.2x-minified 1000px image shrinks to
its 200px footprint, a 0.8x image (above threshold) is left alone, and scale 0
disables it.
@github-actions github-actions Bot added area: content Content, display lists, fonts, images, and color size: S 50-199 changed lines, excluding Cargo.lock enhancement New feature or request labels Aug 5, 2026

@YUZHEthefool YUZHEthefool left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the optimization work. I cannot merge this version yet because it downsamples the first decoded image before caching it only by ObjectId. The same Image XObject may be painted under several CTMs: a later larger paint will reuse the first small raster, while a later smaller paint will double-filter it. Please retain the decoded source image and cache renderer-specific derivatives by target size, with a small-then-large two-CTM regression test.

Doing the resample in the interpreter also changes semantics for non-CPU consumers such as WGPU, SVG, and PPTX; the claimed byte-identical behavior is not true across backends. Please either move it to a backend-specific optimization with defined output behavior, or redesign the cache at the renderer boundary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: content Content, display lists, fonts, images, and color enhancement New feature or request size: S 50-199 changed lines, excluding Cargo.lock

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants