From review: album.createSmart and setRules refuse an unknown field, a
missing operator or a yes/no value that is neither yes nor no, but
library.filter applied such a rule set and showed an empty grid. It now
returns the same problems as an error and leaves the current filter in
place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yes/no fields (Has Edits, Cropped, Has GPS, Virtual Copy, In a Stack,
Best of Similar Shots) showed "true" / "false" in the editor and
summaries, and Russian had no translation for either. Matching read the
value with as_bool().unwrap_or(true), so an agent's "false" (a string)
or any other non-boolean counted as yes, while the summary said
"false".
- catalog: bool_value is the one reading. true / "yes" / "true" / 1 /
no value mean yes; false / "no" / "false" / 0 mean no (strings in any
case). Anything else matches nothing, and RuleSet::problems reports it
("`edited` is yes or no, not \"maybe\""), so album.createSmart and
setRules refuse it. bool_label gives "Yes" / "No"; describe() reads
"has edits is yes".
- ui: i18n::bool_text for the editor's dropdown and translated
summaries (Ja / Nein…, existing keys in all locales). The editor
stores the value back as a plain boolean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Choice values showed their rule ids in English ("Copyright Status is
publicDomain", "Treatment is monochrome"), and Russian had no
translation for any of them. Kind::Choice now carries (id, label) pairs
like the operators: rules still store and accept the ids, and people
see the labels.
- catalog: choice_label(field, id); RuleSet::describe uses the label
("copyright status is public domain").
- engine: album.ruleFields keeps `choices` as ids and adds
`choiceLabels` {id: label}. A test pins the ids, since the new tuple
type would otherwise serialize as pairs.
- ui: i18n::choice_text is the one path for the editor's value dropdown
and translated summaries. Aspect labels are "Landscape (wide)" /
"Portrait (tall)", because the existing "Landscape" / "Portrait" keys
mean scenery and people. Seven locales gain Video, Copyrighted, Public
Domain, Unknown, the aspect labels and Square (ru also Raw). A test
requires every choice label to be translated in every language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Camera Info gains Shutter Speed, compared in seconds and written as the
camera shows it: "≤ 1/60" finds faster exposures, "between 1/1000 and
1/125" works, and a value that isn't a time matches nothing. The editor
takes it as text (1/250, 0.5, 2").
The shutter parser moves from engine export into the catalog
(parse_shutter_seconds) so export and the rule share it. A new
rules-editor test checks that every field × operator default value
makes a valid rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FIELDS is now listed in field-menu order, with TOP_LEVEL_FIELDS (rating,
pick flag, colour label, any searchable text) followed by FIELD_GROUPS
(Source, File, Date, Keywords & People, Description, Camera Info,
Location, Size, Develop, Assisted Culling). field_group / field_label
look a field up. album.ruleFields reports each field's group so agents
see the same structure as the editor. Rule ids are unchanged, so saved
smart albums keep working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lossless JPEG scans whose SOS gives predictor selection value 8 (which T.81 leaves undefined) are read as Hasselblad codes them (ljpeg::decode_hasselblad): 32-bit little-endian words read from their most significant bit, no byte stuffing; each row in pairs of samples, both Huffman categories before both differences; category 16 is followed by 16 ignored bits and means -32768; each sample is the previous sample of its column parity in the row plus the difference, modulo 2^16, both starting at 32768 on every row. Only the layout measured is accepted (one component, 16 bits, no point transform or restarts, even width); other layouts and selection values above 8 stay Unsupported. The rule was measured on 14 CC0 files with the ljpeg-explorer tool. RENDER_CACHE_VERSION 23.
A clipped raw pixel no longer records the scene's colour (one channel held at
the clip level turns a white sky magenta or blue through the matrix while the
camera JPEG keeps it white). As training pairs such pixels pulled the fitted
colour matrix toward grey and made the chroma curve remove colour at their
display luminance: on the CC0 Z 6II forest sample every chroma sample above
display luminance 0.36 was clipped, and the curve cut colourfulness there to
13-19%.
The proxies now carry a per-pixel clip mask (any covered sensor sample at or
above 0.99, aligned with the CR3 framing). The search on all pixels still
decides whether a photo gets a look and which attempt gives it; only that
attempt is refitted without the clipped pixels, and the look on all pixels is
kept when the refit misses the gates, so no photo loses its look or switches
attempt. Photos without a look get one when the search without the clipped
pixels finds it. Camera-profile pairs leave clipped pixels out too.
Render cache version 20 -> 22.
A batch ran its photos strictly one after another: each render is
row-parallel inside, but a photo's decode, its PNG/TIFF/WebP encode and
the wait on a GPU render are mostly serial, so a 32-core machine averaged
~9.5 busy cores on the CPU path and 1-2 with the GPU.
run_batch now keeps export_parallelism() photos in flight (cores / 4,
1..=4; one on wasm32) on scoped lane threads plus the calling thread.
Each holds an estimate of its working memory from a new export_gate
(twice the memory budget, separate from the decode gate it nests over),
so full-size 24 MP exports (~1.2 GB each) run two at a time by default.
Lanes run at most `lanes` photos past the last one placed, which also
bounds the finished files waiting for their turn. Paths, conflict
handling, original protection, writes, results and progress stay in
batch order (Placer), so the files are the same as one at a time; a
panic in a lane becomes that photo's error.
render_bench's batch scenario now goes through export_batch. 8 x 24 MP
JPEGs, 32 cores + RTX 4080 Laptop: CPU 3.87 s -> 2.35 s, GPU 4.21 s ->
1.82 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a CropAngle, Lightroom's CropLeft/CropTop and CropRight/CropBottom
are the top-left and bottom-right corners of the tilted crop on the
stored image, and its angle turns the other way to LightCraft's. The
import treated the edges as an axis-aligned rectangle in the rotated
frame and kept the angle's sign, so straightened crops came out the
wrong shape and size.
Carry the two corners through the orientation and then LightCraft's
straighten rotation (angle = -CropAngle, mirrored orientations flip it
back), using the stored image's aspect from fileWidth/fileHeight. On a
real catalog every one of 223 straightened crops now matches the frame
of Lightroom's export, and the content difference falls to that of
unstraightened crops.
Refs #483
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quantile matching fits the held-out pixels slightly worse than the conditional-median fit it replaced. On 7 of 1023 archive files (compacts, bridge cameras, early Micro Four Thirds) the look passed the acceptance gates only with the old curve, and the quantile curve lost it. The search now runs with the quantile fit first (all pixels, away from edges, with and without the camera profile) and, only when no look passes, runs again with the earlier conditional-median plus isotonic fit, restored as fit_tone_conditional. The gates are unchanged, so no file loses its look relative to the previous fit, and the quantile curve is used wherever it passes. Tests: the flat-step test stays; a synthetic photo whose JPEG carries per-pixel noise misses the gates with the quantile fit and passes with the fallback, and the order of the two is checked.
crs crop edges are normalized to the stored (unrotated) image, while
LightCraft's crop rectangle lives in the oriented frame. Catalog import
applied them unchanged, so crops on portrait photos (EXIF 6 or 8) landed
along the wrong axes.
Read Lightroom's orientation code (Adobe_images.orientation: AB, BC, CD,
DA and the mirrored BA, CB, DC, AD) and map the rectangle into the
oriented frame with Orientation::map_norm_rect; a mirrored orientation
also mirrors the straighten angle. The code comes from the catalog, so
this works while the originals are still missing at import time.
Refs #483
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lightroom Classic catalogs store the crop edges and straighten angle in
their develop settings but, unlike XMP, never a HasCrop flag. The shared
crs mapper only applies a crop when crs:HasCrop is True, so every crop in
an imported catalog was dropped and reported as unmapped.
Infer HasCrop in the catalog path when any crop field is present. The XMP
path is unchanged: there HasCrop="False" with crop fields means the crop
is switched off.
Refs #483
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-photo look fitted its tone curve as the camera JPEG's median per
scene-luminance bin followed by isotonic pooling. On lens-corrected, sharpened
JPEGs of busy scenes neighbouring bins disagree by more than the gap between
them, so pooling produced flat steps: 13 of 31 segments on a CC0 Z 6II sample,
flat segments in 9 of 13 fitted Nikon Z / ILCE-7 samples. Each step maps a
range of scene tones to one output tone, which renders as posterised patches
with hard edges (issue #475, also #211, #454), and the conditional medians
flatten both ends of the curve.
Quantile matching (sorted scene quantiles against sorted JPEG quantiles) is
increasing by construction and insensitive to misregistration. On the same 13
files no fit has a flat segment, all still pass the gates, held-out RMS within
0.005 of before. Regression test: a misregistered synthetic JPEG.
The one-strip Sony ARW layout was accepted when the raw strip was 1.00-1.25x width x height bytes. The DSLR-A200, A230 and A350 sample files carry strips of 1.06x, 1.23x and 1.04x, passed that test and decoded as colour noise. Every file that decodes correctly (108 in the test archive) has a strip of exactly width x height bytes, so the layout is now accepted only then; the rest report "Sony ARW version 1 / packed compressed variant" and open from the embedded preview like the A290/A380. Part of #461 (not a decoder). RENDER_CACHE_VERSION 16 -> 19.
Renders a raw's DNG ProfileGainTableMap (Apple ProRAW's local tone mapping)
when the photo's Profile option "Camera local tone mapping" is on
(`profile.camera_local_tone`, off by default: Lightroom Classic renders
ProRAW without it). Built on #427's parser; supersedes the decode-time
applier of the first version of #273.
- raw/gaintable: GainTableMap::gain / evaluator (GainEval) per DNG 1.7.1:
bilinear between tables with edges replicated, input clamp(weights .
(R,G,B,min,max))^gamma in linear ProPhoto after the baseline exposure,
index input x MapPointsN clamped to the last point. Saturating cell
math; maps whose sizes don't match their gains aren't evaluated.
SourcePlacement: where a developed source sits in the active area.
- pipeline/local_tone: the gain per output pixel in the scene-linear
stage, before the white balance, mapped back through the frame (crop,
user orientation, warp) and the EXIF orientation. lin_needs_cpu routes
the stage to the CPU when it's on (CPU and GPU match); lin_key includes
it. SourceInfo::local_tone carries the map from the decoder.
- develop: Profile::camera_local_tone (omitted from JSON when off, so
existing settings and their hashes are unchanged); Profile row checkbox
when the source has a map; translations.
- RENDER_CACHE_VERSION 17. Docs: parity row and What's New as an opt-in.
Off renders pixel-identical to main on two iPhone 15 Pro ProRAWs (12 and
48 MP). On adds ~60-75 ms to the scene-linear stage at 1536x2048 on a
2-core VM, the same for both files (cost follows output pixels).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFqow2P1EtDxt6nRjjQoTA
1.Added Simplified Chinese font support for web and desktop builds.
2.Kept Inter as the default font, with Chinese used only as a fallback.
3.Verified Chinese glyph tests and a macOS universal build.
Signed-off-by: supper_yijie <syj794844854@163.com>
Samsung SRW raws whose raw IFD (private compression 32769 or 32770) stores the samples uncompressed are decoded from the sensor data: 12-bit samples packed two in three bytes (bit order told apart from the samples; NX5, NX10, NX11 and NX20, NX200, NX210, NX1000, NX1100) and 16-bit little-endian words (EX1, WB2000). CFA layout from the raw IFD's CFAPattern; framing, white balance, black and saturation levels from the maker note (value offsets relative to the note; the vectors are stored offset by its EncryptionKey); a one-direction Exif picture size crops to the centre of the framing; black from dark rows or columns when the file states none; saturation plateau below the stated level wins. Compressed codings (32770 with fewer bytes, 32772, 32773) stay preview only with that reason. srw is imported from folders; SRW takes the file-local camera look. RENDER_CACHE_VERSION 17.
sRAW and mRAW store subsampled YCbCr in a lossless-JPEG frame cut into
slices of whole MCUs. Reuse the subsampled lossless decoder added for
Sony M/S ARW with Canon's per-component sequential prediction, re-chop
the stream into image planes, and convert to linear white-balanced RGB
with chroma gains measured per file against the camera's own IFD2 RGB.
Render cache version 17.
Every demo-photo source load evaluated the procedural scene from scratch. A 24 MP demo
original costs ~200 CPU-seconds (AlpineLake, the photo the Detail tests open), and #323
added ~30 loupe tests that each load it at 1:1, Before and After separately. On the 4-vCPU
FreeBSD VM the ui-egui lib tests went from 1198 s to 3046 s, and the tests' 120 s/300 s
waits expired (loupe windows, fixture thumbnails starved of the shared rayon pool, the demo
detail snapshot).
Demo scene pixels are now generated once per (scene, size) and shared: concurrent loads
wait for one generation (Before/After at 1:1 no longer generate the original twice) and
the most recent results are kept up to half the memory budget. Locally, pinned to 4 CPUs,
the ui-egui lib suite now finishes in 155 s; without the change it had done 231 of 338
tests after 25 minutes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One config-dir helper (engine::config::config_dir; camera_profiles re-exports it) with both
the face-models and denoise-models defaults; hosts set up both model folders; Settings has
Faces and AI Denoise tabs; both command sets registered; both model dialogs; locale entries
from both; one [features] table per app manifest; Cargo.lock regenerated by cargo; the
DecodedSource cache cost counts both segmentation mattes and the denoised twin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TIFF raws that describe a full-resolution CFA IFD with DNG's tags (levels, crop, AsShotNeutral) but carry no DNG version tag are read by the DNG reader (RawFormat::CfaTiff, dng::decode_as). RGGB when the file states no CFAPattern. Lossless JPEG scans with a predictor selection value above 7 are rejected with that reason and stay preview-only. .3fr and .fff are imported from folders. RENDER_CACHE_VERSION 17.