Context
Motivated by supernote-obsidian-plugin#154: a crash/"reboot" opening/scrolling long (100+ page) notes on memory-constrained iOS devices. Chased through several fixes there (lazy-loading page images, evicting scrolled-away ones, debouncing the thumbnail sidebar's load-on-scroll) - see that issue and PR #151, #155, #156, #158, #159 for the full trail.
One contributing factor never actually fixed: the plugin's thumbnail sidebar shows a small (~140px wide) preview per page, but generates it by calling the exact same rasterization path as the full main-view page (ImageConverter.convertToImages → WorkerPool.processPages → this library's toImage), just displayed shrunk via CSS. There's no cheaper/lower-resolution path - every thumbnail costs the same full-page-resolution rasterization, PNG encode, and (client-side) memory footprint as actually viewing that page. Debouncing (the fix in #159) reduces how often this fires during a fast scroll, but each individual thumbnail load is still much more expensive than it needs to be.
Ask
Add a way to render at a reduced resolution/scale, so callers that only need a small preview (thumbnails, are there others?) aren't paying full-page cost for it.
Where this lives
src/conversion.ts:
toImage(note: IRenderableNote, pageNumbers?: number[]) - decodes each requested page's layers via RattaRLEDecoder at note.pageWidth/pageHeight (full resolution, always), composites them, returns Image[].
extractPageRenderData(note, pageNumber) - slices a single page's minimal renderable data for sending across a Worker boundary.
Possible approaches (not prescriptive - whoever picks this up should evaluate)
- Simplest, lowest-risk: render at full resolution as today (unavoidable -
RattaRLEDecoder decodes into a full pageWidth×pageHeight buffer, and compositing needs matching dimensions across layers), then downscale the final composited Image via image-js's own Image.prototype.resize(options: ResizeOptions) before returning/encoding. Doesn't reduce the RLE-decode CPU cost, but does reduce the final PNG size and (more importantly for the motivating issue) the memory footprint of whatever holds onto the result afterward. Smallest change - probably just a new optional parameter on toImage (e.g. { maxDimension?: number } or similar) applied per-page after compositing.
- More ambitious, bigger change: decode at a reduced resolution directly (e.g. strided/downsampled reads in
RattaRLEDecoder), avoiding the full-resolution decode/composite cost entirely, not just the output size. Touches the actual RLE decode algorithm - higher risk, needs care to not break full-resolution rendering (which is still needed for the main view/exports).
Approach 1 is probably the right starting point given it's additive and doesn't touch decode internals at all, but flagging both since the second is where the real CPU savings would come from if that ever matters.
Non-goals / out of scope here
- Nothing about when to request a thumbnail vs. full render - that's the Obsidian plugin's
ensurePageImage()/thumbnail-sidebar logic (already handled there via lazy-loading + debouncing), not this library's concern.
- No opinion on the actual thumbnail pixel size to target - that's a plugin-side UI decision (currently ~140px wide, in
styles.css's .supernote-thumb-sidebar).
Context
Motivated by supernote-obsidian-plugin#154: a crash/"reboot" opening/scrolling long (100+ page) notes on memory-constrained iOS devices. Chased through several fixes there (lazy-loading page images, evicting scrolled-away ones, debouncing the thumbnail sidebar's load-on-scroll) - see that issue and PR #151, #155, #156, #158, #159 for the full trail.
One contributing factor never actually fixed: the plugin's thumbnail sidebar shows a small (~140px wide) preview per page, but generates it by calling the exact same rasterization path as the full main-view page (
ImageConverter.convertToImages→WorkerPool.processPages→ this library'stoImage), just displayed shrunk via CSS. There's no cheaper/lower-resolution path - every thumbnail costs the same full-page-resolution rasterization, PNG encode, and (client-side) memory footprint as actually viewing that page. Debouncing (the fix in #159) reduces how often this fires during a fast scroll, but each individual thumbnail load is still much more expensive than it needs to be.Ask
Add a way to render at a reduced resolution/scale, so callers that only need a small preview (thumbnails, are there others?) aren't paying full-page cost for it.
Where this lives
src/conversion.ts:toImage(note: IRenderableNote, pageNumbers?: number[])- decodes each requested page's layers viaRattaRLEDecoderatnote.pageWidth/pageHeight(full resolution, always), composites them, returnsImage[].extractPageRenderData(note, pageNumber)- slices a single page's minimal renderable data for sending across a Worker boundary.Possible approaches (not prescriptive - whoever picks this up should evaluate)
RattaRLEDecoderdecodes into a fullpageWidth×pageHeightbuffer, and compositing needs matching dimensions across layers), then downscale the final compositedImageviaimage-js's ownImage.prototype.resize(options: ResizeOptions)before returning/encoding. Doesn't reduce the RLE-decode CPU cost, but does reduce the final PNG size and (more importantly for the motivating issue) the memory footprint of whatever holds onto the result afterward. Smallest change - probably just a new optional parameter ontoImage(e.g.{ maxDimension?: number }or similar) applied per-page after compositing.RattaRLEDecoder), avoiding the full-resolution decode/composite cost entirely, not just the output size. Touches the actual RLE decode algorithm - higher risk, needs care to not break full-resolution rendering (which is still needed for the main view/exports).Approach 1 is probably the right starting point given it's additive and doesn't touch decode internals at all, but flagging both since the second is where the real CPU savings would come from if that ever matters.
Non-goals / out of scope here
ensurePageImage()/thumbnail-sidebar logic (already handled there via lazy-loading + debouncing), not this library's concern.styles.css's.supernote-thumb-sidebar).