image: add on-demand size=large preview tier (~2048px JPEG q85) #100

Merged
cameron merged 1 commits from feature/image-large-preview into master 2026-05-19 21:51:09 +00:00
Owner

Adds a third PhotoSize between Thumb (200px) and Full (original). The
viewer placeholder and map callout previously upscaled a 200px thumb
into a full-screen / full-width view, which looked visibly blocky on
3× devices. The new tier is generated on-demand, disk-cached, and
served via the existing /image endpoint.

Storage layout mirrors the Thumb branch's lookup chain:

  1. hash-keyed: /_large/<hash[..2]>/.jpg (shared across
    libraries when content_hash is known)
  2. library-scoped legacy: /_large/<lib_id>/<rel_path>

Generation pipeline mirrors generate_image_thumbnail:

  • RAW: decode the embedded JPEG preview, apply EXIF orientation,
    resize to 2048-long-edge, encode JPEG q85
  • HEIC/HEIF: ffmpeg with scale + q:v 5 (≈ q85)
  • everything else: image crate decode + thumbnail() + JpegEncoder
    Never upscales — sources below the 2048 cap re-encode at native size.

Handler offloads decode/resize to web::block to keep the actix worker
free (a 24MP source takes 100–500ms). Writes via tempfile+rename so
concurrent readers can't observe a half-written JPEG. On any
generation failure, falls through to the Full branch (which itself
serves the RAW embedded preview for unrenderable RAW containers).

Video requests for size=large fall back to the existing thumb pipeline
since there's no useful 2048px video tier.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

Adds a third PhotoSize between Thumb (200px) and Full (original). The viewer placeholder and map callout previously upscaled a 200px thumb into a full-screen / full-width view, which looked visibly blocky on 3× devices. The new tier is generated on-demand, disk-cached, and served via the existing /image endpoint. Storage layout mirrors the Thumb branch's lookup chain: 1. hash-keyed: <thumbs>/_large/<hash[..2]>/<hash>.jpg (shared across libraries when content_hash is known) 2. library-scoped legacy: <thumbs>/_large/<lib_id>/<rel_path> Generation pipeline mirrors generate_image_thumbnail: - RAW: decode the embedded JPEG preview, apply EXIF orientation, resize to 2048-long-edge, encode JPEG q85 - HEIC/HEIF: ffmpeg with scale + q:v 5 (≈ q85) - everything else: image crate decode + thumbnail() + JpegEncoder Never upscales — sources below the 2048 cap re-encode at native size. Handler offloads decode/resize to web::block to keep the actix worker free (a 24MP source takes 100–500ms). Writes via tempfile+rename so concurrent readers can't observe a half-written JPEG. On any generation failure, falls through to the Full branch (which itself serves the RAW embedded preview for unrenderable RAW containers). Video requests for size=large fall back to the existing thumb pipeline since there's no useful 2048px video tier. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cameron added 1 commit 2026-05-18 17:28:57 +00:00
Adds a third PhotoSize between Thumb (200px) and Full (original). The
viewer placeholder and map callout previously upscaled a 200px thumb
into a full-screen / full-width view, which looked visibly blocky on
3× devices. The new tier is generated on-demand, disk-cached, and
served via the existing /image endpoint.

Storage layout mirrors the Thumb branch's lookup chain:
  1. hash-keyed: <thumbs>/_large/<hash[..2]>/<hash>.jpg (shared across
     libraries when content_hash is known)
  2. library-scoped legacy: <thumbs>/_large/<lib_id>/<rel_path>

Generation pipeline mirrors generate_image_thumbnail:
  - RAW: decode the embedded JPEG preview, apply EXIF orientation,
         resize to 2048-long-edge, encode JPEG q85
  - HEIC/HEIF: ffmpeg with scale + q:v 5 (≈ q85)
  - everything else: image crate decode + thumbnail() + JpegEncoder
Never upscales — sources below the 2048 cap re-encode at native size.

Handler offloads decode/resize to web::block to keep the actix worker
free (a 24MP source takes 100–500ms). Writes via tempfile+rename so
concurrent readers can't observe a half-written JPEG. On any
generation failure, falls through to the Full branch (which itself
serves the RAW embedded preview for unrenderable RAW containers).

Video requests for size=large fall back to the existing thumb pipeline
since there's no useful 2048px video tier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cameron merged commit d04b86e32c into master 2026-05-19 21:51:09 +00:00
cameron deleted branch feature/image-large-preview 2026-05-19 21:51:09 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Apps/ImageApi#100