Skip to content

slides: photos shrink at insert — 2560 px cap, lossy for photos, lossless for graphics - #477

Merged
nyblnet merged 2 commits into
mainfrom
slides-photo-shrink
Sep 14, 2026
Merged

nyblnet merged 2 commits into
mainfrom
slides-photo-shrink

Conversation

@nyblnet

@nyblnet nyblnet commented Sep 14, 2026

Copy link
Copy Markdown
Owner

The next file-size lever after #447/#476 (a save drops what nothing refers to) and #468 (built-in fonts): the picture that gets pasted in the first place. A phone photo is ~4000 px and several MB; a slide is 1280 px wide. Format untouched — src is still a data URI or an asset: key; model.ts unchanged (asserted by the rig).

UX, as the maintainer approved it: lossy for photos, 2560 px cap, toast only when it matters.

  • Every insert path — file picker, paste (screenshots), the picture panel's Replace file… — runs through slides/src/editor/shrink.ts.
  • Cap: long edge to 2560 px (2× the slide width, crisp on a 4K projector) — a fixed cap, not the element's box, so resizing later stays sharp. Never upscaled.
  • Photos become lossy (JPEG or WebP at 0.85, whichever the browser produces smaller); graphics stay lossless (PNG): anything with transparency, a flat-background screenshot, a few-colour logo. The classifier samples real pixels on a ~40 px grid and calls it a graphic when a third or more of consecutive samples are identical (flat background — a photo's noise never does that), or on alpha, or on very few colours; a smooth sky gradient stays a photo. (First version downscaled to 64×64 to classify; that averaged the noise away and made a gradient photo a 4 MB PNG — caught in Chrome, fixed, and the "sky" case is in the rig.)
  • Keep rule: the original bytes stay unless the result is ≥20% smaller — an already-tight JPEG/WebP is never re-encoded for nothing. SVG and GIF are never touched.
  • Toast only when the saving is ≥200 KB: Photo stored at 2560 px — 11.2 MB → 139 KB (or Image stored… for a graphic); otherwise the usual "Image pasted".
  • Picture panel: new Picture section with Embedded · 18 KB · 800×600, Replace file… (shrunk) and Replace file (original size)… (every pixel, the honest way out). About: Shrink photos on insert checkbox, localStorage 'bento-shrink-photos', this browser only, never in the document.

Measured in Chrome on the built shell, through the real paste handler (ClipboardEvent with a DataTransfer file):

input stored before → after dims toast
4032×3024 PNG, gradient + noise (photo) image/webp 11.2 MB → 139 KB 2560×1920 "Photo stored at 2560 px — 11.2 MB → 139 KB"
3000×2000 PNG, text on flat colour (screenshot) image/png, kept 735 KB → 735 KB 3000×2000 "Image pasted" (downscaled PNG was not 20% smaller → original kept; 93.7% of pixels exactly the background colour — crisp)
800×600 PNG photo (515 KB) image/webp 515 KB → 26 KB 800×600 none (saving < 200 KB)
800×600 WebP at 0.75 (already tight) kept, byte-identical 18 KB → 18 KB 800×600 "Image pasted"
4000×4000 SVG kept, byte-identical "Image pasted"
same 11 MB photo with the preference off image/png, as it came 11.2 MB 4032×3024 "Image pasted"

Picture panel on a stored image reads Embedded · 18 KB · 800×600 with both Replace buttons; the About checkbox writes off/on. Timing in a visible tab: the 11 MB photo took 1.5 s end to end.

Rig scripts/test-slides-shrink.ts — 33 checks: photo / sky / screenshot / logo / alpha classification and the exact flat-run threshold, the grid sampler, the keep rule at 19% vs 21%, the cap arithmetic, untouchable formats, the toast threshold and wording, every insert path routed through the shrink, the About switch, and git diff origin/main -- slides/src/model.ts empty. Negative control: breaking the keep rule goes red. Registered in CI after the fields rig; test-ci-registered 328/328. 9 strings × 8 catalogs + 22 packs, coverage 100%; typecheck, modelkeys green. CHANGELOG entry under [Unreleased].

Not done: nothing retroactive — an existing deck's pictures are untouched until something new is inserted. A deliberate Compress pictures in this deck… action can follow if wanted.

…less for graphics

A phone photo is 4000 px wide and several MB; a slide is 1280 wide. Every
image insert (file picker, paste, panel replace) now goes through
editor/shrink.ts: the long edge is capped at 2560 (crisp on 4K), photos
are re-encoded JPEG/WebP 0.85, and graphics — anything transparent, a
flat-background screenshot, a few-colour logo — are only downscaled,
never lossy. The original bytes are kept unless the result is at least
20% smaller; SVG and GIF are never touched. A toast says what happened
when the saving is 200 KB or more.

The picture panel shows what is stored (Embedded · size · pixels) and
offers Replace file (original size)… for every pixel; About carries
Shrink photos on insert (localStorage, this browser). Format untouched:
a picture is still a data URI or an asset: key; model.ts unchanged.

Rig scripts/test-slides-shrink.ts (33 checks), registered in CI.
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Build size

main (2dafe71) → slides-photo-shrink (99a7afa)

app base PR change
bento/slides 717.5 KiB 721.5 KiB +4.1 KiB (+0.57%)
bento/spaces 273.0 KiB 273.0 KiB 0.0 KiB (0.00%)
bento/dash 424.5 KiB 424.5 KiB 0.0 KiB (0.00%)

Updated: 2026-09-14T23:46:57Z

…ides' strings and rigs kept; aspect-lock rig reads the whole Picture section
@nyblnet
nyblnet merged commit 3a7eb8f into main Sep 14, 2026
2 checks passed
nyblnet added a commit that referenced this pull request Sep 14, 2026
…o shrink): both sides kept; shrink rig asks the format directly instead of diffing model.ts against main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant