perf: crop element transforms, composite dlimg in place, cache fonts and QR codes#31
Open
balloob wants to merge 1 commit into
Open
perf: crop element transforms, composite dlimg in place, cache fonts and QR codes#31balloob wants to merge 1 commit into
balloob wants to merge 1 commit into
Conversation
…and QR codes Steady-state and worst-case render speedups. No visual change — the transform change is verified pixel-identical to the previous full-canvas path. - B1: transform only the region an element occupies instead of the whole canvas. apply_transform_region() crops to the pivot-centered bounding radius (mirror and rotation are isometries about the pivot, so all content stays within it), plus a resampling margin, then transforms the crop and composites it back at the right offset. 40 rotated tiny rects on 800x480 went from ~496 ms to proportional cost. - B3: composite dlimg in place with ctx.img.alpha_composite() instead of allocating and blending a full-canvas temporary three times per image (~35x overhead). Clips to the canvas first to preserve the previous off-canvas behavior. - B4: module-level (path, size) truetype cache shared across FontManager instances, so a fresh FontManager per generate_image() no longer re-reads fonts from disk (blocking I/O on the event loop) every render. - B5: LRU-cache rendered QR images keyed by (data, boxsize, border, fill, back). Generation is ~5 ms (mostly the mask-pattern search) and dashboards re-render the same QR every update, so the cache makes the steady-state cost ~0. Adds unit tests asserting apply_transform_region is pixel-identical to the full path (rotation, mirror+rotation, off-center pivot, near-edge clipping), that the font cache is shared across managers, and that QR renders are reused. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UVpLN4Rzdv7y3ud7QzutVJ
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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
Four performance fixes — one worst-case (per-element transforms) and three steady-state (dlimg, fonts, QR). No visual change: the transform change is verified pixel-identical to the previous full-canvas path (see tests).
Findings addressed
_render_transformedtransformed and alpha-composited the entire canvas per transformed element (measured: 40 small rects on 800×480 → 0.3 ms plain vs 496 ms withrotation: 45on each). Newapply_transform_regioncrops the layer to the pivot-centered bounding radius (mirror and rotation are isometries about the pivot, so every non-transparent pixel stays within the farthest-bbox-corner distance), plus a resampling margin, transforms just that crop, and composites it back at the right offset. Cost is now proportional to the element, not the canvas.dlimgcomposited the whole canvas three times per image. Replaced the full-canvasImage.new+paste+alpha_composite+paste-back (~35× overhead) with an in-placectx.img.alpha_composite(source, (x, y)), clipping to the canvas first so a partially off-canvas image still doesn't raise.generate_imagebuilds a freshFontManagerper call, so its cache never survived — every render re-ranImageFont.truetype(blocking disk I/O on the event loop). Added a module-level(path, size)truetype cache shared across instances, mirroring the existing MDI font cache. The per-instance cache (andclear_cache) are unchanged.maxsize=32) keyed by(data, boxsize, border, fill, back)holding the rendered RGBA image.Correctness of B1
apply_transform_regionis proven equivalent toapply_transform+ full composite: mirror (reflection across an axis through the pivot) and rotation (about the pivot) both preserve distance to the pivot, so a crop of radius⌈max-corner-distance⌉ + 2px(the +2 covers the BICUBIC kernel support), clipped to the canvas, contains all source and transformed pixels; transforming it about the translated pivot yields identical output. Verified numerically over a battery of cases (rotation, mirror+rotation, off-center pivot, near-edge clipping) — all pixel-identical.Tests
Unit tests assert
apply_transform_regionmatches the full path pixel-for-pixel across rotation / mirror+rotation / off-center pivot / near-edge clipping, that the font cache is shared acrossFontManagerinstances and keyed on size, and that identical QR inputs reuse a cached render. No new failures (pre-existingtests/visual/*byte-snapshot failures under Pillow 12 are unrelated).🤖 Generated with Claude Code