Conflicts: crates/engine/src/camera_preview.rs: LOOK_VERSION stays 13, all three version notes kept; docs/raw-parity.md header merged (newer date, both changes)
Review follow-up to the vignetting fit fix:
- `proxies` applies the embedded vignetting gain before it moves the sensor
into the camera JPEG's lens geometry, so a warped proxy pixel gets the gain
of the position it samples, as the render applies it.
- `alignment` reads NaN pixels (outside a lens-warped proxy) as missing. The
`max(1e-4)` turned them into black, a false edge along the border, and the
warped candidate lost on wide-angle RAFs with barrel distortion.
- Tests: the colour fit gets the embedded lens, the vignetting is applied
before the warp, and alignment ignores missing pixels.
27 + 60 X-S10 RAFs: mean ΔE00 unchanged (3.06, 2.55 -> 2.54), none worse by
more than 0.02.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The RAF look fit compared the uncorrected sensor proxy with the camera
JPEG, which is already corrected for vignetting, so the tone curve learned
the brightening and the render then applied the embedded vignetting
correction a second time. Shadows and midtones came out up to 0.6 / 0.3 EV
too light on X-S10 files.
`look_proxies` applies the embedded correction to the sensor proxy of
every per-file fit (colour, monochrome, near-black fallback), as
`fit_dng_tone` already did. LOOK_VERSION 13, RENDER_CACHE_VERSION 71.
60 held-out X-S10 RAFs: mean ΔE00 2.71 -> 2.55, 32 better, none worse by
more than 0.05.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UPDATE_APP (native builds; craft-update is a native-only dependency) carries our 128 px icon and accent (#f2a516, its colour in ArtCraft Launcher). The updater, the self-update
helper and craft-update's startup prompt all get them through crate::UPDATE_APP; the
prompt draws the icon, the accent and "by ArtCraft" (craft-update's default developer).
craft-update is pinned to storytold/craft-libs main at 1065653e7 (#434).
Clippy fixes for Rust 1.99 in untouched code (same logic):
- crates/engine/src/cmd/export.rs
- crates/ui-egui/src/dialogs/rules_editor/rules_move.rs
- crates/ui-egui/src/panels/detail.rs
X-T20, X-T2, X-Pro2, X-E3, X-H1 and X100F have no measured colour matrix, so
As Shot showed on the relative scale. Their RAFs store the camera's own
neutral multipliers for Illuminant A and D65 (tag 0xf00c). Two points
give a line in (ln R/G, ln B/G); mired is linear along it, isotherms are
perpendicular, tint is scaled by the CIE locus spacing. Only the file's pair
and CIE colorimetry are used. Bodies with matrices are unchanged. The look
stays the per-file JPEG fit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
The per-file look fitted from the embedded JPEG can join two pixel
populations the camera renders at different brightness for the same scene
luminance (by hue) with a near-vertical tone step. On a dark ISO 1100
D3400 frame of saturated blue and red the step had a log-log slope of
12-28 and the render's fine luminance noise in the blue area was 1.46x
that of Adobe's DNG of the same frame.
Fitted tone curves are now limited to a log-log slope of 4 between knots
(fitted looks: median steepest segment 2.1, 95th percentile 3.6). A
steeper segment is spread over the smallest window of knots whose
end-to-end slope is under the bound; a window that reaches an end of the
curve is weighed against leaving that end free at slope 2, keeping the
one closer to the fit's pairs. D3400 blue-area luminance noise drops to
0.99x Adobe's.
LOOK_VERSION 13, RENDER_CACHE_VERSION 71.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
The default camera look is fitted to the raw's embedded JPEG, decoded in the
colour space embedded_preview_color_space reports. That function parsed the
file as TIFF, which fails on a CR3 (ISO BMFF), so a CR3 shot in Adobe RGB was
read as sRGB and the look was fitted to a desaturated target. A CR2 shot in
Adobe RGB parses, but its EXIF says ColorSpace Uncalibrated with InteropIndex
"R98" rather than Adobe RGB's "R03", so it was read as sRGB too.
Read the Canon maker-note ColorSpace (0x00b4: 1 = sRGB, 2 = Adobe RGB): for a
CR3 from its CMT3 block, else an EXIF ColorSpace of sRGB from CMT2; for a CR2
from the EXIF maker note, before the EXIF fallback.
LOOK_VERSION (13) and RENDER_CACHE_VERSION (71) are bumped so cached
smart previews and thumbnails are rebuilt with the new look. Smart previews of
preview-only raws now follow LOOK_VERSION too, since their proxy is the
embedded JPEG read in the declared colour space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
Lightroom Classic's HDR and panorama merges of monochrome raws store a
one-plane LinearRaw image as lossy (XYB) JPEG XL tiles whose codestream
declares a grey colour encoding (16-bit float with a linear curve, or
16-bit integer with the sRGB curve). Those were refused as "lossy JPEG XL
tile with a single colour plane", so the files opened preview-only.
A lossy grey tile is still coded as three XYB channels. It is now
restored like the RGB case: jxl-oxide renders linear sRGB (never the
declared encoding, which would clip), the single plane is the luminance
(CIE Y, D65) of that, then the declared transfer curve is applied.
One plane must be declared grey and three RGB; HDR curves, other white
points and intensity targets above 255 stay unsupported.
RENDER_CACHE_VERSION 70 -> 71: files that showed their embedded preview
before render from raw data now, under the same content key.
Refs #275
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
- finish_8bit/finish_deep hand back the render itself when there is no
output sharpening and no watermark. Before, every export cloned the
image (~100 MB at 24 MP) and output_sharpen built a ~290 MB float copy
of it even for SharpenFor::None, all on one thread.
- Output sharpening's box blurs run row-parallel (par_rows); each pixel
is computed as before.
- The parallel JPEG encoder converts each band's rows to YCbCr inside the
band's task instead of first filling whole-image planes (~290 MB for
4:4:4 at 24 MP, written once and read back).
- fdct_quant rounds with plain arithmetic (round_i16) instead of
f32::round, a libm call per coefficient without SSE4.1; it equals
`x.round() as i16` for all 2^32 inputs.
Output is byte-identical (pinned by encoded_bytes_are_pinned and
sharpened_pixels_are_pinned, whose hashes come from main).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
examples/export_bench imports the files given and runs export_photo
(decode, full-size render, encode) N times, forgetting the decoded
original between runs like a batch export does, and prints the median
and minimum wall time. OPT takes export options as app.export JSON,
PHASES=1 times render and encode separately, OUT=dir writes the files
for byte comparisons between builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reuse cached folder listings through Arc and allow tests to inject Local places.
Show browsed folders below hidden ancestors as session roots. Apply the same
hidden-entry checks to folder walks, browsing, import scanning, and Local.
Return an error when either file index is zero. Keep the resolved-path fallback for lenient comparisons. Test both unknown-identity orderings and distinguish identity lookup failures from known-original refusals.
Merge main at 662df66f.