Mask ids come unchecked from develop.merge, sidecars (lc:settings) and
pastes. With a mask id of u32::MAX, next_mask_id() and Preset::apply
added 1 to it: debug builds panicked, release builds wrapped to 0, so
the second new mask got the same id as the first.
next_mask_id() now falls back to the lowest id no mask uses when the
highest is at the limit, and Preset::apply takes each preset mask's id
from it instead of counting on its own, so mask.add, mask.brushStroke,
mask.duplicate and presets with masks all get unused ids.
Render cost is pixels x samples (CPU and GPU), and only mask.add / mask.update capped the list, so a
sidecar's lc:settings, a preset, develop.merge, render --settings or a catalog / history load could
carry thousands and stall a render or lose the GPU device. MAX_COLOR_RANGE_SAMPLES moves to
lightcraft-develop and ColorRange::samples deserializes through a capped visitor (the first 64 in
order, the rest skipped as IgnoredAny); the CPU and GPU renders also take at most 64 for settings
built in code.
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
The test's stored image and LightCraft's size of the photo were both 20 × 10, so
fitting radials to the photo's size instead of the stored image went unnoticed.
The photo is now already in the library at its shown 10 × 20, as a vertical shot
is.
The catalog's XMP is merged before the develop row, and its masks were taken as
they are, on the stored image: when the row has no masks of its own to replace
them, they stayed a quarter turn off on a vertical shot. They now go through the
same frame as the row's. A radial is fitted to the stored image before it is
turned, so its radii swap with the stored aspect and not the sidecar default.
Updating a photo already in the library keeps its Rotate Left / Right and Flip in
LightCraft, which LightCraft draws masks after, but the masks went through the
catalog's orientation only: an upright photo turned right in LightCraft got an
unturned gradient. They now go through the catalog's orientation and then the
photo's own.
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>
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.