A vibe coded PWA for creating HDR Gain Map JPEG images.
Access the live version here to process your photos: https://sturmen.github.io/ultrahdr-pwa-svelte/
| HDR | SDR |
|---|---|
Don't think of the "old" HDR, which is totally different.
More information: https://gregbenzphotography.com/hdr/
This is an attempt at a cross-platform way to enhance SDR images into the widely-compatible JPEGR (aka UltraHDR JPEG, aka JPEG with a gain map) format. The goal is that users may have an SDR image that they enjoy, and they use this progressive web app to add an enhancement layer that improves the image but does not alter the original nor introduce compatibility issues.
Gain-map generation is handled by GMNet.
flowchart LR
O_ROT{"rotation != 0?"}
O_EXIF{"EXIF auto-rotation present?"}
O_DISCARD{"discardGainMap=false?"}
O_PRESERVE_ZERO{"rotation=0 and no auto-rotation/resize?"}
O_PRESERVE_LOSSLESS{"Lossless preserved-component rotation eligible?"}
O_HEIC_ZERO{"rotation=0?"}
O_RESIZE{"Resize/constrain needed?"}
subgraph C1["Input Classification Stage"]
direction TB
A["Input file"] --> B{"Effective input class"}
B --> J1["Standard JPEG without gain map"]
B --> J2["UltraHDR JPEG with embedded gain map"]
B --> H1["HEIC/HEIF with native gain map"]
B --> H2["HEIC/HEIF raw HDR intent without native gain map"]
B --> H3["HEIC/HEIF without gain map"]
B --> H4["HIF HDR-intent input"]
B --> T1["TIFF"]
B --> O1["Other raster inputs (PNG/WebP/etc.)"]
end
subgraph C2["Image Decode Stage"]
direction TB
D1["Decode SDR pixels"]
D2["Convert TIFF to PNG-like SDR decode"]
D3["Convert HEIC/HEIF SDR image to PNG-like SDR decode"]
D4["preserved: use decoded SDR + native gain map components"]
D5["hdr-intent: decode raw HDR intent to linear RGBAF16"]
D6["hdr-intent: decode HIF HDR intent"]
D7["Decode preserved components, align/resize, rotate, re-encode"]
D8["Extract base JPEG and force generated path"]
end
subgraph C3["Rotation Normalization Stage"]
direction TB
R3["Attempt lossless JPEG rotation"]
R4["Lossless EXIF normalization"]
R5["Lossless JPEG SDR bypass"]
R6["Decode, rotate, re-encode SDR"]
R9["preserve-with-rotation"]
R11["Rotate preserved base + gain map JPEGs losslessly, then rebuild"]
R13["Rotate preserved components before re-encoding"]
end
subgraph C4["Gain Map Decision Stage"]
direction TB
G1["GMNet generation path"]
G2["generated path"]
G4["Constrain SDR image before encoding"]
G5["Keep decoded dimensions"]
G6["HDR-intent HEIF API-0 encode path"]
end
subgraph C5["Final Encode Stage"]
direction TB
F1["Encode SDR base + generated gain map"]
F2["preserved: rebuild UltraHDR from compressed base + gain map"]
F3["Rebuild UltraHDR with preserved source metadata"]
Z["Final UltraHDR JPEG output"]
end
J1 --> O_ROT
O_ROT -->|"no"| O_EXIF
O_ROT -->|"yes"| R3
R3 -->|"eligible"| R5
R3 -->|"fallback"| R6
O_EXIF -->|"yes"| R4
O_EXIF -->|"no"| R5
R4 -->|"success"| R5
R4 -->|"fallback"| R6
R5 --> G1
R6 --> G1
J2 --> O_DISCARD
O_DISCARD -->|"no"| D8
O_DISCARD -->|"yes"| O_PRESERVE_ZERO
O_PRESERVE_ZERO -->|"yes"| F2
O_PRESERVE_ZERO -->|"no"| R9
R9 --> O_PRESERVE_LOSSLESS
O_PRESERVE_LOSSLESS -->|"yes"| R11
O_PRESERVE_LOSSLESS -->|"no"| D7
H1 --> O_DISCARD
O_DISCARD -->|"yes"| D4
D4 --> O_HEIC_ZERO
O_HEIC_ZERO -->|"yes"| F3
O_HEIC_ZERO -->|"no"| R13
H2 --> D5
D5 --> G6
H3 --> D3
D3 --> G1
H4 --> D6
D6 --> G6
T1 --> D2
D2 --> G1
O1 --> D1
D1 --> G1
D8 --> G2
G1 --> G2
G2 --> O_RESIZE
O_RESIZE -->|"yes"| G4
O_RESIZE -->|"no"| G5
G4 --> F1
G5 --> F1
R5 --> F1
F1 --> Z
F2 --> Z
R11 --> Z
D7 --> Z
F3 --> Z
R13 --> Z
G6 --> Z
quality, stripExif, and maxContentBoost primarily affect encoding quality and metadata, not the top-level route through generated, preserved, or hdr-intent. Preserved gain maps keep their source metadata unless discardGainMap=true forces regeneration. The preserve-with-rotation branch covers preserved inputs that still need auto-rotation, explicit rotation, or preserved-component resize/alignment work before the final rebuild. Every branch ends in an UltraHDR JPEG output.
npm run test:e2enpm run test:e2e:mobile