Skip to content

Enhancement: High-resolution image masters + WebP rendition delivery#488

Open
baltinerdist wants to merge 2 commits into
amtgard:masterfrom
baltinerdist:feature/high-res-images
Open

Enhancement: High-resolution image masters + WebP rendition delivery#488
baltinerdist wants to merge 2 commits into
amtgard:masterfrom
baltinerdist:feature/high-res-images

Conversation

@baltinerdist

Copy link
Copy Markdown
Contributor

Summary

Replaces the upload-time downscale that destroyed image quality with a store-high-res-master / derive-optimized-renditions pipeline for player photos and player/park/kingdom (and shared unit/event) heraldry. Storage is cheap and one-time; delivery bandwidth is the recurring cost — so we stop conflating them.

Design spec is included in the branch: docs/superpowers/specs/2026-07-07-image-pipeline-masters-renditions-design.md.

What changed

Storage — high-res masters. Uploads are kept at up to 3000px (downscale-only, never upscale), PNG when transparent else JPEG q92, with a 6 MB post-encode reject ceiling that fires before touching disk — a too-large re-upload can't destroy the existing image. Client-side clampImageIfHuge replaces the ~340 KB squeeze, passing original bytes through untouched unless the longest edge exceeds 3000px.

Delivery — rendition set. Each upload also generates thumb (256px) + display (1024px) WebP renditions served as static files. A size-aware resolve_media_ext serves the right one per surface (nav/list/map → thumb, hero → display, lightbox → master) and falls back to the master when a rendition is missing, so un-backfilled images never break. heraldry-rendition-backfill.php derives renditions for existing images idempotently.

Transport / infra. nginx client_max_body_size 8M + webp added to the static cache regex; PHP upload_max_filesize/post_max_size raised to 8M in all three Dockerfiles.

Correctness / polish.

  • Remove paths now sweep all master + rendition variants (shared Common::unlink_image_set) — previously "removed" images kept being served from leftover renditions.
  • Over-ceiling rejections propagate to the user instead of reporting false success.
  • Lightbox enlarges the master, not the downscaled rendition.
  • Native alert() in the heraldry flows replaced with the in-page feedback pattern; stale "340 KB / Max 1 MB" upload hints corrected (banner hints intentionally left — banners are a separate pipeline).

Verification

  • Functional harness against the real GD pipeline: 15/15 (no-upscale honored, 5000×4000 clamps to 3000, an 11.6 MB image rejects while the existing file survives, PNG→JPEG re-upload sweeps stale files, resolve_media_ext resolves renditions and falls back to master).
  • Two adversarial QA passes (server core + client/wiring); all findings fixed and re-verified.
  • php -l clean on all changed PHP/templates; node --check clean on all changed JS. WebP confirmed available in the PHP 8.1 containers.

Known follow-ups

  • Scroll generator (separate branch): its builder reads the same model fields this PR re-sizes; at merge time it must request Size => 'master' for print-quality scrolls. Being handled on that branch.
  • A few immediate-redirect-on-success create paths carry the reject signal in the response but don't display it pre-redirect (would require altering the redirect); the common edit paths surface it.
  • Run heraldry-rendition-backfill.php at deploy time to generate renditions for existing images.

🤖 Generated with Claude Code

baltinerdist and others added 2 commits July 7, 2026 15:00
Design for storing high-quality image masters and deriving
size-optimized WebP renditions for player photos and player/park/
kingdom heraldry, replacing the current upload-time downscale that
destroys quality. Two-phase rollout; static serving preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01ShyA7SCTysgREoQPBoxs1Q
Replace the upload-time downscale that destroyed image quality with a
store-high-res-master / derive-optimized-renditions pipeline for player
photos and player/park/kingdom (and shared unit/event) heraldry.

Storage: uploads are kept at up to 3000px (downscale-only, never upscale),
PNG when transparent else JPEG q92, with a 6MB post-encode reject ceiling
that fires before touching disk (a too-large re-upload can't destroy the
existing image). Client-side clampImageIfHuge replaces the ~340KB squeeze,
passing original bytes through untouched unless the longest edge >3000px.

Delivery: each upload also generates thumb (256px) + display (1024px) WebP
renditions served as static files; a size-aware resolve_media_ext serves
the right one per surface (nav/list/map = thumb, hero = display, lightbox =
master) and falls back to the master when a rendition is missing, so
un-backfilled images never break. heraldry-rendition-backfill.php derives
renditions for existing images idempotently.

Infra: nginx client_max_body_size 8M + webp cache header; PHP
upload_max_filesize/post_max_size raised to 8M in all Dockerfiles.

Also: remove paths now sweep all master+rendition variants (shared
Common::unlink_image_set); over-ceiling rejections propagate to the user
instead of reporting false success; lightbox enlarges the master not the
rendition; native alert() in heraldry flows replaced with the in-page
feedback pattern; stale upload-size UI hints corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01ShyA7SCTysgREoQPBoxs1Q
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