perf: fix severe slowdown with 5+ screenshots#39
Open
kmadhu0 wants to merge 1 commit into
Open
Conversation
rAF + debounced save: - Batch all updateCanvas() calls to one render per animation frame (previously each slider tick fired a full synchronous render) - Debounce IndexedDB writes to 500ms; exports and slide transitions flush synchronously via updateCanvasNow() Side preview optimizations: - Render side-preview canvases at display resolution via ctx.scale() instead of full export resolution (1320×2868 → ~130px wide), ~100x fewer pixels per preview - Cache rendered side-preview bitmaps per screenshot index; non-selected screenshots don't change while editing the active one, so re-drawing them every frame is pure waste. Cache invalidates on language/device switch, style transfer, and project change. Main canvas: - Skip GPU buffer reallocation when canvas dimensions haven't changed (canvas.width = x always clears + reallocates, even for same value) Noise: - Replace getImageData/putImageData pixel loop (14MB alloc per frame) with a cached offscreen canvas composited via overlay blend mode. Pattern regenerates only when intensity or canvas size changes. Text: - Add _normalized flag to text objects; normalizeTextSettings() skips the JSON deep-clone on already-normalized objects. Flag is stripped before IndexedDB serialization. Misc: - Revoke blob URLs in getLucideImage() after Image load to stop incremental memory leak on icon color changes Co-Authored-By: Claude Sonnet 4.6 <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.
Closes #38
Problem
The app becomes almost unusable with more than 2-3 screenshots. Every slider drag triggered: a full synchronous canvas render (1320×2868 px), four additional side-preview renders at the same resolution, an immediate IndexedDB write, and a 14MB pixel-manipulation loop when noise was enabled. All of this on the main thread, per slider tick.
What changed
rAF batching + debounced save
updateCanvas()now schedules viarequestAnimationFrame, collapsing dozens of synchronous calls during a slider drag into a single render per framesaveState()is debounced to 500ms; exports and slide transitions useupdateCanvasNow()to flush synchronously when neededSide preview resolution
ctx.scale()so font sizes and all other values stay correct — ~100× fewer pixels per previewSide preview bitmap cache
Canvas reallocation guard
canvas.width = xunconditionally clears and reallocates the GPU buffer even when dimensions haven't changed; now skipped when unchangedNoise
getImageData→ pixel loop →putImageData(14MB allocation per frame) with a cached offscreen canvas composited viaoverlayblend mode. Pattern only regenerates when intensity or canvas size changes.Text normalization
normalizeTextSettings()did a JSON deep-clone on everygetText()call. A_normalizedflag now short-circuits subsequent calls on the same object. Flag is stripped before IndexedDB serialization.Blob URL leak
getLucideImage()was creating a blob URL per icon render and never revoking it. Now revoked in bothonloadandonerror.Testing
Verified with Playwright (headless Chromium):
updateCanvasNow()+toDataURL()produces valid PNG for exportNotes on PR #37
PR #37 includes the same
revokeObjectURLblob URL fix — if that merges first, this line will be identical and conflict-free. All other changes here are unique to this PR. PR #37's "debounce" is a different mechanism (server-push to MCP backend, not IndexedDB).🤖 Generated with Claude Code