CR2 and PEF decode without a camera colour matrix, like ARW, NEF, RW2,
RAF and CR3, but camera_preview::file_local_look (and the catalog's
relative white balance) left them out, so they rendered with the neutral
fallback matrix: the camera preview showed first, then a flat,
desaturated render replaced it. They now use the same guarded fit and
relative WB; a fit that fails the gates keeps the fallback, so a file can
only stay as it was or move closer to its camera JPEG. Not measured on
the corpus here (raw.pixls.us is blocked from this environment).
RENDER_CACHE_VERSION 15 re-renders cached CR2/PEF thumbnails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8
Decode lossless CRX and version 0x100/0x200 C-RAW in portable Rust,
including sensor geometry, timed white balance and black/white levels.
Verify all sensor samples against six independent M50/R100/R8 references.
Any CR3 decode error falls back to the embedded JPEG, as CR3 files did
before this decoder. The full sensor buffer is allocated only after the
first plane decodes, and plane buffers grow row by row. Tile planes
decode in parallel with a windowed bit reader (24 MP: 424 -> 89 ms
lossless, 519 -> 138 ms C-RAW on an M4).
Add guarded CR3 camera-JPEG colour fitting with independent framing
validation, relative white balance and cache invalidation.
Validation: cargo xtask ci.
Add German language selection and persisted locale support, complete German
label and format catalogs, and German release notes. Connect previously
untranslated UI text, preserve user names, and size translated buttons to
fit. Format cached import source facts in the active UI language.
Validate locale coverage, placeholder formats, language persistence,
German glyphs, button bounds and cached import language switching. Review
native and headless screenshots; run all seven cargo xtask ci gates.
Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
RW2 / RWL / RAW join ARW and NEF: the starting look (matrix, hue/saturation table, tone and chroma curves) is
fitted to the file's own embedded JPEG behind the same gates, with white balance relative to the as-shot look.
Panasonic previews always show the whole active area while the default crop is the in-camera aspect ratio, so when
the preview matches the active area instead of the crop, the reference is cropped to the default crop before
fitting (corpus_rw2_with_an_in_camera_crop_gets_a_camera_look).
On 174 CC0 samples with a preview 140 fits are accepted (93 of the 114 bodies with a preview), median ΔE against
the camera JPEG 5.0 with matching lightness; most of the others are compacts and kit zooms whose camera JPEG is
distortion-corrected (tag 0x0119 isn't used yet). `lightcraft-cli calibrate` keeps collecting ARW and NEF only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtQmZ6peZXGn3wiqeowV2v
Decode every raw encoding Panasonic and Leica bodies write, from the DMC-LX1 (2005) to the DC-S1RM2 (2025):
compressed formats 4 (most bodies, 2008-2022) and 6 (S1 / S1R / S1H / S5, GH5M2), the prefix-coded strips of
format 8 (GH6 and later), packed formats 2, 5 and 7, and the 16-bit words of the 2005-2007 .RAW files. Until now
only format 5 decoded; every other Panasonic file opened as preview only.
Clean-room: no raw-decoder source was read. Tag names come from tag documentation; every encoding was recovered
by black-box analysis of 178 CC0 files from raw.pixls.us (118 bodies), and the module docs note the measurement
that settled each rule. Format-8 tags of unknown meaning must carry the values every sample has, otherwise the file
is reported as unsupported rather than guessed at.
Levels and geometry: formats 4 and 6 store samples 15 above the recorded black level (files without one use 15);
white is the active area's saturation plateau (the linearity limits are wrong on extended low-ISO shots, the GF1
and the Digilux 3); the in-camera aspect ratio is the default crop; mapped-out defect pixels are filled from
same-colour neighbours; the CFA pattern applies at sample (0, 0).
.rwl and .raw files are imported (engine, MCP folder expansion, file dialog, Linux MIME types). One file per
encoding joins the never-panic proptest; 11 CC0 files join `cargo xtask corpus`, and corpus_panasonic_encodings
checks each encoding's bit depth, black level, crop and CFA layout against the camera's own JPEG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtQmZ6peZXGn3wiqeowV2v
Engine (crates/engine/src/segment/):
- One worker thread owns the model and the encoded photo; the session
sends jobs (with the render job for the model input) and gets results
over channels, never locking what the model holds. In the app
(Segmenter::background) clicks, descriptions, prepares and detail
passes return at once and Session::segment_poll applies results (one
undo step per settled selection, pending clicks shown on the photo);
the CLI/MCP wait for them. Newer requests for the same selection
supersede queued ones.
- Every job runs under catch_unwind: a candle panic becomes an error and
the model is reloaded next time; busy counters are reset by drop
guards; a dead worker is restarted. The model unloads after 10 min idle.
- segment.model.status / .download {acknowledged: true} / .cancel: the
download runs on its own thread; status via atomics (try_lock only).
Without the model every AI request fails at once with "The SAM 3 model
is not installed…"; renders/exports never need it; `seg` params make
AI masks from stored segmentations without it.
- Hostile documents: at most 4 detail patches per component (capped on
deserialize and in the renderer), at most 64 Object clicks.
UI: Object / Describe without the model open "Download the SAM 3
Model?" (size, folder, SAM License link; Not Now / Download, progress,
cancel, Try Again with the reason, Continue once installed); progress in
the Masking panel when the dialog is closed. Reverted the PR's unrelated
default change (mask overlay off + hover-red) to main's behaviour.
tools/install-sam3.*: --fail so error pages never land in files, 416
restarts instead of looping, the weights are verified before they are
moved into place (a bad file is deleted, never resumed from).
Docs: docs/ai-masks.md (in-app download, mirrors, SAM License summary,
no weights in the repo), NOTICE, parity LR-MASK-OBJECTS, What's New,
ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of PR #198 onto the language table:
- Traditional Chinese (Taiwan) catalogs as a table entry: code zh-hant, name
繁體中文(台灣), script Hant; zh-TW / zh-HK / zh-Hant tags reach it; command
app.language.traditionalChinese
- display-only helpers: builtin_label (stock preset/profile names), history_label
(generated history steps, user preset names verbatim), source_label (library
headings, album names verbatim), date_group_label / display_time
- menu, dialog and panel strings (profiles, presets, curves, crop, versions,
history, grid, date sidebar, About links) go through the catalogs
- a language command and ui.set apply the language at once; a native-menu test
checks that every language switch rebuilds the menu titles
Adapted while porting (Claude):
- date headings and capture times are localised for every language from its
*-formats.json date patterns, not only for zh-TW; ja and zh-hans gain the date
patterns, weekday names and "Unknown Date", and zh-hans the new menu messages
(ja's come from the PR)
- plain catalogs may cover different messages (English fallback); the test
reports the gaps instead of failing. The test requiring every engine command
label to be translated is dropped
- preset groups stay grouped by name: a user preset in a stock-named group is
not split into a separate group (the group header is translated when it holds
stock presets); the PR's split was a side effect of labelling
- no Traditional Chinese face in craft-fonts yet: Hant prefers the Simplified
Chinese face (which carries the Traditional characters) over the Japanese ones
- zh-hant translations for the messages main added since the PR branched
Co-authored-by: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The localization layer was hardcoded to English and Japanese: an enum with two
variants, an `is_japanese()` gate compiled into the format macro, and one
catalog pair. Adding a language meant editing the menus, the settings row, the
checkmark logic, the format macro and the font stack — each in a different place.
A language is now one entry in `i18n::language_table!`: BCP-47 code, endonym,
ISO 15924 script and catalog. The Language menu, the settings row, the shortcut
sheet and the control channel are all built from that table, and `build.rs`
picks up `locales/<code>-formats.json` by name, so a further language is a table
entry plus two catalogs (docs/localization.md).
Simplified Chinese (`zh-hans`) ships as the second translation: `zh-hans.json`
(1019 entries, key-set and placeholder-identical to `ja.json`) and
`zh-hans-formats.json` (102 entries). Its glyphs come from craft-fonts' Noto
Sans CJK SC, whose `Hans` faces the UI now installs *before* the Japanese ones
for a Chinese reader, so shared Han characters keep the language's forms
(`fonts::cjk_fallback`); the export watermark accepts every CJK face.
`tr_format!` keeps its compile-time check. It formats the English source at the
call site — a runtime template cannot be handed to `format!` — and names those
values so a translation can place them by name (`{n}`) or position (`{_1}`).
Every translation is still checked by `format!`, so a translation with an
unknown placeholder fails the build. The Japanese catalog was rewritten from the
old `{:.0}` plural-discard trick to that by-name form; behaviour is unchanged.
Also: settings persist the BCP-47 code (never the Rust variant name),
`LIGHTCRAFT_LANGUAGE` accepts system-style tags (`zh_CN`, `ja_JP.UTF-8`),
`xtask parity` reads the second command table, and the glyph tests cover every
catalog character plus the interface symbols (`▸`/`▾` turned out to have no
glyph in any installed font and are gone from the Chinese text).
Chinese and Japanese both need `CRAFT_FONTS_DIR` as before; the web build still
embeds only the Japanese face (Noto Sans CJK SC alone would push the wasm past
Cloudflare's 25 MiB per-file cap), documented in docs/localization-zh-hans.md.
Co-Authored-By: DeepSeek V3.2 <noreply@deepseek.com>
CR3 files imported as preview-only with the 1620x1080 PRVW JPEG and no metadata
(no camera, capture time, exposure, lens or GPS): nothing read the container.
- crates/meta/src/cr3.rs: bounded ISO base media box parser for CR3 (Canon uuid
box with CMT1-4 and THMB, tracks with their sample entries, CMP1 coding header
and sample location, XMP uuid box). Every size/offset checked; depth and box
count bounded.
- Metadata: CMT1 (IFD0), CMT2 (Exif) and CMT4 (GPS) merged into one TIFF Exif
block, so CR3 reads like any TIFF-based raw; XMP from its uuid box.
- Preview: the full-size JPEG track (6960x4640 on an EOS R6 Mark III) before
PRVW/THMB; imports report the full image size.
- Tests: synthetic CR3 (boxes, tracks, merged Exif), truncation/corruption sweep,
JPEG track preferred over PRVW with fallback.
Raw (CRX) data is still not decoded; CR3 stays preview-only.
LightCraft has no colour matrix for Nikon cameras. Because of this, NEF
files showed colours that were too gray and too green.
- Colour: LightCraft now calculates a colour matrix and a tone curve from
the JPEG preview in each NEF file. Sony ARW files use the same
procedure and the same quality checks. White balance is relative to
the as-shot look, as for ARW files.
- One function, camera_preview::file_local_look, selects the formats for
the fit and for the relative white balance. Photo::relative_wb in the
catalog uses the same formats.
- Black level: Nikon stores the black level (maker note 0x003d) in
14-bit units, also in 12-bit files. The decoder read it as a 12-bit
value. Because of this, 12-bit files from the D750, D780, D850, D7500
and Z 50 were too dark. Now the decoder divides the value by 4 for
12-bit files.
- The corpus gets the D7500 12-bit and 14-bit lossless samples. A test
compares their levels. The render cache version increases to 9.
Results on 13 NEF files that LightCraft can decode: LightCraft accepted
the fit for all 13 files. The mean colour difference from the camera
JPEG (CIE76 dE) decreased from 13-46 to 2.9-7.8. One file with mixed
light improved by a small quantity only (12.7 to 12.0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sony bodies from before ~2017 (RX100–RX100M5, RX10, NEX, SLT, ILCE-6000,
A7/A7II/A7RII, ...) write no plain 0x7313/0x7310/0x74c7 tags in the raw IFD,
so the decoder used unit WB multipliers (strong green cast, e.g. the CC0
DSC-RX100M3 sample), black 512 (800 on 1-inch sensors), and kept padding
columns at the right edge.
- White balance: WB_RGBLevels from the maker note's enciphered Tag2010
block (layouts per ExifTool's Tag2010a..i tables; byte substitution
p^3 mod 249 checked black-box on 15 CC0 files: SonyISO matches Exif ISO,
ILCE-7M3 levels match its plain 0x7313).
- Black level: recovered from the encrypted SR2SubIFD by known-plaintext
analysis (constant key in every file; RX100M6/ILCE-7M3 plain 0x7310 as
plaintext; 8 keystream bytes at offset 2510, plausibility-checked).
- Crop: DNG DefaultCrop or the maker note's FullImageSize.
- "12-bit uncompressed" ARWs hold 14-bit-scale data: black/white follow it.
- Render cache version bumped; corpus manifest + corpus test, unit tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Japanese fonts (BIZ UDPGothic Regular/Bold for the UI, BIZ UDMincho for text
watermarks; #110, #114) now come from storytold/craft-fonts instead of assets/fonts/.
- crates/engine/build.rs: the craft-fonts recipe. With CRAFT_FONTS_DIR set it embeds
the manifest's fonts as lightcraft_engine::CRAFT_FONTS (wasm32: BIZ UDPGothic Regular
only, for the web size budget); unset, CRAFT_FONTS is empty. Local files only, no
network; craft-fonts is not in any Cargo.toml.
- UI: theme::font_definitions adds the Japanese faces after Inter and egui's defaults
in every family (BIZ UDPGothic first, Bold first for semibold). About box lists the
embedded families.
- Watermarks: Inter, then the craft-fonts Japanese faces (Mincho first).
- Removed BIZUDPGothic-Regular/Bold.ttf, BIZUDMincho-Regular.ttf and their OFL files,
and their ATTRIBUTION rows; added an attribution row for craft-fonts as an optional
build input; NOTICE updated.
- Tests: Japanese glyph tests use CRAFT_FONTS and skip without it; new tests for
Japanese in the UI with craft-fonts and for the UI and watermarks without it.
- CI: the FreeBSD job builds/tests with craft-fonts. Releases check out craft-fonts at
a pinned commit, build with CRAFT_FONTS_DIR + CRAFT_FONTS_REQUIRED=1, and ship the
fonts' OFL licences (plus NOTICE) in every package.
- Docs: AGENTS.md, CLAUDE.md, README, docs/web.md, docs/localization-ja.md, ROADMAP,
parity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove scripts/package-japanese-macos.py, the japanese-local feature and its
config/library/startup overrides, the settings.language migration, and the
local-build notes in the docs. Upstream library and config defaults (incl.
library::default_dir, which already honours LIGHTCRAFT_LIBRARY) are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shippori Mincho's name table credits 'FONTDASU / Google Inc. / but / Adobe' — its kanji
come from Adobe's Source Han Serif, which our asset rules exclude even under the OFL
(CLAUDE.md: no Source Han fonts). BIZ UDMincho (Morisawa Inc., design TypeBank Co., Ltd.;
OFL-1.1, from google/fonts at 63833b7) is a Mincho of its own with no Adobe glyphs, and
smaller (6.2 MB vs 8.7 MB). Attribution, licence text and NOTICE updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ROADMAP.md: new 'Where we stand' (by dimension and by kind of user; checklist 79% vs
~60-70% as a day-to-day Lightroom replacement) and 'Where we're going' (ordered priorities:
camera colour calibration, CR3 and compressed raws, verified camera coverage, render fidelity
suite, lens profiles, AI model strategy, then HDR / Classic modules / video). Current
parity numbers, M11 status, risks (raw-source decision of 2026-10-05), log.
docs/parity.md: new rows LR-PROF-CAMERACOLOR, LR-IMP-CAMERA-COVERAGE (P0) and
LR-BEHAV-RENDER-FIDELITY (P1) so quality gaps are tracked; Top gaps reordered by user impact.
README.md: honest status summary above the feature table; stale rows fixed (compressed NEF,
DNG/original export, image watermark, Move import), camera colour and AI/HDR/video rows.
AGENTS.md / CLAUDE.md: agents read the assessment before picking work, and downgrade
✅ rows they find wrong or incomplete.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decode Nikon Huffman-compressed NEF (compression 34713): lossless and lossy
(type 1 and 2) at 12 and 14 bits, with the maker-note 0x96 linearization
curve, predictor seeds and either byte order. Clean-room: 0x96 layout and
encoding-table sizes from prose write-ups (Clevy, Claff); the fixed Huffman
tables were recovered by black-box analysis of CC0 raw.pixls.us samples
(compressed vs uncompressed shots of the same scene) and verified on ~45
files from ~35 bodies. Lossy-after-split files stay preview only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `cargo xtask parity` adds a weighted-completion line (✅ 1, 🟡 ½, out of scope left out) per
tier to the generated summary: 65.7% of 500 in-scope rows (P0 98.5%, P1 79.9%, P2 11.7%).
- ROADMAP: remaining work by package in agent-hours (≈ 295–540; ≈ 75–130 h wall clock with
4–6 agents), effort-weighted completion ≈ 20–30% for full parity, milestone statuses
refreshed, Totals replaced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- embedded_preview: CR3 PRVW/THMB boxes (plus RAF, RW2, ORF, PEF from the
format commits); stops at the first valid box of each kind
- engine: raw variants that report Unsupported are probed and shown from
their embedded JPEG (orientation from the JPEG or the raw), not an error
- crates/raw/tests/corpus.rs: every corpus/raw file is recognised, has a
JPEG preview (DNG optional) and decodes or is a known-unsupported
variant; prints decode timings
- xtask corpus --download: 37 CC0 raw.pixls.us samples (matched by SHA-256)
- ROADMAP/crate docs: format coverage, NEF Huffman / RW2 format 4 /
compressed ORF+RAF gaps, neutral colour-matrix fallback
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>