RAW Editor - LUT Preview update (#272)
* Add Raw Editor webapp with RAW decode, develop controls and LUT grading
Introduce a new "Raw Editor" WebApp under src/web that decodes camera RAW
files client-side and develops them with a Camera-Raw style interface.
Decoder (js/rawdecoder.js):
- TIFF/IFD parser covering the common RAW containers (ARW, DNG, NEF, CR2,
ORF, RW2 ...), little/big endian, with SubIFD/EXIF traversal.
- CFA (Bayer) extraction for uncompressed 12/14/16-bit and Sony ARW2 lossy
compression, bilinear demosaic with black/white-level normalisation and
camera (AsShotNeutral) or gray-world white balance.
- Robust embedded full-size JPEG preview fallback (IFD pointers + brute-force
SOI/EOI scan) so any RAW still renders when its compression is unsupported.
- EXIF metadata (camera, shutter, aperture, ISO, focal length).
Develop pipeline (js/glrender.js): WebGL2 real-time renderer implementing
white balance (temperature/tint), exposure, contrast, highlights, shadows,
whites, blacks, clarity, dehaze, vibrance and saturation, plus an optional
3D LUT grade. A separable Gaussian low-pass drives clarity/dehaze local
contrast. Keeps a valid identity 3D texture bound at all times so drivers
that reject incomplete sampler3D bindings still render.
LUT (js/lut.js): Adobe/IRIDAS .cube parser for 1D and 3D LUTs, uploaded to a
WebGL 3D texture.
UI (index.html, css/style.css, js/editor.js): dark Camera-Raw inspired layout
with RGB histogram, EXIF readout, Basic/LUT tabs, editable sliders, auto white
balance / auto tone, treatment (colour / B&W), file open (ArozOS picker, local
picker and drag & drop) and JPEG export back to storage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Hand off developed image to Pixel Studio on Done
Repurpose the Raw Editor's "Done" action into a hand-off: it now writes the
current develop to a temporary JPEG (tmp:/RawEditor, auto-cleared by ArozOS)
and launches Pixel Studio as a float window with that file as its input
(matching Pixel Studio's ao_module_loadInputFiles hash format), then closes
the Raw Editor window.
Falls back to the normal Save dialog when running outside the ArozOS desktop.
The bottom-bar button is relabelled "Open in Pixel Studio".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Redesign Raw Editor UI to match the newer Lightroom / Camera Raw layout
Rework the develop panel into the 2024 Lightroom-style layout:
- Thin centred title bar with open / save / reset / fit icons.
- Borderless histogram with shadow/highlight clipping indicators and an
EXIF readout (ISO, focal, aperture, shutter).
- "Edit" header carrying Auto (auto-tone) and B&W (treatment) pills.
- Profile selector row.
- Collapsible Light / Color / Effects / LUT groups, each with a chevron to
fold and an eye toggle that bypasses that group's adjustments.
- Lightroom slider rows: label + right-aligned editable value on one line,
thin track below, with coloured gradient tracks for Temperature (blue->
yellow), Tint (green->magenta) and Vibrance / Saturation.
- White-balance eyedropper wired to auto white balance.
- Bottom bar with a live filmstrip thumbnail, Fit / 100% zoom, an underlined
dimension readout and Cancel / Open in Pixel Studio actions.
- Decorative panel-selector strip on the right edge.
Pipeline: add Texture (fine local contrast), Vignette (radial) and Grain
(post noise) controls to the WebGL develop shader to complete the Effects
group, plus per-group bypass handling in the parameter builder.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Add ArozOS-backed LUT library with a managed folder
LUTs can now be managed through the user's ArozOS storage instead of only
one-off browser uploads:
- On first use the app creates a "user:/RawEditor/LUTs" folder (via the
file_system newItem endpoint with a CSRF token). Users can drop .cube
files into it with the Files app.
- The LUT panel scans that folder (listDir) and lists every .cube in a
"Library" dropdown; picking one reads it back through the media endpoint,
parses it and applies it live. A refresh button re-scans on demand.
- "Import .cube to library" still loads a local file immediately and now
also uploads it into the library folder so it persists and reappears in
the list next time.
- Selecting a LUT re-enables the LUT group (clears any eye bypass); the hint
line reports how many LUTs are available.
Falls back gracefully to local import when running outside the ArozOS desktop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Add scroll-zoom and right-drag pan; remove unused UI
- Scroll wheel now zooms the photo, centred on the cursor, and the current
zoom level is shown in the status bar. Fit / 100% buttons reset the view.
- Right-mouse (or middle) button drag pans the image; the browser context
menu is suppressed over the canvas. The view re-fits on window resize.
The canvas is now transform-based (translate + scale) instead of the old
fit / 1:1 CSS toggle.
- Removed the Profile dropdown (no camera profiles in this pipeline) and the
decorative right-edge tool strip, so the panel only shows working controls.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Pan with left mouse button and fix LUT dropdown overflow
- Change canvas panning from right-drag to left-mouse (and middle) drag, with
a grab / grabbing cursor over the image. The context menu is no longer
suppressed.
- Fix the LUT Library dropdown (and any select) overflowing the panel when an
option label is long: give .mini-select min-width:0 so it can shrink inside
its flex row instead of stretching to the widest option.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Load EXIF from JPEG APP1 so shooting info shows reliably
Some RAW files (and any plain JPEG) did not populate the ISO / focal /
aperture / shutter readout under the histogram. The cause: when the RAW's
outer TIFF structure fails to parse, the image still loads via the brute-force
embedded-JPEG scan, but the metadata comes back empty.
Add a JPEG APP1 EXIF reader (the APP1 payload is itself a TIFF block, so it
reuses the existing TIFF parser) and use it to:
- read EXIF for plain JPEG images, and
- fill any missing EXIF fields from the embedded preview for RAW files,
which rescues shooting info when the main TIFF could not be parsed.
Also harden applyMeta so the EXIF line is written before the window-title
call and a title error can no longer suppress it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Match Photoshop LUT rendering: tetrahedral interpolation + true sRGB
The same .cube LUT looked cyan/teal-shifted compared to Photoshop. Two causes,
both fixed:
- Interpolation: the 3D LUT was sampled with GPU trilinear filtering, which
introduces a cyan/green cast on film-style LUTs. Switch the LUT textures to
NEAREST and do proper tetrahedral interpolation in the shader — the same
method Photoshop's Color Lookup and Resolve use.
- Transfer function: encode to display with the true sRGB OETF instead of a
plain 2.2 gamma. This exactly inverts the sRGB decode applied to 8-bit
sources, so the values fed into the LUT match what Photoshop feeds it
(previously the mismatch shifted shadow tones the LUT is sensitive to).
Also honour the LUT's DOMAIN_MIN / DOMAIN_MAX when sampling.
Verified in-browser: an identity LUT now passes through unchanged (exact
tetrahedral), a channel-swap LUT swaps correctly, and a constant LUT maps to
the constant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Harden RAW EXIF parsing for real camera files
Improve the chance that ISO / focal / aperture / shutter show for real RAWs
(e.g. Sony ARW), which kept the readout blank:
- Scan every embedded JPEG for APP1 EXIF, not just the largest preview. Sony
keeps EXIF in the small thumbnail while the full-size preview has none.
- Stop recursing the MakerNote (0x927C) as if its bytes were IFD offsets —
that could be pathologically slow / derail a clean parse on real files;
EXIF is reached through the standard ExifIFD (0x8769) pointer.
- Cap IFD entry counts (>4096) to bail out of parsing stray/garbage offsets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Fix RAW preview selection: never decode CFA raw as the preview image
On real Sony ARW files the develop view hung and EXIF stayed blank. Root cause:
the CFA raw plane is a lossless-JPEG-compressed stream that also starts with
0xFFD8, and the embedded-preview finder picked it as the "largest JPEG" (31 MB)
and handed it to the browser to decode as an image. The browser cannot decode
lossless JPEG, so decoding stalled and the pipeline never reached the metadata
display.
Fixes:
- Ignore CFA / raw IFDs (Photometric 32803) when hunting for a preview JPEG.
- Only accept a range that is a real JPEG (SOI at the start AND EOI at the end)
via a looksLikeJpeg() check, so a truncated raw stream can't be chosen.
- Cap the brute-force SOI/EOI scan span so raw bytes containing 0xFFD8 can't
yield a multi-megabyte junk range.
With this, ARW files pick the correct full-size preview, the image loads, and
ISO / focal / aperture / shutter populate under the histogram.
Verified against a real 25MP Sony ILCE-6700 ARW end-to-end in the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
* Add LUT "Log input" mode for log-to-display conversion LUTs
Film-conversion LUTs (F-Log/S-Log/V-Log -> Rec.709, e.g. the Fujifilm
FLog_to_ETERNA LUT) are authored for a flat log signal. Applied straight to a
normal photo they over-contrast and over-saturate, which is why the result
looked much punchier than the same LUT in Photoshop.
Add a "Log input" toggle in the LUT panel: before the 3D lookup the image is
flattened toward a log signal so the LUT reads it the way it was designed to,
giving the intended muted film look instead of a punchy one.
- A true camera-log encode can't work from a display-referred base (a JPEG
spans ~2.5 stops, F-Log expects ~14 — it fogs shadows or greys highlights),
so the flatten is a clean log2 curve that keeps blacks black and whites
white while lifting mid-tones. No wash-out.
- Auto-enables when the LUT name/title looks like a log conversion
(f-log, s-log, v-log, log-c, ...); still user-toggleable, with an Amount
slider for fine control.
Verified end-to-end on the real Sony ARW + Fujifilm ETERNA LUT: colours stay
natural and the grade is muted rather than over-saturated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151sG7aaGnwCPRt3qvGNxd3
---------
Co-authored-by: Claude <noreply@anthropic.com>