feat(content): add ContentInterpreter::with_image_downscale - #17
Conversation
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.
YUZHEthefool
left a comment
There was a problem hiding this comment.
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.
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_otherwiseexercises 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, andscale 0disables the pass.Testing notes (please read)
This test targets the
maybe_downscale_to_footprinthelper 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 theImageCacheinternals, 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.