mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 11:37:13 -06:00
Issue #2052: merge main and limit export fallback to Thai
Keep the existing Inter and CJK export order and the UI font chain. Defer Arabic export faces until joining and bidirectional ordering exist. Restore main's headline documents and record remaining text-layout limits.
This commit is contained in:
+14
-3
@@ -2,7 +2,8 @@
|
||||
|
||||
LightCraft selects objects with **SAM 3** (Segment Anything with Concepts, Meta 2025), run in
|
||||
pure Rust by `crates/segment` on [candle](https://github.com/huggingface/candle): on the GPU
|
||||
through Metal on macOS, on the CPU elsewhere (for now).
|
||||
through Metal on macOS, on the CPU elsewhere (for now). If the Mac's GPU can't run it, it runs on
|
||||
the CPU (see the worker below).
|
||||
|
||||
**The model is optional.** It is not part of LightCraft, and nothing requires it: without it,
|
||||
Object and Describe offer to download it, every other feature (and every existing mask, see
|
||||
@@ -16,7 +17,10 @@ In the Masking panel (M):
|
||||
to leave it out. Clicks show as green (include) and red (leave out) dots.
|
||||
- **Describe**: type what to select — `sky`, `trees`, `the red car` — and press Return. Every
|
||||
instance the model finds (score above 0.5) joins the mask; if nothing matches, nothing is
|
||||
created and you are told so.
|
||||
created and you are told so. Around what it found, the mask follows the model's semantic mask
|
||||
for the phrase, which is sure inside and runs on where two instances touch (the instances'
|
||||
own masks alone left a sky or a net about 70–90 % selected and a thin line between two people
|
||||
side by side).
|
||||
- Several things at once: `car, road` (comma-separated) selects each and merges them.
|
||||
- **+ / −** under the selected mask (and the Add / Subtract / Intersect menus) combine
|
||||
selections: Describe `car`, click the mask, + ▸ Describe… `road`; − ▸ Describe… `people` takes
|
||||
@@ -214,7 +218,7 @@ wait and return the result (when built with the `sam` feature; the default CLI h
|
||||
`NOTICE` — the ported files say so and that they were modified): the 32-layer ViT backbone
|
||||
with 2-D RoPE and windowed attention, the feature pyramids, the SAM 2-style prompt encoder and
|
||||
two-way mask decoder for clicks, and the CLIP text encoder, DETR encoder/decoder (box
|
||||
relative-position bias, presence token) and pixel decoder for text. The CLIP tokenizer is a
|
||||
relative-position bias, presence token), pixel decoder and semantic head for text. The CLIP tokenizer is a
|
||||
small BPE in `tokenizer.rs`. The downloader (original code) is the `lightcraft-fetch` crate, shared by any model that is downloaded; `fetch/` here says what SAM 3 needs.
|
||||
- candle is pinned at 0.9.2: later releases make `candle-core` depend on `tokenizers` with the
|
||||
Oniguruma C library, and the product is pure Rust. Weights are read with positional reads (no
|
||||
@@ -225,6 +229,13 @@ wait and return the result (when built with the `sam` feature; the default CLI h
|
||||
encoded photo; the session sends it jobs and receives results over channels, never holding a
|
||||
lock the model holds. Each job runs under `catch_unwind`: a panic inside candle becomes an
|
||||
error and the model is reloaded on the next request; busy counters are reset by drop guards.
|
||||
- GPU fallback (issue #1974): a job that fails on the GPU (a panic or a candle error, e.g. an
|
||||
AMD GPU in an Intel Mac whose Metal compiler crashes on a matmul kernel) frees the model and runs
|
||||
once more on the CPU. If that works, the model stays on the CPU until LightCraft restarts and
|
||||
the user is told once ("The GPU couldn't run the AI mask model…"); if the CPU fails too, the
|
||||
error is reported and the next job tries the GPU again. Missing or damaged model files and
|
||||
remote-worker errors are not retried. `LIGHTCRAFT_GPU=0` or `LIGHTCRAFT_GPU_BACKEND=off` keep
|
||||
the model on the CPU from the start.
|
||||
- Hostile documents: a selection keeps at most 4 detail patches (when read and when rendered),
|
||||
each at most 1024² logits; more than 64 clicks are never sent to the model.
|
||||
- Accuracy: `crates/segment/tests/reference.rs` compares every stage against the Python
|
||||
|
||||
@@ -60,6 +60,7 @@ guard.detail(&file_name);
|
||||
| Lightroom catalog import | `crates/ui-egui/src/lightroom_import.rs` | stop being cancellable for a phase that must finish |
|
||||
| Face scan, AI Denoise | `crates/engine/src/cmd/face_recognize.rs`, `crates/engine/src/denoise.rs` | a continuous job with no ✕, kept in session state |
|
||||
| Find Missing Photos | `crates/ui-egui/src/tasks.rs` | a generic background task with a row (`kind: Some(..)`) |
|
||||
| Reload from Disk | `crates/ui-egui/src/reload.rs`, `crates/engine/src/cmd/reload.rs` | a command split into plan / probe / apply so the app reads the files on a worker thread (`tasks::spawn_tracked`: progress and `Cancel::Flag`); the result is committed through `Session::execute_fn` (undo step, save, XMP auto-write), and dropped for a photo a later-started reload has already updated; `wait: true` runs it in place |
|
||||
| Reading focus points | `crates/engine/src/focus_scan.rs` | a worker paced by the UI's per-frame pump (`focus.pump {pace}`); `Cancel::Flag`; commits in batches without undo steps |
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
# Sony ARW, Nikon NEF, Panasonic RW2, Fujifilm RAF, Canon CR3/CR2, Pentax PEF, Samsung SRW, Olympus ORF and Epson ERF starting look
|
||||
# Sony ARW, Nikon NEF, Panasonic RW2, Fujifilm RAF, Canon CR3/CR2, Pentax PEF, Samsung SRW, Olympus ORF, Epson ERF and Minolta MRW starting look
|
||||
|
||||
ARW, NEF, RW2, RAF, CR3, CR2, PEF, SRW, ORF and ERF decoding supply sensor data and camera white-balance multipliers, but currently no measured camera colour matrix. Treating camera RGB as linear sRGB produces a dark, muted default rendering. The loader now estimates a file-local starting look from that file's embedded camera JPEG, only when a usable camera matrix is absent. All output pixels still come from the RAW sensor data; there is no JPEG replacement or uniform saturation boost.
|
||||
ARW, NEF, RW2, RAF, CR3, CR2, PEF, SRW, ORF, ERF and MRW decoding supply sensor data and camera white-balance multipliers, but currently no measured camera colour matrix. Treating camera RGB as linear sRGB produces a dark, muted default rendering. The loader now estimates a file-local starting look from that file's embedded camera JPEG, only when a usable camera matrix is absent. All output pixels still come from the RAW sensor data; there is no JPEG replacement or uniform saturation boost.
|
||||
|
||||
## Colour and tone are separate
|
||||
|
||||
A fixed sensor proxy of at most 96×96 is developed with the file's WB, black/white levels and active crop. The embedded JPEG is decoded with DCT scaling and a 64-million-pixel input limit, converted through the codec's colour management, and reduced to the same size. Aspect ratios must agree within 2%. For colour fitting, near-black, clipped and nonfinite samples are excluded. Learning a colour matrix requires at least 256 paired pixels, 5% coloured samples (linear channel spread >0.05) and a nonsingular input covariance with bounded condition. An existing camera profile supplies the colour model: fitting only tone still requires 256 pairs and 5% non-monochrome samples, but permits channel spread >0.005 so dark or dull scenes can use the known colours.
|
||||
A fixed sensor proxy of at most 96×96 is developed with the file's WB, black/white levels and active crop. The embedded JPEG is decoded with DCT scaling and a 128-million-pixel input limit (61 MP bodies' JPEGs, e.g. the ILCE-7RM6 full frame at 66.5 MP, exceed 64 MP; the codec checks the cap before decoding at full size, about 200 MB transient), converted through the codec's colour management, and reduced to the same size. Aspect ratios must agree within 2%. Near-black, clipped and nonfinite samples are excluded. Learning a colour matrix requires at least 256 paired pixels, 5% coloured samples (linear channel spread >0.05) and a nonsingular input covariance with bounded condition. An existing camera profile supplies the colour model: fitting only tone still requires 256 pairs and 5% non-monochrome samples, but permits channel spread >0.005 so dark or dull scenes can use the known colours.
|
||||
A fixed sensor proxy of at most 96×96 is developed with the file's WB, black/white levels and active crop. The embedded JPEG is decoded with DCT scaling and a 128-million-pixel input limit (61 MP bodies' JPEGs, e.g. the ILCE-7RM6 full frame at 66.5 MP, exceed 64 MP; the codec checks the cap before decoding at full size, about 200 MB transient), converted through the codec's colour management, and reduced to the same size. Aspect ratios must agree within 2%. For colour fitting, near-black, clipped and nonfinite samples are excluded. Learning a colour matrix requires at least 256 paired pixels, 5% coloured samples (linear channel spread >0.05) and a nonsingular input covariance with bounded condition. An existing camera profile supplies the colour model: fitting only tone still requires 256 pairs and 5% non-monochrome samples, but permits channel spread >0.005 so dark or dull scenes can use the known colours. Any tone curve (full, partial or black-and-white look) also needs some contrast: the scene luminance of the brightest 1/32 of the pairs must be at least 1.2× that of the darkest 1/32 (`MIN_TONE_RANGE`); below that, the range check refuses the curve, which leaves only nearly uniform frames. A curve under 1.5× (the limit until issue #631) must also render the frame's bright areas, which the fit and the held-out gates leave out (camera JPEG luminance 0.85 and up), no further from the camera JPEG than the neutral fallback does (in luminance for a black-and-white JPEG): fitted to a narrow range, it extrapolates them. A hazy ILCE-7RM4 burst at 1.46 to 1.49 opened with the neutral fallback, 18 L\* darker than its camera JPEG, while the frame at 1.51 got a look at a held-out RMS of 0.013; with 1.2 they all do. Across 135 public Sony files the lowest range is 4.0, so none of them changes.
|
||||
|
||||
Two thirds of the usable pixels train a ridge-regularised 3×3 **chromaticity** correction (luminance-normalised RGB). This avoids trying to encode the camera's nonlinear tone curve in a matrix, which left skin grey in the earlier matrix-only estimate. Coefficients are bounded by 8. A matrix can't follow the camera's hue-dependent rendering, though: on a photo of a lime shirt among grey rocks the best matrix rendered the shirt olive (16° hue error), because the few saturated pixels have little weight against the many dull ones, and a matrix fitted to the shirt alone breaks everything else. So a **hue/saturation table** (5 values × 72 hues × 5 saturations, the DNG `ProfileHueSatMap` layout with an sRGB-encoded value axis, applied in linear ProPhoto RGB after the baseline exposure) is fitted to the residual hue shifts and saturation ratios after the matrix: Gaussian kernels around each node (2.5° hue, 0.15 saturation, 0.12 encoded value), weighted by saturation, shrunk toward identity where the photo has few samples, saturation 0 left as identity so neutrals stay neutral. The value axis matters: on a sunny waterfall scene dark yellows had to turn ~12° toward orange while brighter yellow-greens turned ~9° toward green, and one shift per hue made both worse. The table restores each pixel's luminance (the tone curve owns luminance; without that, saturation boosts darkened colours and the table lost on held-out pixels).
|
||||
|
||||
@@ -35,7 +34,9 @@ The wider set needs at least 96 dark samples (JPEG luminance below 0.015), inclu
|
||||
|
||||
If the original curve cannot be fitted, the wider curve can still be accepted after the sample-count and rank checks. In either case, the completed look must pass its existing held-out gates: a full look needs finite errors, linear-display squared error below 70% of the neutral fallback's and per-channel RMS no greater than 0.10. Partial and monochrome tone-only looks need luminance rank correlation of at least 0.5 and finite squared errors below 70% of the fallback's in both linear and gamma-encoded values. The monochrome comparison uses luminance; the full and partial comparisons use colour channels. Colour-pair selection, colour fitting and the original colour training split are unchanged.
|
||||
|
||||
The extra dark knots move the straight-line extension down toward black without a new serialized curve format or a different CPU/GPU interpolation rule. `LOOK_VERSION` 5 refreshes older fitted smart previews when their originals are online; `RENDER_CACHE_VERSION` 62 redraws cached renders.
|
||||
A near-black frame can have fewer than 256 colour pairs, so none of those attempts reaches tone fitting (#1587). After all existing looks fail, a tone-only fallback uses the wider signal floors above. At least 90% of the JPEG proxy must be below luminance 0.015. It excludes clipped sensor pixels from training and refuses invalid samples and JPEG highlights at or above 0.85. Every clipped pixel must stay at least as close to its JPEG luminance as the default curve leaves it. At least 256 usable pairs must show luminance rank correlation of 0.5 or more. Two thirds train a quantile curve; the held-out third must reduce squared luminance error by 30% in both linear and gamma-encoded values. This fallback keeps the source's spectral or neutral colour and learns no matrix, hue/saturation table or chroma curve. Earlier accepted looks keep their result.
|
||||
|
||||
The extra dark knots move the straight-line extension down toward black without a new serialized curve format or a different CPU/GPU interpolation rule. `LOOK_VERSION` 12 refreshes older fitted smart previews when their originals are online; `RENDER_CACHE_VERSION` 69 redraws cached renders.
|
||||
|
||||
No process-version bump is needed under the [rule for changes to source data](process-versions.md#the-rule). The decoded source carries the fitted camera tone in `SourceInfo`; both V1 and V2 use it in `finish::base_tone`. Existing raws edited under V1 or V2 can therefore change when decoded again. Offline smart previews keep their stored pixels and tone until rebuilt from the original. The scan on library opening runs once per look version for each smart previews folder. If an original was offline at that scan, run Build Smart Previews after reconnecting it.
|
||||
|
||||
@@ -51,6 +52,8 @@ Vendor RGB WB multipliers alone do **not** identify an absolute illuminant witho
|
||||
|
||||
**Kelvin through the camera's white-balance presets.** Sony ARWs store, in the encrypted `SR2SubIFD`, the multipliers of every white-balance preset, five of them at a stated temperature (2500, 3200, 4500, 6000 and 8500 K). For a camera without measured matrices whose files carry them (`lightcraft_raw::kelvin_presets`: Sony ILCA-99M2, ILCA-77M2, SLT-A33, DSC-RX10, ILCE-3500, ILCE-7S) they take the matrices' place in everything above: the as-shot multipliers are placed on the curve through the five presets (mired linear along it, in log red/green and blue/green), an offset from the curve is the tint, and the decision, the Bradford adaptation, the pixel-identical upgrade and `Op::SetAsShotWb` are the same. The scale is Sony's own: files shot on the Kelvin setting read back what the dial said (ILCA-77M2 3400 K → 3396 K and 5200 K → 5193 K, SLT-A33 4700 K → 4708 K, the public ILCE-3500 sample's 5600 K → 5594 K; with the colour filter one step towards magenta, the SLT-A33's 3400 K reads 3473 K / +11), and the Shade preset reads 7493 K on the public ILCE-7S sample. Off the curve, two constants come from the 14 Sony bodies that have both measured matrices and presets: the tint is the offset scaled by the locus's own spacing (the matrices give 0.90–1.04 of it), and the isotherms lean against the curve's normal (`ISOTHERM_TILT` 0.74, measured 0.59–0.84). On 150 ILCE-7SM2 photos read both ways, the presets' temperature tracks the matrices' to ±4 mired around Sony's 8 % higher scale, and the tint to −9 ± 4. A listed camera whose file has no presets keeps the Kelvin scale with As Shot at D65, as a covered camera without multipliers does. Bodies with measured matrices keep reading through them.
|
||||
|
||||
Canon CR2s list their presets by name in the maker note's `ColorData` (`0x4001`), each with Canon's colour temperature for it: for versions -4 and 13–15 the curve is built the same way from the Daylight, Shade, Cloudy, Tungsten and Flash rows (sorted by temperature; Fluorescent, a green light off the locus, and the Kelvin row, which holds the dial's last setting, are left out). Only the PowerShot G1 X Mark III is enabled, checked on a reporter's nine photos, one per white-balance setting: the as-shot levels read 4160 / 4580 / 5995 / 5215 / 2816 / 3703 / 7162 / 5368 / 4813 K (Auto, Daylight, Shade, Cloudy, Tungsten, Fluorescent at tint +31, Flash, Underwater, Kelvin 5500) where the camera's own `ColorTempAsShot` says 4306 / 4703 / 6005 / 5272 / 2770 / 3533 / 7100 / 5347 / 4925 K. Those photos were shot with the white-balance shift 8 steps (of 9) towards blue (`WBShiftAB` −8), which is why a Daylight shot reads about 4600 K and not the Daylight preset's 5651 K: the levels the camera applied are bluer than the preset's, and the camera's own number agrees.
|
||||
|
||||
What the matrices say is not verified against Lightroom's Kelvin. On the ILCE-7M3 a Color Temp. series reads 7–10 % below Sony's dial through them, smoothly and monotonically, with tint near the locus (issue #730); whether the matrices or the dial are off is open (a grey card under an incandescent bulb would tell, since the Illuminant A matrix is exact there by construction). Public samples: ILCE-7M3 AWB 4268 K / +7, ILCE-7M4 AWB 4344 K / +26, GFX100S on its Daylight preset 4659 K / +9 (Fujifilm's nominal daylight is about 5300 K: the same direction and size of gap).
|
||||
|
||||
Sony's proprietary `ColorMatrix` is not assumed to be a DNG XYZ-to-camera matrix. No Adobe profile/matrix or GPL decoder/calibration source is used. The product remains pure Rust.
|
||||
@@ -109,6 +112,21 @@ On a local set of 54 ARW6 and lossless ARW photos (the maintainer's own shots pl
|
||||
|
||||
A pooled profile was tried and is not bundled. Pooling the 46 photos (158,549 colour pairs) and validating on withheld photos (every third photo, N = 15, trained on the other 31) took mean ΔE from 5.71 with per-file fitting to 6.40 with the profile: 11 of 15 photos regressed, 3 improved and 1 was unchanged, the largest by +4.68, and 2 of the 8 rejected files were newly accepted. Per-file fitting therefore ships for this body. The 42 private photos are one photographer's scenes, 38 of them in the Standard creative style (the 12 RawDB files were not inventoried); revisit when a varied multi-photographer set exists.
|
||||
|
||||
## DNG previews with embedded lens corrections
|
||||
|
||||
A DNG with its own colour model and no non-identity profile tone curve uses the existing
|
||||
`camera_preview::fit_dng_tone` path, including its eligibility and acceptance checks. There is
|
||||
one preview fit. Files with a look table, a gain table map or an unusable preview keep their
|
||||
existing behaviour.
|
||||
|
||||
When the file carries lens opcodes, that fit develops its sensor proxy through those opcodes
|
||||
before reducing it: its camera preview already contains their distortion and vignetting
|
||||
corrections. This uses the raw decoder's existing opcode implementation, including chained
|
||||
warps. The separate proxy vignetting multiplier is skipped in this case. The editable source
|
||||
still has lens corrections removed, so the pipeline applies them once through Enable Profile
|
||||
Corrections. Developing this proxy requires a full-resolution demosaic; files without such
|
||||
opcodes keep their binned proxy.
|
||||
|
||||
## Camera profiles (pooled per model)
|
||||
|
||||
One photo can show too little of a colour for its own fit to learn it: in a second shot of the lime shirt only a few dozen proxy pixels show it, next to a hillside of foliage at the same hue that the camera renders differently, and the shirt stayed yellow (hue 68° against the JPEG's 82°; a 192-pixel proxy only reached 74°). `lightcraft-cli calibrate [--max N] [--out DIR] FOLDERS…` therefore pools the colour pairs of many ARWs, NEFs or RAFs per camera model — each against its own embedded JPEG, at a 192-pixel proxy, at most 4000 pairs per photo, files spread evenly over the folders — and fits one matrix (from white-balanced camera RGB, so no single photo's white point is baked in) and one hue/saturation/value table. Models need at least 5 usable photos.
|
||||
@@ -173,7 +191,11 @@ For 52 camera models LightCraft has colour matrices fitted to the camera's measu
|
||||
(`crates/raw/src/spectral.rs`, generated into `spectral_table.rs` by `tools/spectral_camera_table.py` from
|
||||
[rawtoaces-data](https://github.com/AcademySoftwareFoundation/rawtoaces-data) v1.1.0, Apache-2.0): 22 Canon, 11 Nikon,
|
||||
11 Sony, 3 Fujifilm and 5 others, plus the other names rawtoaces-data lists for them (EOS Rebel T3i and Kiss X5 for
|
||||
the EOS 600D; bodies it lists as sharing a sensor, such as the GFX100S with the GFX 100). Per model the generator fits,
|
||||
the EOS 600D; bodies it lists as sharing a sensor, such as the GFX100S with the GFX 100), and the bodies `SAME_SENSOR`
|
||||
in `spectral.rs` adds by hand because the table is generated: Fujifilm's 26.1 MP X-Trans CMOS 4 bodies (X-T30, X-T30 II,
|
||||
X-Pro3, X100V, X-S10, X-E4) take the X-T3's matrices. Without them an X-T30 raw whose camera JPEG was shot in Acros
|
||||
(black and white) got its tone curve on the muted neutral fallback, with a grey sky where the scene had a deep blue
|
||||
one. Per model the generator fits,
|
||||
under CIE Standard Illuminant A and under D65, a white-preserving 3×3 from white-balanced camera RGB to XYZ by the
|
||||
least mean CIE76 error over rawtoaces-data's 190 training reflectances, and writes DNG `ColorMatrix1/2` and
|
||||
`ForwardMatrix1/2`. The existing DNG colour model then interpolates them for the file's as-shot white, which the
|
||||
@@ -187,7 +209,7 @@ A raw starts from the first of these that is available (`camera_preview::startin
|
||||
4. the model's spectral matrices, with LightCraft's default tone curve and no JPEG fit;
|
||||
5. the neutral fallback.
|
||||
|
||||
When the file's JPEG is black and white, step 2 fits the tone curve alone with the profile's colour; without a profile, or when that fit fails, 4 and 5 take the tone curve fitted to it (see the black-and-white JPEG paragraph above).
|
||||
When the file's JPEG is black and white, step 2 fits the tone curve alone with the profile's colour; without a profile, or when that fit fails, 4 and 5 take the tone curve fitted to it (see the black-and-white JPEG paragraph above). If every existing JPEG fit fails, a near-black frame may take the guarded tone-only fallback described above, after its spectral or neutral colour has been chosen.
|
||||
|
||||
The spectral matrices only replace the neutral fallback: a photo whose JPEG fit is accepted (with or without a
|
||||
profile) renders exactly as before, and a covered model takes its spectral matrices only when that fit fails the
|
||||
|
||||
@@ -17,6 +17,18 @@ callbacks, so the app's loop is paused. Keep the window on screen while you driv
|
||||
headless snapshots or `lightcraft-cli mcp` without `--connect`. A request that has already started
|
||||
(a long command blocks frames) still gets the full 60 s.
|
||||
|
||||
Connections close after five minutes without incoming bytes while the server is waiting for
|
||||
input, including in the middle of an unfinished request line. This allows pauses between
|
||||
automation commands while releasing abandoned connection slots. An idle read timeout gets an
|
||||
`ok: false` error saying the connection is closing; incomplete input is never dispatched.
|
||||
This is a socket read inactivity limit, not a total connection lifetime: a peer that keeps
|
||||
sending bytes can keep it open. Waiting for an already-dispatched command's reply does not
|
||||
count as idle reading and retains the request timeout above.
|
||||
|
||||
Reconnect before sending another request after an idle close. MCP connect mode detects closed
|
||||
connections before sending and reconnects; if a connection is lost after sending a request,
|
||||
its outcome remains unknown and the client must not blindly replay it.
|
||||
|
||||
**Only requests are read.** Every line must be a JSON object with a string `method` (`id` and `params` are
|
||||
optional; blank lines are skipped). Anything else — text that isn't JSON, a JSON array or number, an object without
|
||||
`method`, invalid UTF-8, or a line longer than 4 MiB — gets one error reply
|
||||
@@ -76,7 +88,7 @@ filmstrip is visible.
|
||||
| `ui.scroll` | `{dx, dy, cmd?, ctrl?, shift?, alt?}` | Wheel / two-finger scroll at the current pointer; pans over the image, modifier-scroll zooms |
|
||||
| `ui.zoom` | `{factor}` | Pinch zoom at the current pointer (positive scale multiplier; 1 = unchanged). Position it first with `ui.move` or `ui.hoverWidget` |
|
||||
| `ui.set` | partial UI state, e.g. `{"view": "detail"}` | Resulting UI state |
|
||||
| `ui.dialog.confirm` / `ui.dialog.cancel` | — | Close the open dialog |
|
||||
| `ui.dialog.confirm` / `ui.dialog.cancel` | — | Close the open dialog (confirm runs its action; Synchronize Folder on a folder that is up to date answers `{"upToDate": true}` and starts nothing) |
|
||||
| `ui.resize` | `{width, height}` | Resize the window |
|
||||
| `ui.zoomFactor` | `{factor}` | Set the interface scale (0.5–3; egui's zoom keys are off, Cmd+= / Cmd+- zoom the photo) |
|
||||
| `ui.screenshot` | `{path?, headless?}` | `{path, width, height}` once the frame (with finished renders) is captured. `headless: true` draws the UI on the CPU (no compositor needed); a windowed capture that gets no frame within 2 s falls back to headless automatically. While the loupe still waits for a render, `ui.widgets {filter: "loupe:rendering"}` lists its "Rendering…" pill; `loupe:renderFailed` is the pill saying a render failed behind a stand-in; Compare and Reference panes have their own (`compare:rendering:<pane>`, `compare:renderFailed:<pane>`) |
|
||||
@@ -281,6 +293,22 @@ one pixel.
|
||||
(clicks, keys, drags) has played out. Widget ids for `ui.clickWidget` come from `ui.widgets`
|
||||
(e.g. `button:upright-auto`). A 1600×1000 demo snapshot takes ~1–2 s (debug build).
|
||||
|
||||
## Import album picker
|
||||
|
||||
In the Import Photos review, `albumPicker:import-album` opens the destination picker.
|
||||
`albumPickerFolder:<folder-id>:import-album` expands or folds a folder; it never chooses a destination.
|
||||
`albumPickerItem:<album-id>:import-album` selects a regular album. The picker follows sidebar order,
|
||||
opens the current choice's ancestors and shows its full path on the closed button. Smart albums
|
||||
are not import destinations. Type into `albumPickerSearch:import-album` to search folder/album paths;
|
||||
↑ / ↓ and Enter pick a search result, and Escape closes the picker while keeping the review open.
|
||||
If the chosen album is deleted while the review is open, the destination returns to None before
|
||||
confirmation instead of submitting the deleted ID.
|
||||
|
||||
`importAlbum:none` clears the destination and any pending new album name. `importAlbum:new` keeps
|
||||
or initializes that name, edited through `field:importAlbumName`. Choosing an existing album clears
|
||||
the pending name. `ui.dialog.confirm` starts the same import into the selected album ID; engine
|
||||
`library.import` parameters `album` and `albumName` are unchanged.
|
||||
|
||||
## Panel docking
|
||||
|
||||
Library, Tools, and Presets panels can be regrouped, split, floated within the app window, closed, and reopened. Tools keeps the current Edit/Crop/Masking mode; the photo view and filmstrip stay together. The document/session view is protected. Layouts, active tabs, panel visibility, and floating rectangles are saved with the app's existing UI preferences. These operations do not edit document contents.
|
||||
|
||||
+12
@@ -127,6 +127,18 @@ tiles. All ten sensor comparisons pass. R8 HEVC preview tracks are distinguished
|
||||
the primary Bayer RAW; the HEVC preview itself is not decoded. Missing corpus
|
||||
files are reported as skipped by the tests.
|
||||
|
||||
In a tile with a neighbour on its right, the vertical high-pass bands of the level-1 and level-2
|
||||
low-pass columns (bands 8 and 5) are entropy coded at an odd width: an even band carries one extra
|
||||
column that synthesis does not use. No other band is extended, including the last level's (band 2).
|
||||
The decoder takes each band's coded width from this rule. It used to try the synthesis width and up to
|
||||
two more columns and keep the first that decoded to the exact end of the stream, but two widths can
|
||||
both do that and give different coefficients (#899). The rule was measured on every C-RAW plane in
|
||||
`cr3_corpus.rs`: two-tile files at tile widths 2816, 3144, 3192, 3444 and 3564 (G5 X Mark II, M50,
|
||||
R100, 250D, RP, R, 90D, M6 Mark II), whose left tiles extend bands 5 and 8 exactly where the rule says,
|
||||
and single-tile `0x100` and `0x200` files, which never extend a band. In every plane, each band decodes
|
||||
at its coded width and at no other width up to two columns wider, so all sensor hashes are unchanged.
|
||||
No public file has three or more C-RAW tiles; a middle tile follows the same rule, which is unverified.
|
||||
|
||||
The version `0x200` QP predictor and entropy framing were checked independently.
|
||||
Controlled input mutations establish floor averaging of paired QP rows before
|
||||
the nonlinear integer table, then multiplication, division by eight, addition
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ pool. A scalar implementation checks the optimized operators on synthetic networ
|
||||
Tract is not a workspace dependency or a product feature.
|
||||
CPU inference and the GPU model module are feature-gated; ordinary engine/web builds
|
||||
do not compile the faer/GEMM backend. The engine depends on the inference crate only through `denoise = ["dep:lightcraft-denoise", …]`;
|
||||
lightweight `lightcraft-denoise-core` metadata and cache formats remain available without inference. Pictures are capped at 100 megapixels; at most
|
||||
lightweight `lightcraft-denoise-core` metadata and cache formats remain available without inference. Pictures are capped at 110 megapixels; at most
|
||||
four tile calls run together, chosen within the process-wide working-memory budget.
|
||||
One idle CPU workspace is retained, and cache costs reserve room for the blended picture.
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ LightCraft reads the distortion and vignetting tables that Fujifilm cameras stor
|
||||
path, EXIF orientation handling and DNG export use them like any other embedded correction.
|
||||
|
||||
Corrected: **distortion** (with the camera's frame-filling zoom) and **vignetting**, for the 59 bodies listed in
|
||||
`crates/raw/src/vendor/raf_lens.rs` (`VALIDATED`). **Not corrected: lateral chromatic aberration** (the third table,
|
||||
`0xf00f`); see "Chromatic aberration" below. Bodies without a checked sample, and tables in a layout or normalisation
|
||||
that was not understood, stay uncorrected.
|
||||
`crates/raw/src/vendor/raf_lens.rs` (`VALIDATED`); the nine-knot bodies among them other than the X-T4 were checked
|
||||
with an earlier reading of their tables than the one they now get (see "Knot radius"). **Not corrected: lateral
|
||||
chromatic aberration** (the third table, `0xf00f`); see "Chromatic aberration" below. Bodies without a checked sample,
|
||||
and tables in a layout or normalisation that was not understood, stay uncorrected.
|
||||
|
||||
## Clean-room evidence
|
||||
|
||||
@@ -29,10 +30,12 @@ Every table is an array of signed rationals with `n` knots:
|
||||
- `scale × n` is the half diagonal of the recorded image (the RAF crop) in pixels: 3749 for 6240×4160, 4643 for
|
||||
7728×5152, 2942 for 4896×3264, 7280 for 11648×8736, on 61 of the 65 bodies. Files that store another number (X-S20,
|
||||
X-T30 III and X-M5 write the 40 MP value, 4643; an X-T5 in an in-camera crop) are left uncorrected.
|
||||
- Knots are positions along the radius. The **last knot is the farthest corner**: `ρ = knot_last · r`, `r` the
|
||||
distance from the centre in half diagonals. Older bodies use 11 knots `0, 0.1 … 1`, newer ones 9 knots evenly
|
||||
spaced in `r²` (`√(k/8)`, last `√(9/8) = 1.0607`), GFX 50 bodies 8 (`√(k/8)`, last 1). The distortion zero and the 100 %
|
||||
illumination at the axis are anchored at `ρ = 0` when the first knot lies above it.
|
||||
- Knots are **radii in half diagonals**: the farthest corner is at 1, and a table is read at `ρ = r`, the distance
|
||||
from the centre in half diagonals. Older bodies use 11 knots `0, 0.1 … 1`, newer ones 9 knots evenly spaced in `r²`
|
||||
(`√(k/8)`), GFX 50 bodies 8 (`√(k/8)`, last 1). In the nine-knot tables the eighth knot is the corner and the ninth,
|
||||
`√(9/8) = 1.0607`, lies beyond it. In all 1,140 tables of 570 X-T4 raws, taken with three lenses at every focal
|
||||
length they were shot at, the ninth repeats the eighth's value. The distortion zero and the 100 % illumination at the axis
|
||||
are anchored at `ρ = 0` when the first knot lies above it.
|
||||
- Distortion values are percent: the bracket `1 + D/100` multiplies the radius.
|
||||
- Vignetting values are percent of the axial illumination at the (source) radius.
|
||||
- The chromatic-aberration table has the layout `[scale, knots, red × n, blue × n, scale]` (fractions of the radius,
|
||||
@@ -43,8 +46,8 @@ Every table is an array of signed rationals with `n` knots:
|
||||
The camera's JPEG samples the raw at
|
||||
|
||||
```
|
||||
source = z · r · (1 + D(ρ(source)) / 100) ρ = knot_last · source
|
||||
z = 1 / max over the output border radii of (1 + D(ρ(source)) / 100)
|
||||
source = z · r · (1 + D(source) / 100)
|
||||
z = 1 / max over the output border radii of (1 + D(source) / 100)
|
||||
```
|
||||
|
||||
The table is indexed by the **source** (sensor) radius, not the output radius, and the zoom `z` keeps the output
|
||||
@@ -54,11 +57,16 @@ radius is solved per radius by fixed-point iteration, then `source(r) = r (k0 +
|
||||
tables 0.0007, median 0.00012) or folds is rejected. The correction itself is the warp's bracket; the camera's rounding
|
||||
and interpolation between knots are not reproduced beyond linear interpolation between knots.
|
||||
|
||||
Vignetting is the gain `100 / V(ρ(r))` at the source radius, fitted by a 5-term minimax polynomial in `r²`
|
||||
Vignetting is the gain `100 / V(r)` at the source radius, fitted by a 5-term minimax polynomial in `r²`
|
||||
(`FixVignetteRadial`); a fit more than 0.025 off the table is rejected.
|
||||
|
||||
## Validation
|
||||
|
||||
The tables were first read with the last knot at the corner (`ρ = knot_last · r`). For the 11-knot and GFX 50 tables,
|
||||
whose last knot is 1, that is the same reading. For the nine-knot tables it reads every value from 6 % further out;
|
||||
"Knot radius" below has the evidence that changed it. The figures in the rest of this section were measured with the
|
||||
earlier reading.
|
||||
|
||||
Method: LightCraft's uncorrected render of each RAF (1200 px long edge) against the camera JPEG (`camera-jpeg/`,
|
||||
resized to the same size). Per file the high-passed luminance is correlated (NCC) after a warp with a free global scale and
|
||||
shift; the warp is (a) similarity only, (b) the table with its sign flipped, (c) the table, lookup at the output
|
||||
@@ -104,6 +112,35 @@ tables (X-M5, X-T2, X-T3) give a gain of zero in the JPEG. Through the shipped c
|
||||
β at least halves on 30 of them. Seven combinations show only a fraction of the amplitude
|
||||
(S1, SL1000, X-A2, X-A3, X-S1, X-T1, X-Pro3); the camera setting that governs them is not recorded.
|
||||
|
||||
### Knot radius
|
||||
|
||||
On the X-T4, the nine-knot tables read at the plain radius match the camera and the earlier reading does not:
|
||||
|
||||
- **Geometry, against the free fit measured above** (`matches_the_measured_x_t4_geometry`, pixls 3913, XF16-55 at 16 mm):
|
||||
the camera's scale is 1.0300. Read at the plain radius the table gives a zoom of 1.0298 and stays within 0.0008 half
|
||||
diagonals of the fit at every radius; the earlier reading gave 1.0333 and strayed up to 0.0034
|
||||
- **Geometry and gain, against synthetic charts the camera converted.** Copies of the same raw with patches of known
|
||||
colour, converted by the camera in its USB raw conversion mode. Against the camera's geometry from 445 chart markers,
|
||||
the distortion table read at the plain radius is at most 1.2 pixels off at 3120 × 2080 out to 0.92 of the half
|
||||
diagonal (earlier reading 4.2), and against its corner gain from 46,818 flat windows the vignetting table is within
|
||||
1.2 % out to 0.9 of the half diagonal (earlier reading up to 6.5 % too bright, 5.9 % at 0.8)
|
||||
- **On photographs**, 500 of the contributor's own X-T4 raws (XF16-55, XF70-300 and a 13 mm lens), shown only as
|
||||
aggregates. With the earlier reading the table bends inside the frame, where the ninth knot's repeated value begins
|
||||
(`1 / 1.0607 = 0.943` of the half diagonal), and on 253 of the 425 zoom-lens raws the warp polynomial failed its fit
|
||||
checks, so they got no distortion correction at all. Read at the plain radius the bend is at the corner and 424 of
|
||||
the 425 get their warp. The 75 raws from the 13 mm lens record an all-zero distortion table, so they have nothing to
|
||||
correct either way. Against the camera's own conversion of each raw (LightCraft's render at 1,200 pixels, compared
|
||||
at 780 × 520), the correlation of the high-passed luminance went from a median of 0.03 to 0.90 on the XF16-55 (327
|
||||
raws) and from 0.02 to 0.83 on the XF70-300 (91), and stayed at 0.96 on the 13 mm. Over flat areas, LightCraft's
|
||||
corners less its centre, against the camera's, went from +0.56 to +0.21 L* on the XF16-55 and from +0.49 to +0.21 on
|
||||
the XF70-300. A further 292 raws held back from all of this gave the same: correlation 0.06 to 0.91 and 0.21 to
|
||||
0.85, corners +0.62 to +0.26 and +0.35 to +0.14 L*
|
||||
|
||||
The other bodies with nine-knot tables are read at the plain radius too, because their knots have the same layout
|
||||
(`√(k/8)`, the eighth at the corner), but none of them was measured at it: their place in `VALIDATED` rests on the
|
||||
figures under "Validation" above, taken with the earlier reading, and is unconfirmed for the plain radius until a camera
|
||||
JPEG from each is checked. Render cache version 66 redraws thumbnails rendered with the earlier reading.
|
||||
|
||||
### Chromatic aberration (not applied)
|
||||
|
||||
The CA table is real: on the raw mosaics themselves (uncompressed RAFs, colour planes built by normalised convolution so
|
||||
|
||||
+21
-10
@@ -1,6 +1,6 @@
|
||||
# Gaps: where LightCraft falls short of Lightroom
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** minor (gap 10: 515 of 1,446 models verified, ~84% of photographers on a verified body)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-22 · **Change:** minor (#2052: Thai export glyphs added; text-layout limits remain; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 551 models verified; gap 15: Print Page Options — page numbers, page info, crop marks; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: `\` shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains)
|
||||
|
||||
Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and
|
||||
how hard it blocks them), then by effort. **Agents pick work from the top** (the first entry nobody is
|
||||
@@ -15,35 +15,35 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
|
||||
|
||||
| # | Gap | Evidence | User impact | Hours | Doc |
|
||||
|---:|---|---|---|---:|---|
|
||||
| 1 B | **Camera colour is estimated, not measured** (LR-PROF-CAMERACOLOR). Raws start from a per-file fit to the camera JPEG, 5 bundled profiles or 52 spectral matrices; others use a neutral matrix (a black-and-white camera JPEG gives its tone curve only, #577; dark JPEG tones also guide the toe, #1307) | ΔE vs camera JPEG 2.9–10 after fitting (raw-parity.md); 0 chart-calibrated models vs 1,500 Adobe Standard profiles | every raw shooter sees colour that is close but not Lightroom's; some get neutral-matrix colour | 50–90 | [raw-parity](raw-parity.md#colour) |
|
||||
| 1 B | **Camera colour is estimated, not measured** (LR-PROF-CAMERACOLOR). Raws start from a per-file fit to the camera JPEG, 5 bundled profiles or 52 spectral matrices (plus same-sensor bodies, e.g. the X-T30 through the X-T3); others use a neutral matrix (a black-and-white camera JPEG gives its tone curve only, #577; dark JPEG tones also guide the toe, #1307) | ΔE vs camera JPEG 2.9–10 after fitting (raw-parity.md); 0 chart-calibrated models vs 1,500 Adobe Standard profiles | every raw shooter sees colour that is close but not Lightroom's; some get neutral-matrix colour | 50–90 | [raw-parity](raw-parity.md#colour) |
|
||||
| 2 B | **CR3 variants no public file uses** (LR-IMP-FORMATS): `crx.rs` refuses vertical-tile C-RAW, partial-subband and other plane coding, multi-tile 0x200 and adaptive low-frequency gains, and those files fall back to the preview. All 110 CC0 CR3s on raw.pixls.us (31 folders) decode from sensor data, so none of these variants has a public sample; 26 CR3 bodies are verified (M50 / R100 / R8 exact, R5 Mark II pinned, 22 more pinned on one CC0 file each and checked against the camera JPEG, [cr3.md](cr3.md#further-bodies-one-cc0-file-each)). HEIF stills are `.HIF` files, not CR3; no CR3 sRAW / mRAW sample exists | measured: Canon's share whose camera fully works 73% → 94% (raw-parity.md); 18 of 22 popular Canon bodies write CR3, all 18 now verified | +0.3 points of photographers; the variants surface only as user files that open as a preview | 10–25 | [raw-parity](raw-parity.md#remaining-work-ranked-by-photographers-unlocked-per-hour) |
|
||||
| 3 B | **Render fidelity unmeasured** (LR-BEHAV-RENDER-FIDELITY): tone, highlights, texture / clarity / dehaze, NR and sharpening are tuned by eye | no side-by-side suite; a retune needs a new process version (only V1 exists) | edits imported from Lightroom look different; the look "isn't Lightroom's" | 30–60 | [raw-parity](raw-parity.md), [process-versions](process-versions.md) |
|
||||
| 4 | ~~Original-pixel detail at 100%~~ (LR-VIEW-ZOOM): closed 2026-10-12, see Closed recently | checklist row ✅ | — | 0 | [ui-parity](ui-parity.md) |
|
||||
| 5 B | **AI masks: Subject, Sky, People, Background, Landscape** are heuristics; Object / Describe run SAM 3 but its download has no mirror configured | `crates/segment`; ai-masks.md; MASK rows 67% | masking is how most Lightroom users edit today | 40–70 + model decision | [target-app-parity](target-app-parity.md#ai) |
|
||||
| 6 B | **Compressed ORF** (every Olympus / OM body since ~2008) | measured: 66 of 78 Olympus / OM models preview-only; `orf.rs` refuses; OM bucket 3% of photographers, 3% of them fully work | +2.6 points; best gain per hour of any raw gap (~0.2) | 8–15 | [raw-parity](raw-parity.md#compressed-orf-a-clean-room-starting-point) (the two Olympus patents that describe the method) |
|
||||
| 7 | **Nikon High Efficiency NEF** (HE / HE★ on Z 8, Z 9, Z 6III, Z f, Z50II, Z5II when set to HE) | named and preview only (#193); needs a pure-Rust JPEG XS decoder | +1.1 points (only shots taken in HE; lossless works on the same bodies); low gain per hour (~0.04), so not a beta blocker | 20–40 | [raw-parity](raw-parity.md) |
|
||||
| 8 B | **No lens profile database** (LR-EDIT-OPTICS-PROFILE): only DNG opcodes, RW2 in-file distortion, Sony ILCE-7RM4A and ILCE-7RM6 distortion (`docs/sony-ilce7rm6-lens-corrections.md`) and Nikon Z 6II / Z 8 / Z f distortion (`docs/nikon-lens-corrections.md`) | Adobe ships 3,635 lens profiles; we never use them | distortion / vignetting correction is a default step for many | 40–80 + calibration shots | [raw-parity](raw-parity.md#lens-profiles) |
|
||||
| 8 B | **No lens profile database** (LR-EDIT-OPTICS-PROFILE): only DNG opcodes, RW2 in-file distortion, Sony ILCE-7RM4A, full-frame ILCE-7CR distortion (Auto only) and ILCE-7RM6 distortion (`docs/sony-ilce7rm6-lens-corrections.md`) and Nikon Z 6II / Z 8 / Z f distortion (`docs/nikon-lens-corrections.md`) | Adobe ships 3,635 lens profiles; we never use them | distortion / vignetting correction is a default step for many | 40–80 + calibration shots | [raw-parity](raw-parity.md#lens-profiles) |
|
||||
| 9 B | **Tests don't run in CI on macOS, Windows or Linux x64**: only FreeBSD runs `cargo test`; the others build packages | `.github/workflows/` (freebsd, packaging-lint, release, windows-arm64); the linux-riscv64 release job added in 0.6.0 (#844) runs only `lightcraft-cli --version` under QEMU | regressions reach users on the main platforms; `cargo xtask ci` runs only locally | 6–12 | [target-app-parity](target-app-parity.md#by-dimension) |
|
||||
| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 545 of 1,446 Adobe models verified (38%), ~85% of photographers on a verified body; Nikon 5 bodies verified, Canon 34, Leica 14 (the Q3 by a recorded check of 38 private DNGs). Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 / F828 regression (#702), DSLR-A200…A390 / A850 12-bit ARW (#535) | measured: [raw/coverage.tsv](raw/coverage.tsv) | per-body bugs (levels, crops, WB) surface only from users | 25–45 | [raw-parity](raw-parity.md#by-maker-measured) |
|
||||
| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 551 of 1,446 Adobe models verified (38%), ~85% of photographers on a verified body; Nikon 5 bodies verified, Canon 34, Leica 14 (the Q3 by a recorded check of 38 private DNGs). Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 regression (#702); F828 SRF now discovered and imported as preview-only, sensor decoding still missing, DSLR-A200…A390 / A850 12-bit ARW (#535) | measured: [raw/coverage.tsv](raw/coverage.tsv) | per-body bugs (levels, crops, WB) surface only from users | 25–45 | [raw-parity](raw-parity.md#by-maker-measured) |
|
||||
| 11 | **Enhance: Raw Details, Super Resolution; AI denoise without shippable weights or X-Trans** | ENH rows 0%; denoise Bayer only, RawNIND weights GPL and opt-in | Fujifilm users get no AI denoise; nobody gets it out of the box | 30–60 + model decision | [raw-parity](raw-parity.md#demosaic-levels-crops-and-lens-metadata) |
|
||||
| 12 | **Content-aware / generative remove, detect distractions, reflections removal** | REM rows 75%; patch synthesis missing | cleanup work goes back to Photoshop | 25–45 (+ generative model) | [target-app-parity](target-app-parity.md#ai) |
|
||||
| 13 | **Lightroom catalog migration is one-way and approximate**: unsupported Adobe profiles / AI settings archived, history kept as data; offline record counts and destination are now shown in a lasting import result | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) |
|
||||
| 13 | **Lightroom catalog migration is one-way and approximate**: unsupported Adobe profiles / AI settings archived, history kept as data; main-file pages read on demand up to 16 GiB, with dense-table and WAL bounds retained (#798); offline record counts and destination are now shown in a lasting import result | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) |
|
||||
| 14 | **HDR display** (EDR / Windows HDR); the headroom limit and SDR rendition from `crs` XMP (the HDR switch, `HDREditMode`, is read). HLG AVIF export done; the display's SDR/HDR decision is tested, blocked on eframe's 8-bit surface | HDR rows 70%; LR-VIEW-HDR-DISPLAY ⬜ | HDR editing works blind on HDR monitors | 12–25 | [hardware-parity](hardware-parity.md) |
|
||||
| 15 | **Print module**: identity plate / watermark / page numbers / crop marks / photo info overlays, Custom Package Auto Layout, printer-driver Page Setup and direct printing, JPEG file output (the Print dialog writes a colour-managed PDF) | LRC-PRINT rows | Classic users who print | 15–25 | [target-app-parity](target-app-parity.md#features) |
|
||||
| 15 | **Print module**: identity plate / watermark / photo info overlays, Cut Guides for the packages, Custom Package Auto Layout, printer-driver Page Setup and direct printing, JPEG file output (the Print dialog writes a colour-managed PDF) | LRC-PRINT rows | Classic users who print | 15–25 | [target-app-parity](target-app-parity.md#features) |
|
||||
| 16 | **Map view, Book, Slideshow module, publish services** | LRC-MAP / BOOK / SS rows ⬜ | Classic users; publish services matter for Flickr / SmugMug workflows | 60–100 | [target-app-parity](target-app-parity.md#features) |
|
||||
| 17 | **Tethered capture** | ⬜ | studio photographers | 25–45 + cameras | [hardware-parity](hardware-parity.md) |
|
||||
| 18 | **Video**: playback, trim, global edits, export | VID rows 0% | users who mix stills and clips | 25–45 | [target-app-parity](target-app-parity.md#features) |
|
||||
| 19 | **Plug-in ecosystem**: no Lightroom SDK equivalent (export / publish plug-ins, metadata plug-ins) | ⬜ | pro workflows built on plug-ins (LR/Enfuse, Jeffrey's exporters, Negative Lab Pro) | 40–80 + owner decision | [target-app-parity](target-app-parity.md#by-dimension) |
|
||||
| 20 | **Long-tail raw formats**: compressed SRW, ARQ pixel shift, IIQ, Hasselblad compression, X3F, CRW, MRW, ERF, KDC, DCR, MEF, GPR, old FinePix | raw-parity.md | small user share each | 45–85 | [raw-parity](raw-parity.md) |
|
||||
| 21 | **Localization**: 9 of 15 shipped languages partial (de, es, fr, ja, uk now 97–99% of catalog messages, awaiting native review; pt-BR, ru, zh-hans, zh-hant 74–92%), lower-layer errors English; Hindi, Arabic, Indonesian, Korean, Vietnamese absent; no RTL or complex shaping | measured from catalogs | non-English users see mixed English | 40–70 (shipped to full); 145–255 (five new) | [localization-parity](localization-parity.md) |
|
||||
| 20 | **Long-tail raw formats**: compressed SRW, ARQ pixel shift, IIQ, Hasselblad compression, X3F, CRW, ERF, KDC, DCR, MEF, GPR, old FinePix (MRW decodes since 2026-10-18) | raw-parity.md | small user share each | 40–80 | [raw-parity](raw-parity.md) |
|
||||
| 21 | **Localization**: 10 of 16 shipped languages partial (de, es, fr, ja, uk now 97–99% of catalog messages and id every message but What's New, awaiting native review; pt-BR, ru, zh-hans, zh-hant 74–92%), lower-layer errors English; Hindi, Arabic, Korean, Vietnamese absent; no RTL or complex shaping; exports add Thai glyphs only (#2052), with horizontal mark positioning still incomplete and Arabic fallback deferred until joining and bidi exist | measured from catalogs | non-English users see mixed English | 40–70 (shipped to full); 135–239 (four new) | [localization-parity](localization-parity.md) |
|
||||
| 23 | **Accessibility**: AccessKit tree audited by a test; canvas handles, spots and mask pins by keyboard; left: brush and red-eye by keyboard, a session with a real screen reader | LR-BEHAV-ACCESS 🟡 | blind and low-vision users | 2–4 | [ui-parity](ui-parity.md) |
|
||||
| 22 | **Pen pressure on macOS / Linux tablets** (brush size and flow follow pressure where winit reports a pen force), OS monitor profiles on Windows (Linux reads colord's, macOS the screen's), follow-window colour on Linux / Windows | hardware-parity.md | brush users with tablets; multi-monitor setups | 11–22 | [hardware-parity](hardware-parity.md) |
|
||||
| 24 | **Brush live preview and masking feel** (#517, #518); Feather / Edge mask refinement (15.5) | issues | masking feels slower than Lightroom | 8–12 | [ui-parity](ui-parity.md) |
|
||||
| 25 | **File writes**: AVIF import; Render to DNG as a catalog command (lossy DNG and the Render to DNG export format landed) (PSD export landed with Edit in PhotoCraft, #953) | file-format-parity.md | interchange edge cases | 19–34 | [file-format-parity](file-format-parity.md) |
|
||||
| 26 | **Performance at scale**: 85k-photo library opens in 1.7–4.7 s; 100k grid test pending; colour NR at half resolution; at 100k photos stack and album lookups are indexed (an "In a Stack" or "Album" smart album counts in ~14 ms, was 0.6–2.7 s) | ROADMAP log, gpu-pipeline.md | large-catalog users | 15–30 | [target-app-parity](target-app-parity.md#by-dimension) |
|
||||
| 27 | **Windows / Linux runtime coverage**: Windows installer UI unverified on Windows (#79); GPU path proven on Apple and on one AMD GPU on Linux (Radeon 8060S, Vulkan); NVIDIA, Intel and DX12 unverified; the Linux riscv64 tarball (new in 0.6.0, #844) is cross-compiled and never run beyond a QEMU smoke test | ROADMAP, issues, `release.yml` | non-Mac users find bugs first | 13–27 | [hardware-parity](hardware-parity.md) |
|
||||
| 27 | **Windows / Linux runtime coverage**: the Windows x64 installer is verified on Windows 11 (0.9.1: install and upgrade end on the completion page, a custom folder is kept, Launch works, removal is clean), but uninstalling from Settings → Apps ends with no confirmation (#892, #931); GPU path proven on Apple and on one AMD GPU on Linux (Radeon 8060S, Vulkan); NVIDIA, Intel and DX12 unverified; the Linux riscv64 tarball (new in 0.6.0, #844) is cross-compiled and never run beyond a QEMU smoke test | ROADMAP, issues, `release.yml` | non-Mac users find bugs first | 13–27 | [hardware-parity](hardware-parity.md) |
|
||||
| 29 | **HDR merge leftovers** (LR-MERGE-HDR): AI Denoise doesn't run on merged (linear) DNGs; JPEG brackets are treated as linear (no camera response curve); every frame is held as 32-bit float, ~0.86 GB per 67 MP frame (half-float frames would halve it) | tested on Sony A7R VI brackets 2026-10-14 ([merge.md](merge.md)); Lightroom's Denoise supports HDR DNGs | HDR shooters who denoise their merges, merge JPEGs, or merge 7+ frames of high-resolution raws on 16 GB machines | 10–20 (+ denoise linear-RGB contract, gap 11) | [merge](merge.md) |
|
||||
| 28 | **Small Classic shortcuts and keys**: rating `[` `]`, flag cycling, filter-bar keys, F1 help, panel keys (Tab, F5, F8; F6 and F7 toggle the filmstrip and the left panel) | [ui-parity](ui-parity.md#shortcuts) | Classic muscle memory | 4–8 | [ui-parity](ui-parity.md) |
|
||||
| 28 | **Small Classic shortcuts and keys**: ⌘L (Enable Filters), Tab / ⇧Tab (hide the side panels / all panels; Tab moves the keyboard focus here) and F5. Rating `[` `]`, flag cycling (`` ` ``), `\` (Filter Bar), F1 help, F6 / F7 / F8 (filmstrip, left panel, right panel) are bound | [ui-parity](ui-parity.md#shortcuts) | Classic muscle memory | 2–4 | [ui-parity](ui-parity.md) |
|
||||
|
||||
Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform work they need around them,
|
||||
**~340–600 h** to beta (incl. 40–80 h for the ~92 open core-path user issues) (see [target-app-parity.md](target-app-parity.md#stage)).
|
||||
@@ -60,6 +60,8 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
|
||||
|
||||
## Closed recently (kept for calibration)
|
||||
|
||||
- 2026-10-20: the import review album picker follows the sidebar folder hierarchy and searches album paths (#2362, LR-IMP-ADD-DIALOG), retaining None/New album and existing destination IDs. This closes a usability subgap; no ranked gap or headline feature count closes.
|
||||
|
||||
- 2026-10-12: original-pixel detail at 100% (gap 4, LR-VIEW-ZOOM, #323): soft proofing gets zoom windows like
|
||||
Detail, Before/After, Compare, Reference and mask overlays; 1:1 tested equal to the full-size export.
|
||||
- 2026-10-11: Sony ILCA-99M2 profile.
|
||||
@@ -77,6 +79,15 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-22 | minor | #2052: Thai glyphs added to shared export text; gap 21 retains Thai mark positioning, Arabic joining and bidi limits. No ranked gap closes; estimates unchanged |
|
||||
| 2026-10-21 | minor | Gap 28 shrinks: F8 shows / hides the right panel (`view.rightPanel`); rating `[` `]`, flag cycling and F1 help were already bound, so the entry lists only filter-bar keys, Tab / ⇧Tab and F5; 2–4 h (was 4–8) |
|
||||
| 2026-10-20 | minor | #2362: import destination albums use the shared hierarchy/search picker instead of a flat name list; existing import workflows retained; ranked gaps/readiness counts unchanged |
|
||||
| 2026-10-19 | minor | Gap 21: Indonesian (`id`) shipped with every catalog message but What's New; four new languages left (Hindi, Arabic, Korean, Vietnamese), 135–239 h |
|
||||
| 2026-10-19 | minor | Gap 27: the released 0.9.1 x64 MSI is tested on Windows 11 Pro 24H2 (a fresh install and an upgrade from 0.8.0 end on the completion page, so #790 is fixed; the upgrade keeps a custom folder; Launch starts the app; removal leaves nothing behind); uninstalling from Settings → Apps still ends with no confirmation (#892, #931); hours unchanged |
|
||||
| 2026-10-18 | patch | Gap 1: six X-Trans CMOS 4 Fujifilm bodies (X-T30 …) take the X-T3's spectral matrices instead of the neutral matrix |
|
||||
| 2026-10-15 | minor | Gap 15 shrinks: the Page panel's Page Options (Page Numbers, Page Info, Crop Marks) print on Single Image and Contact Sheet pages; identity plate, watermark, photo info, Cut Guides, direct printing and JPEG output remain |
|
||||
| 2026-10-12 | minor | Gap 10: SR2/SRF folder discovery fixed; F828 imports as preview-only. Sensor decoding remains a gap; readiness scores unchanged |
|
||||
| 2026-10-15 | minor | Gap 28: `\` shows / hides the Filter Bar in the grids; the entry drops `[` `]` (#509), flag cycling (#1490) and F1 (Help), already on main; ⌘L is the filter-bar key left; hours unchanged |
|
||||
| 2026-10-14 | minor | Gap 26: at 100k photos stack and album lookups are indexed, so an "In a Stack" or "Album" smart album counts in ~14 ms (was 0.6–2.7 s); open time unchanged |
|
||||
| 2026-10-14 | minor | Gap 10: Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (coverage.py 515 → 545 verified); sensor verification does not establish colour parity (#1277) |
|
||||
| 2026-10-14 | minor | Gap 21: de, es, fr, ja and uk catalogs filled (97–99%); 55–90 h → 40–70 h |
|
||||
|
||||
@@ -43,6 +43,8 @@ Overrides (read once at launch):
|
||||
- else `WGPU_BACKEND` (wgpu's own variable, same names) — before #136 only the window honoured it,
|
||||
the compute device always used Vulkan + DX12 (+ Metal).
|
||||
- `LIGHTCRAFT_GPU=0`: GPU rendering off for the process (the window is unaffected).
|
||||
- Both `LIGHTCRAFT_GPU=0` and `LIGHTCRAFT_GPU_BACKEND=off` also keep AI Denoise, the face models
|
||||
and the AI mask model (SAM 3 on Metal) on the CPU.
|
||||
|
||||
**DX12 shader compiler (issue #471).** Every instance (window and compute) compiles DX12 shaders with
|
||||
FXC (`d3dcompiler_47.dll`, part of Windows; `lightcraft_gpu::backend::backend_options`). wgpu's
|
||||
@@ -55,6 +57,13 @@ chooses; with `dxc`, put `dxcompiler.dll` *and* `dxil.dll` (DXC ≥ 1.8.2502) ne
|
||||
FXC builds the compute kernels somewhat slower than DXC (≈ 9 s vs 7.5 s for the GPU test suite on an
|
||||
RTX 4080), off the startup path.
|
||||
|
||||
FXC merges stores to neighbouring elements of a storage array into one wider store. NVIDIA's DX12 driver ran
|
||||
the 16-byte store it made of a denoise convolution's output wrong on a GTX 1070 (every second pixel's channels
|
||||
shifted; DXC and Vulkan were right), so `nn_conv.wgsl` steps those stores by `1 + zero` from its uniform and FXC
|
||||
leaves them apart. The transposed convolution's merged store ran right on that card, but it steps the same way.
|
||||
`nn::tests::a_layer_stores_every_pixels_channels_where_the_next_reads_them` catches the fault on such a card;
|
||||
`a_transposed_convolution_stores_every_pixels_channels_where_the_next_reads_them` checks the other store exactly.
|
||||
|
||||
**Off the startup path.** The compute device is created on a background thread once the window is
|
||||
up (`gpu::warm_up` from the first frame's settings), and never while GPU rendering is off: with
|
||||
Settings ▸ Performance ▸ *Use the GPU for rendering* unchecked (applied before the window opens),
|
||||
|
||||
@@ -21,6 +21,15 @@ length is used when the camera isn't listed) and the lens model string. Lens nam
|
||||
number (focal lengths, apertures) in the database name must appear in the EXIF name, and the lens maker must agree
|
||||
when the file names one.
|
||||
|
||||
Some cameras record no lens model at all, only the lens specification: Nikon DSLRs write the lens's focal range and
|
||||
maximum apertures in their maker note (tag `0x0084`) and no Exif `LensModel`, whatever lens is mounted. For such a
|
||||
file the lens is looked up by a name built from the specification, `24-70mm f/2.8` (`18-55mm f/3.5-5.6`,
|
||||
`85mm f/1.8`), and a profile is used only when one of its names is exactly that (ignoring case and punctuation).
|
||||
The specification fits every maker's lens with that range (a Nikkor, a Sigma, a Tamron), so neither a partial match
|
||||
nor a name with a maker in it ("Samyang 85mm f/1.4" for "85mm f/1.4") is taken; a database that should correct such
|
||||
files names its profile that way, e.g. a lensfun entry with an extra `<model>24-70mm f/2.8</model>` for the one
|
||||
24-70 f/2.8 the photographer owns. lensfun's own database has no such names, so it doesn't correct these files.
|
||||
|
||||
| Correction | Models | Interpolated over |
|
||||
|---|---|---|
|
||||
| Distortion | `poly3`, `poly5`, `ptlens` | focal length |
|
||||
|
||||
@@ -16,6 +16,7 @@ Select photos in Photo Grid or Square Grid, then use:
|
||||
| `` ` `` (backtick) | Cycle the flag: unflagged → pick → reject → unflagged |
|
||||
| `Cmd`/`Ctrl` + `↑` / `↓` | Raise / lower flag status (reject < unflagged < pick; stops at either end) |
|
||||
| `Shift` + `0`–`9`, `P`, `X` or `U` | Apply the action and select the next photo |
|
||||
| `\` (backslash) | Show / hide the Filter Bar |
|
||||
|
||||
Enter opens the active photo without changing the selection. Text fields, dialogs and open popups keep the key; a custom Enter binding takes precedence. Enter has no additional action in Detail.
|
||||
|
||||
@@ -39,6 +40,11 @@ step. AutoWrite uses the normal rating metadata path. In Detail, including Maski
|
||||
remapping and disabling the default brush bindings take precedence over their grid bracket actions.
|
||||
The separate Decrease Rating / Increase Rating commands can also be assigned in Help → Keyboard Shortcuts.
|
||||
|
||||
`\` shows and hides the Filter Bar in Photo Grid and Square Grid, as in Lightroom Classic's Library; in
|
||||
Detail and in the full-screen preview (`F`) it keeps showing the original (Develop's Before / After key).
|
||||
View → Filter Bar (`Shift+F`) works in every view. A Show Original moved to another key keeps its meaning
|
||||
in the grids, and `\` given to another command runs that command.
|
||||
|
||||
Flag cycling and raise/lower use each photo's own flag, so a mixed selection steps together without being made uniform. Adobe's shortcut list names the backtick "Cycle flag settings" but does not state the order; unflagged → pick → reject is LightCraft's choice. The key is the logical grave key, so it follows the keyboard layout (on layouts where it is a dead accent key, assign another key in Help → Keyboard Shortcuts to `photo.cycleFlag`).
|
||||
|
||||
`Home` and `End` make the first or last photo of the view the active, selected one. `Shift` + `←` /
|
||||
|
||||
@@ -28,6 +28,11 @@ settings are imported. Missing originals remain catalogued for relinking, count
|
||||
separately from failures reading files that are present. Known-missing originals are not probed
|
||||
again. Previously browsed Local records join All Photos even when their originals are offline;
|
||||
their promotion is counted once and undone together with the catalog import.
|
||||
An image whose file, folder or root folder record is gone from the catalog, or whose original path
|
||||
is empty (Lightroom leaves these behind and ignores them), is skipped together with its virtual
|
||||
copies instead of failing the whole import. The result lists them as data (`skipped`: each
|
||||
image id with its reason, `missingFile`, `missingFolder`, `missingRoot`, `incompletePath` or
|
||||
`masterSkipped`), also in `library.inspectLightroom`, and the desktop result window says how many in the UI language.
|
||||
Virtual copies count as added records too; reimport reports zero additions when all
|
||||
records already exist. Existing LightCraft
|
||||
edits are preserved by default; `updateExisting=true` explicitly replaces them. Persistent import
|
||||
@@ -44,11 +49,16 @@ retained. Oldest managed archives are retired; legacy `.json` archives remain un
|
||||
`Interop/lightroom-index.json` records imported identities. Source catalogs and originals are
|
||||
never overwritten.
|
||||
|
||||
Each native `.lrcat` database and WAL file is independently limited to 1 GiB
|
||||
(1,073,741,824 bytes). This bounds the current whole-file reader; referenced photo sizes and
|
||||
photo counts are not part of that limit. XMP packets are capped at 16 MiB and each table at one million rows.
|
||||
The in-tree, read-only Rust SQLite B-tree reader follows SQLite’s public file-format
|
||||
specification; it introduces no dependency or C runtime. Table payloads are capped at 256 MiB.
|
||||
Native import reads referenced database pages on demand (at most 64 KiB per page), rather than
|
||||
loading the whole `.lrcat`. Database files are capped at 16 GiB and 16,777,216 pages; the WAL
|
||||
remains capped at 1 GiB. Tables still have a 256 MiB encoded-payload budget and one million rows,
|
||||
individual records are capped at 64 MiB, and XMP packets at 16 MiB. These limits can still reject
|
||||
large catalogs with dense tables; file size alone no longer determines the memory allocation.
|
||||
The open file and source path are checked again after all tables are read. Close Lightroom before
|
||||
importing: metadata checks detect ordinary concurrent changes, but are not SQLite snapshot isolation.
|
||||
|
||||
The in-tree, read-only Rust SQLite B-tree reader follows SQLite's public file-format
|
||||
specification; it introduces no dependency or C runtime.
|
||||
Optional history, keyword and collection tables that exceed limits are skipped with warnings;
|
||||
required photo tables still report errors. Unrelated unsupported schemas are ignored. A needed
|
||||
WITHOUT ROWID or virtual table produces an explicit error, handled as a warning when optional.
|
||||
@@ -97,3 +107,16 @@ the path relative to the catalog that Lightroom records for root folders on the
|
||||
(`relativePathFromCatalog`), when that is a folder, and reports the remapping as a warning. Copying
|
||||
a project folder with its catalog inside it therefore keeps its originals linked. Other moved
|
||||
originals stay missing; use LightCraft's missing-photo relinking tools to update their paths.
|
||||
|
||||
Original and root folder paths are resolved with the catalog as the document and are looked at
|
||||
only when they are on this computer or on the network share the catalog itself is on
|
||||
(`crates/engine/src/doc_path.rs`). An original on another computer (`\\server\share\…`,
|
||||
`//server/…`, `file://server/…`), a device path, or one whose folder or file name in the catalog is
|
||||
an absolute path is not looked at: the import lists it as missing with a warning and leaves the
|
||||
photo and its virtual copies out of the library. To import originals on a share, open the catalog
|
||||
from that share. A path that is not a path on this computer (a Windows drive path read on macOS or
|
||||
Linux) is not looked at either and stays catalogued as missing.
|
||||
|
||||
## Historical local validation
|
||||
|
||||
Validated in a local Linux environment after upstream reconciliation: 47 catalog-focused tests, nine pipeline spot tests, the exact sparse-catalog regression, and all eight `cargo xtask ci` stages passed. The full gate recorded 3,166 passing test executions, zero failures, and 28 ignored tests. An original synthetic sparse catalog with a required image-table page beyond 1 GiB also passed headless application import and selection; its expected missing-original placeholder retained its five-star rating. This snapshot check does not establish native-window coverage or dense-catalog performance.
|
||||
|
||||
@@ -27,11 +27,13 @@ revizí rozsahu a terminologie. Rodilý mluvčí ani profesionální překladate
|
||||
plurálové pravidlo se nepřidává.
|
||||
- Písmo Inter dodávané s aplikací obsahuje české znaky i uvozovky „…“. Další font není potřeba.
|
||||
|
||||
Katalog obsahuje 2 056 běžných položek a 246 formátových šablon. Každý běžný klíč existuje
|
||||
v jiném katalogu nebo je používán kódem. Oproti německému katalogu má 108 dalších klíčů,
|
||||
Katalog obsahuje 2 394 běžných položek a 297 formátových šablon. Každý běžný klíč existuje
|
||||
v jiném katalogu nebo je používán kódem. Oproti německému katalogu má 215 dalších klíčů,
|
||||
všechny s doloženým použitím: popisky příkazů, menu, ovládacích prvků a nastavení,
|
||||
texty HDR/exportu a obličejů a kontextové hodnoty Ano/Ne. Z toho 41 klíčů již existuje
|
||||
v dalších katalozích a 67 doplňuje současné překladové lookupy.
|
||||
texty HDR/exportu a obličejů a kontextové hodnoty Ano/Ne. Z toho 66 klíčů již existuje
|
||||
v dalších katalozích a 149 doplňuje současné překladové lookupy. Doplnění z 18. 10. 2026
|
||||
přidalo texty novějších funkcí (prezentace, tisk, přednastavení efektů masek, zaostřovací
|
||||
body) a prošlo stejnou kontrolou.
|
||||
|
||||
Řádkové fragmenty poznámek k vydáním byly odstraněny, včetně 163 fragmentů převzatých
|
||||
ze sdílených katalogů; text poznámek v dialogu Co je nového používá anglický fallback.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Suomi
|
||||
|
||||
Suomen (`fi`, englanniksi **Finnish**, omakielinen nimi **Suomi**) saa käyttöön valitsemalla
|
||||
**Muokkaa → Kieli → Suomi** tai **Asetukset → Yleiset → Kieli**. Muutos tulee voimaan heti ja
|
||||
tallentuu seuraavaa käynnistystä varten. Komento on `app.language.finnish`; ohjauskanava
|
||||
hyväksyy `ui.set {"language":"fi"}` sekä tunnisteet `fi-FI` ja `fi_FI.UTF-8`. Ikkunattomassa
|
||||
ajossa voi käyttää `LIGHTCRAFT_LANGUAGE=fi`.
|
||||
|
||||
Luettelot `crates/ui-egui/locales/fi.json` ja `fi-formats.json` on laadittu tekoälyavusteisesti
|
||||
(Claude) englanninkielisten lähdemerkkijonojen merkityksestä, kokonaisina fraaseina. Avainlista
|
||||
on kaikkien muiden luetteloiden yhdiste. Suomenkielinen äidinkielinen puhuja (@roopeg) tarkistaa
|
||||
käännöksen; ammattikääntäjä ei ole sitä käynyt läpi.
|
||||
|
||||
## Käytännöt ja laajuus
|
||||
|
||||
- Valikot: Tiedosto, Muokkaa, Näytä, Kuva, Ikkuna, Ohje.
|
||||
- Library = Kirjasto, Develop = Kehitys, Collection = Kokoelma, Album = Albumi,
|
||||
Preset = Esiasetus, Catalog = Katalogi (Lightroom-katalogi; keyword and model lists stay
|
||||
”luettelo”), Snapshot = Tilannevedos, Version = Versio, Print (module) = Tulostus, the
|
||||
command Print… = Tulosta….
|
||||
- Exposure = Valotus, Contrast = Kontrasti, Clarity = Selkeys, Texture = Tekstuuri,
|
||||
Dehaze = Sumunpoisto, Vibrance = Eloisuus, Saturation = Värikylläisyys, Hue = Sävy,
|
||||
Tint = Värisävy, Temperature = Lämpötila, White Balance = Valkotasapaino.
|
||||
- Highlights = Huippuvalot, Shadows = Varjot, Whites = Valkoiset, Blacks = Mustat.
|
||||
- Sharpening = Terävöinti, Noise Reduction = Kohinanpoisto, Grain = Rakeisuus,
|
||||
Vignette = Vinjetti, Color Grading = Väriluokittelu, Tone Curve = Sävykäyrä,
|
||||
Defringe = Värivirheen korjaus, Feather = Häivytys.
|
||||
- Crop = Rajaus, Straighten = Suorista, Mask = Maski, Brush = Sivellin,
|
||||
Radial = Säteittäinen, Linear Gradient = Lineaarinen liukuväri,
|
||||
Soft Proofing = Vedoksen esikatselu.
|
||||
- Spot removal: Spot = Täplä, Heal = Paranna, Clone = Kloonaa. Point Color = Pistevärit and
|
||||
Point Curve = Pistekäyrä keep ”piste”.
|
||||
- Flag = Lippu, Pick = Valitse (picked photo = Valittu kuva), Reject = Hylätty, Rating = Luokitus (Star rating =
|
||||
Tähtiluokitus), Label = Merkintä, Color Label = Värimerkintä, Keyword = Avainsana,
|
||||
Smart Album = Älyalbumi, Filmstrip = Filminauha, Virtual Copy = Virtuaalikopio,
|
||||
Stack = Pino, Template = Mallipohja (AI Model = Malli), Detection = Havaitseminen, Recently Deleted = Äskettäin poistetut, My Photos = Omat kuvat.
|
||||
- Päivämäärä on muodossa `20.9.2026` (ilman etunollia), viikonpäivä alkaa pienellä kirjaimella
|
||||
(`sunnuntai 20.9.2026`), kuukausi–vuosi on `9/2026` ja kellonaika 24-tuntinen `16:04:05`.
|
||||
- Yksiköt: `kt`, `Mt`, `Gt`; prosentin edessä on väli (`50 %`).
|
||||
- Määrät kirjoitetaan sanan jälkeen (”Tuotuja kuvia: 22”, ”Odottavia kuvia: 2”), koska
|
||||
suomessa lukusanan jälkeinen substantiivi on yksikön partitiivissa (1 kuva, 2 kuvaa), eikä
|
||||
englannin monikkopääte `s` voi valita muotoa. Englannin pääte kulutetaan `{:.0}`-merkinnällä.
|
||||
Kun lause taipuu yksikössä kaikille luvuille (”{n} kuvalta”, ”{n} kuvassa”), luku voi olla
|
||||
lauseen sisällä.
|
||||
- Mukana toimitettu Inter-fontti sisältää ä, ö ja å sekä lainausmerkit ” ”. Lisäfonttia ei
|
||||
tarvita.
|
||||
|
||||
Mitä uutta -ikkunan versiotiedotteet jäävät englanniksi, kuten useimmissa luetteloissa. Alemman
|
||||
tason tekniset virheilmoitukset ja osa käännöskerroksen ulkopuolisista viesteistä jäävät
|
||||
englanniksi. Käännös ei muuta kirjastotietoja eikä englanninkielisiä lähdemerkkijonoja.
|
||||
|
||||
## Testaus
|
||||
|
||||
`i18n::tests` tarkistaa komennon rekisteröinnin, vaihdon valikosta ja ohjauskanavasta,
|
||||
kielen tallennuksen, luetteloiden kattavuuden, paikkamerkit, määrämuodot, päivämäärän, ajan ja
|
||||
suomen kirjaimet. Rust tarkistaa muotoilumallit käännösvaiheessa.
|
||||
|
||||
Kielentarkistuksessa kannattaa katsoa erityisesti: ”Väriluokittelu” (Color Grading),
|
||||
”Valoalueet / Valkoiset” (Highlights / Whites), ”Näyttövedos” (Soft Proofing),
|
||||
”Älyalbumi”, ”Pino” (Stack), ”Pistevärit” (Point Color), ”Tekoälykohinanpoisto” sekä
|
||||
luovien esiasetusten nimet. Kielen lisäämisen ohje on tiedostossa
|
||||
[localization.md](localization.md).
|
||||
@@ -0,0 +1,63 @@
|
||||
# LightCraft in Indonesian
|
||||
|
||||
Choose **Edit → Language → Bahasa Indonesia** or **Settings → General → Language**. The choice applies
|
||||
at once and is saved for the next launch (`language: "id"` in `ui.json`); the control command is
|
||||
`app.language.indonesian`. `LIGHTCRAFT_LANGUAGE` selects it for headless runs and accepts `id`, `id-ID`
|
||||
and `id_ID.UTF-8`; the saved value is always `id`. LightCraft does not follow the system language, for
|
||||
Indonesian as for every other language.
|
||||
|
||||
The Indonesian catalogs cover every plain and formats message the other catalogs have (2,258 + 297),
|
||||
plus 69 command labels no catalog translated yet: menus, shortcuts, library, editing, crop, masks,
|
||||
people, import/export, print, slideshow, settings, built-in presets and profiles, history, date
|
||||
headings, status messages and the smart-album rule editor. The embedded release notes (What's New)
|
||||
stay in English. Technical errors from lower layers stay in English, as in the other languages.
|
||||
The catalogs were written with an agent from the meaning of the English labels and reviewed for
|
||||
natural, standard app Indonesian; 356 labels reuse the Indonesian wording of the sibling apps
|
||||
(PhotoCraft, VectorCraft, FilmCraft, FigCraft) so the suite reads the same. A native speaker's pass
|
||||
is welcome.
|
||||
|
||||
File names, command ids, template placeholders and albums, presets or metadata you named yourself are
|
||||
not translated.
|
||||
|
||||
## Style
|
||||
|
||||
- Sentence case everywhere, as in the English; names, brands and abbreviations keep their capitals.
|
||||
- Buttons and menu commands use the bare verb (Simpan, Hapus, Ekspor, Pangkas); progress lines use
|
||||
me- with an ellipsis (Mengimpor…); finished-state toasts put the subject first (`{n} foto diekspor`).
|
||||
- The user is addressed as *Anda*, only where a sentence needs a subject; never *kamu*.
|
||||
- Indonesian keeps a noun unchanged after a count (`3 foto`), so English plural endings are dropped
|
||||
with `{:.0}`.
|
||||
- Dates read `Minggu, 20/09/2026` (`{weekday}, {day:02}/{month:02}/{year}`), months `09/2026`.
|
||||
- Delete is **Hapus**; remove from a container (album, stack, favorites) is **Keluarkan dari …**.
|
||||
Select is **Pilih**; the Pick flag is **Pilihan**.
|
||||
- `▸` and `▾` are written `→` and `⌄`, as in the other catalogs.
|
||||
|
||||
## Terminology
|
||||
|
||||
| Source | Indonesian |
|
||||
|---|---|
|
||||
| Develop | Olah |
|
||||
| File / Edit / View | File / Edit / Tampilan |
|
||||
| Settings / General / Language | Pengaturan / Umum / Bahasa |
|
||||
| Import / Export | Impor / Ekspor |
|
||||
| Photos / Albums / Folders | Foto / Album / Folder |
|
||||
| Smart Album | Album pintar |
|
||||
| Crop | Pangkas |
|
||||
| Masks / Masking | Masker |
|
||||
| Exposure / Contrast | Eksposur / Kontras |
|
||||
| Highlights / Shadows / Whites / Blacks | Sorotan / Bayangan / Putih / Hitam |
|
||||
| Temperature / Tint | Suhu / Rona |
|
||||
| Vibrance / Saturation | Vibrance / Saturasi |
|
||||
| Clarity / Texture / Dehaze | Kejernihan / Tekstur / Hilangkan kabut |
|
||||
| Sharpening / Noise Reduction | Penajaman / Pengurangan noise |
|
||||
| Vignette / Grain | Vinyet / Butiran |
|
||||
| White Balance / Tone Curve | Keseimbangan putih / Kurva tone |
|
||||
| Lens Corrections | Koreksi lensa |
|
||||
| Presets / Profile | Preset / Profil |
|
||||
| Keywords / Rating | Kata kunci / Rating |
|
||||
| Flag / Pick / Reject | Tanda / Pilihan / Tolak |
|
||||
| Stack / Virtual Copy | Tumpukan / Salinan virtual |
|
||||
| History / Recently Deleted | Riwayat / Baru dihapus |
|
||||
| Compare / Before / After | Bandingkan / Sebelum / Sesudah |
|
||||
| Slideshow / Print / Template | Tayangan slide / Cetak / Templat |
|
||||
| People | Orang |
|
||||
@@ -23,8 +23,10 @@ interface never uses Mtavruli (a test checks the whole catalog). Commands are ve
|
||||
are count-neutral, noun first (`იმპორტირებულია ფოტო: 3`); English plural endings are dropped with
|
||||
`{:.0}`. Dates read `კვირა, 20.09.2026` (`{weekday}, {day:02}.{month:02}.{year}`), months `09.2026`.
|
||||
|
||||
Inter has no Georgian letters and craft-fonts has no Georgian face yet, so Georgian text shows boxes
|
||||
until a Georgian face is available to the UI.
|
||||
Inter has no Georgian letters, so the UI draws Georgian with craft-fonts' Noto Sans Georgian (`Geor`),
|
||||
like the Thai and Arabic faces: desktop release builds embed it, and a web build made with
|
||||
`CRAFT_FONTS_DIR` keeps it in the WASM (`crates/engine/build.rs`). The release web build doesn't use
|
||||
craft-fonts yet, and a build without `CRAFT_FONTS_DIR` has no Georgian face and shows boxes.
|
||||
|
||||
## Terminology
|
||||
|
||||
|
||||
+21
-10
@@ -1,12 +1,12 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (de, es, fr, ja, uk catalogs filled; re-measured; Italian and Georgian interface catalogs added) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-20 · **Change:** minor (German form of address: *du*; Indonesian and Finnish interface catalogs added; Italian: every interface string translated, native-speaker corrections; Vietnamese shipped; previously minor: de, es, fr, ja, uk catalogs filled; re-measured; Italian and Georgian interface catalogs added) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
|
||||
How far each interface language is from `full`. How to add or maintain a language is in
|
||||
[localization.md](localization.md); per-language notes are in `localization-<code>.md`
|
||||
([zh-hans](localization-zh-hans.md), [zh-hant](localization-zh-hant.md), [ja](localization-ja.md),
|
||||
[pt-br](localization-pt-br.md), [es](localization-es.md), [de](localization-de.md),
|
||||
[ru](localization-ru.md), [ka](localization-ka.md)).
|
||||
[ru](localization-ru.md), [fi](localization-fi.md), [ka](localization-ka.md), [id](localization-id.md), [vi](localization-vi.md)).
|
||||
|
||||
## The target
|
||||
|
||||
@@ -18,12 +18,12 @@ go beyond the target.
|
||||
|
||||
## How the numbers are measured
|
||||
|
||||
**Measured** (a Python count over `crates/ui-egui/locales/*.json`, 2026-10-10):
|
||||
**Measured** (a Python count over `crates/ui-egui/locales/*.json`, 2026-10-13):
|
||||
|
||||
- The denominator is every message any catalog knows: the union of the plain catalogs' keys (2,074
|
||||
- The denominator is every message any catalog knows: the union of the plain catalogs' keys (2,222
|
||||
English source strings: menus, commands, controls, panels, dialogs, tooltips, presets, What's New
|
||||
lines) plus the 230 messages of the formats catalogs (`*-formats.json`: text with runtime values and
|
||||
date patterns), 2,304 messages in all.
|
||||
lines) plus the 249 messages of the formats catalogs (`*-formats.json`: text with runtime values and
|
||||
date patterns), 2,471 messages in all.
|
||||
- A message counts as translated when the catalog has a non-empty value that differs from the English
|
||||
key. Some identical values are correct (`ISO`, `OK` in German), so the figure slightly understates
|
||||
coverage; on the other hand, strings that never reach a catalog (technical errors raised in lower
|
||||
@@ -41,6 +41,10 @@ Native-speaker review: no catalog has had a formal review. Several catalogs have
|
||||
contributors whose commits only touch that language (ja, zh-hans, zh-hant, fr, de, ru, uk, pt-br);
|
||||
that is promising but is not recorded as review, so the column says `no (community edits)`.
|
||||
|
||||
Form of address: German speaks to the user with *du* ("Wähle ein Foto", "Dein Name"), never *Sie*.
|
||||
LightCraft is built by its community, and *du* is how people helping each other talk. A new German
|
||||
string follows that.
|
||||
|
||||
## The twelve key languages
|
||||
|
||||
| # | Language | Code | UI strings translated | Dialogs / tooltips / help | Script support | Native review | Status | To `full` |
|
||||
@@ -52,11 +56,11 @@ that is promising but is not recorded as review, so the column says `no (communi
|
||||
| 5 | Arabic | `ar` | 0 (0%) | none | **RTL layout and Arabic shaping missing** | no | none (target: not shipped) | 60–110 h (RTL mirroring 30–50 h + shaping, shared with Hindi) |
|
||||
| 6 | French | `fr` | 2,315 / 2,398 (97%; 2026-10-12, the rest are words spelled the same) | as zh-hans | Latin | no (community edits) | partial | 2–4 h (native review, lower-layer errors) |
|
||||
| 7 | Portuguese (Brazil) | `pt-br` | 1,966 / 2,304 (85%) | as zh-hans | Latin | no (community edits) | partial | 6–10 h |
|
||||
| 8 | Indonesian | `id` | 0 (0%) | none | Latin (Inter covers it) | no | none (target: not shipped) | 10–16 h |
|
||||
| 8 | Indonesian | `id` | 2,555 / 2,555 (100%; 2026-10-19: every plain and formats message any catalog has, plus 69 command labels; What's New left in English) | as zh-hans | Latin (Inter covers it) | no (written with an agent; native-speaker pass pending) | partial (target: not shipped) | 2–4 h (native review, lower-layer errors) |
|
||||
| 9 | Japanese | `ja` | 2,383 / 2,398 (99%; 2026-10-12) | as zh-hans; vertical export watermarks | Jpan via craft-fonts (BIZ UDPGothic / UDMincho); IME through winit | no (community edits) | partial | 2–4 h (native review, lower-layer errors) |
|
||||
| 10 | German | `de` | 2,338 / 2,398 (97%; 2026-10-12, the rest are words spelled the same) | as zh-hans; What's New translated line by line | Latin | no (community edits) | partial | 2–4 h (native review, lower-layer errors) |
|
||||
| 10 | German | `de` | 3,644 / 3,754 (97%; 2026-10-16: every interface message and every What's New line except 59: the distinct What's New lines whose English text has a ▸ menu path have no German entry yet and wait for #2016; the rest are words spelled the same) | as zh-hans; What's New translated line by line | Latin | no (community edits) | partial | 2–4 h (native review, lower-layer errors) |
|
||||
| 11 | Korean | `ko` | 0 (0%) | none | **no Korean (Hangul) face**: needs a non-Adobe OFL face in craft-fonts (Noto CJK is allowed only for Chinese) | no | none | 14–22 h |
|
||||
| 12 | Vietnamese | `vi` | 0 (0%) | none | Latin with stacked diacritics (Inter covers it; check combining marks) | no | none (target: not shipped) | 10–16 h |
|
||||
| 12 | Vietnamese | `vi` | 2,471 / 2,471 (100% key coverage of current union; intentional English like `ISO`/`OK` kept) | menus, panels, dialogs, presets, profiles, dates, formats; lower-layer errors English | Latin with stacked diacritics (Inter) | no (community contribution) | partial (catalog complete; lower-layer errors English) | 1–3 h |
|
||||
|
||||
## Other languages LightCraft ships (4)
|
||||
|
||||
@@ -65,8 +69,9 @@ that is promising but is not recorded as review, so the column says `no (communi
|
||||
| Traditional Chinese (Taiwan) | `zh-hant` | 1,993 / 2,304 (87%) | Hant; uses the Simplified Chinese face until craft-fonts has a TC face | no (community edits) | partial | 8–14 h (incl. the font) |
|
||||
| Russian | `ru` | 1,704 / 2,304 (74%) | Cyrillic (Inter) | no (community edits) | partial | 8–12 h |
|
||||
| Ukrainian | `uk` | 2,383 / 2,398 (99%; 2026-10-12) | Cyrillic (Inter) | no (community edits) | partial (lower-layer errors English) | 2–4 h |
|
||||
| Italian | `it` | 2,201 / 2,398 (92%, measured 2026-10-12; the rest is What's New) | Latin (Inter) | no (written with an agent; native-speaker pass pending) | partial (lower-layer errors English) | 3–5 h |
|
||||
| Italian | `it` | 2,628 / 3,278 (80%, measured 2026-10-15; every interface string is translated, the other 650 are What's New lines, left in English) | Latin (Inter) | partly (written with an agent; corrections and the 2026-10-15 additions by a native speaker) | partial (lower-layer errors English) | 3–5 h |
|
||||
| Georgian | `ka` | 2,398 / 2,398 (100%: every plain and formats message any catalog has on 2026-10-12, plus the 27 display labels and 397 What's New lines no other catalog translates) | Geor: no craft-fonts face yet (boxes until one is available) | yes (native speaker) | partial (lower-layer errors English) | 2–4 h (+ a Georgian face) |
|
||||
| Finnish | `fi` | 2,610 / 3,308 (79%, measured 2026-10-17: 2,313 plain + 297 formats; the rest is What's New) | Latin (Inter) | yes (written with an agent; native-speaker review by @roopeg) | partial (lower-layer errors English) | 3–5 h |
|
||||
|
||||
Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bokmål, Thai
|
||||
(Thai needs shaping and line breaking without spaces). Each Latin-script one is ~10–16 h; Thai
|
||||
@@ -93,8 +98,14 @@ Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bok
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-19 | minor | Indonesian interface catalog (`id`): every plain and formats message any catalog has, plus 69 command labels; What's New left in English |
|
||||
| 2026-10-17 | minor | Finnish interface catalog (`fi`): 2,610 of 3,308 messages (2,313 plain + 297 formats), What's New left in English |
|
||||
| 2026-10-15 | trivial | Italian (`it`): 339 interface messages and 60 format strings that were missing or still in English translated (2,628 of 3,278; only What's New left in English); native-speaker corrections: Exposure → *Esposizione*, Sharpening → *Nitidezza*, Preset → *Preset*, blurry → *sfocate* |
|
||||
| 2026-10-13 | minor | Remeasured denominator (2,222 plain + 249 formats = 2,471); Vietnamese catalog complete at 2,471/2,471 (status partial: lower-layer errors still English) |
|
||||
| 2026-10-12 | minor | Italian interface catalog (`it`): 2,201 of 2,398 messages, What's New left in English |
|
||||
| 2026-10-11 | minor | Shared work: the interface decides on error kinds, not English error text (#1046) |
|
||||
| 2026-10-12 | minor | Georgian (`ka`) row: every catalog message |
|
||||
| 2026-10-10 | major | Created: per-language counts measured from the catalogs, the twelve key languages, script support, estimates |
|
||||
| 2026-10-12 | minor | de, es, fr, ja and uk catalogs filled to every message another catalog knows (agent translations, native review welcome); those rows re-measured against the current 2,398 messages. Other rows keep their 2026-10-10 counts (open PRs for pt-BR, ru, zh) |
|
||||
| 2026-10-16 | minor | German (`de`): 217 interface strings and 862 What's New lines added (agent translations, native review welcome); re-measured 2,572 → 3,644 of 3,754 messages (80% → 97%). The denominator grew because German now translates What's New lines no other catalog has; other rows keep their earlier counts |
|
||||
| 2026-10-20 | minor | German (`de`) speaks to the user with *du*; the 11 messages that used *Sie* changed, no count changes |
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# LightCraft bằng tiếng Việt
|
||||
|
||||
Chọn **Chỉnh sửa → Ngôn ngữ → Tiếng Việt** hoặc **Cài đặt → Chung → Ngôn ngữ**.
|
||||
Lựa chọn áp dụng ngay và được lưu cho lần mở sau (`language: "vi"` trong `ui.json`).
|
||||
|
||||
Các tag locale hệ thống sau cũng chọn tiếng Việt: `vi`, `vi-VN`, `vi_VN`, `vi_VN.UTF-8`.
|
||||
|
||||
## Phạm vi
|
||||
|
||||
Catalog tiếng Việt phủ **toàn bộ** bề mặt localization hiện tại của LightCraft
|
||||
(đo 2026-10-13: union plain **2 222** + formats **249** = **2 471** thông điệp):
|
||||
|
||||
- `crates/ui-egui/locales/vi.json` — thông điệp plain / nhãn hiển thị (2 222 khóa)
|
||||
- `crates/ui-egui/locales/vi-formats.json` — thông điệp có giá trị runtime và mẫu ngày (249 khóa)
|
||||
|
||||
Điều này là **100% bề mặt localizable/runtime hiện tại**, không phải “mọi chuỗi tiếng Anh
|
||||
trong toàn bộ repository”. Lỗi kỹ thuật từ crate tầng dưới vẫn có thể là tiếng Anh, giống
|
||||
các ngôn ngữ khác.
|
||||
|
||||
## Quy ước thuật ngữ
|
||||
|
||||
| English | Vietnamese |
|
||||
|---|---|
|
||||
| File / Folder | Tệp / Thư mục |
|
||||
| Library / Album / Smart Album | Thư viện / Album / Album thông minh |
|
||||
| Import / Export | Nhập / Xuất |
|
||||
| Exposure / Contrast | Phơi sáng / Tương phản |
|
||||
| Highlights / Shadows / Whites / Blacks | Vùng sáng / Vùng tối / Vùng trắng / Vùng đen |
|
||||
| White Balance / Temperature / Tint | Cân bằng trắng / Nhiệt độ / Tông |
|
||||
| Vibrance / Saturation | Độ rực rỡ / Độ bão hòa |
|
||||
| Texture / Clarity / Dehaze / Grain / Vignette | Kết cấu / Độ rõ / Khử sương mù / Hạt / Tối góc |
|
||||
| Tone Curve | Đường cong tông màu |
|
||||
| Sharpening / Noise Reduction / AI Denoise | Làm nét / Giảm nhiễu / Khử nhiễu AI |
|
||||
| Masking / Brush / Linear Gradient | Mặt nạ / Cọ / Chuyển sắc tuyến tính |
|
||||
| Preset / Profile / History / Version | Thiết lập sẵn / Hồ sơ / Lịch sử / Phiên bản |
|
||||
| Appearance (theme) | Giao diện |
|
||||
| Subject (AI) | Chủ thể |
|
||||
| Candidate / Select | Ảnh ứng viên / Chọn |
|
||||
| Flag / Picked / Rejected / Unflagged | Gắn cờ / Đã chọn / Đã loại / Không gắn cờ |
|
||||
| Shutter Speed | Tốc độ màn trập |
|
||||
| Smart Previews | Xem trước thông minh |
|
||||
|
||||
Tên sản phẩm và chuẩn giữ nguyên khi là định danh: LightCraft, ArtCraft, GitHub, Discord,
|
||||
JPEG, PNG, TIFF, WebP, AVIF, HEIF, HEIC, RAW, DNG, RGB, HDR, ICC, EXIF, XMP, GPU, MCP, SAM, ISO, OK.
|
||||
|
||||
Tên album / preset / profile do người dùng tạo, tên tệp, command ID và metadata nhập tay
|
||||
không được dịch.
|
||||
|
||||
## Ngày và số lượng
|
||||
|
||||
- Ngày đầy đủ: `{weekday}, {day}/{month}/{year}` (ví dụ `Chủ Nhật, 20/9/2026`)
|
||||
- Tháng/năm: `Tháng {month} năm {year}`
|
||||
- Thứ viết tắt trên date picker: T2…T7, CN
|
||||
- Thông điệp đếm dùng dạng trung tính (`22 ảnh`), không tạo số ít/số nhiều nhân tạo như tiếng Anh
|
||||
|
||||
## Font
|
||||
|
||||
Tiếng Việt dùng Inter (Latin + dấu). Không cần craft-fonts CJK. Kiểm tra glyph trong
|
||||
`vietnamese_panels_dialogs_and_diacritics_paint_with_bundled_fonts`.
|
||||
|
||||
## Kiểm tra
|
||||
|
||||
```sh
|
||||
cargo test -p lightcraft-ui-egui i18n::tests -- --nocapture
|
||||
LIGHTCRAFT_LANGUAGE=vi lightcraft-cli snapshot --demo -o vi.png --size 1600x1000
|
||||
LIGHTCRAFT_LANGUAGE=vi lightcraft-cli snapshot --demo -o vi-narrow.png --size 1280x800
|
||||
```
|
||||
|
||||
Các test tiếng Việt bắt buộc phủ catalog union, nhãn lệnh/control/Settings/Smart Album,
|
||||
đổi ngôn ngữ, lưu `vi`, formats, ngày, dữ liệu người dùng, và glyph dấu.
|
||||
|
||||
## Nguồn dịch (clean-room)
|
||||
|
||||
Dịch từ chuỗi tiếng Anh nguồn của LightCraft. PhotoCraft / VectorCraft / PDFCraft chỉ dùng
|
||||
làm bộ nhớ thuật ngữ khi cùng nguồn tiếng Anh và cùng ngữ cảnh UI. Không dùng gói ngôn ngữ
|
||||
Adobe Lightroom hay tài nguyên độc quyền.
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
The interface ships in the language it is written in (English) plus every language in the table in
|
||||
`crates/ui-egui/src/i18n.rs`. Today that is English, Simplified Chinese (`zh-hans`), Traditional
|
||||
Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`), Russian (`ru`), French (`fr`), Ukrainian (`uk`), Czech (`cs`) and Georgian (`ka`). This file is the reference for **adding or maintaining a language**; the per-language notes
|
||||
Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`), Russian (`ru`), French (`fr`), Ukrainian (`uk`), Czech (`cs`), Georgian (`ka`), Swedish (`sv`), Finnish (`fi`), Indonesian (`id`) and Vietnamese (`vi`). This file is the reference for **adding or maintaining a language**; the per-language notes
|
||||
are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-hant.md`](localization-zh-hant.md),
|
||||
[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md), [`localization-es.md`](localization-es.md),
|
||||
[`localization-de.md`](localization-de.md), [`localization-ru.md`](localization-ru.md),
|
||||
[`localization-cs.md`](localization-cs.md) and [`localization-ka.md`](localization-ka.md).
|
||||
[`localization-cs.md`](localization-cs.md), [`localization-fi.md`](localization-fi.md), [`localization-ka.md`](localization-ka.md), [`localization-id.md`](localization-id.md) and [`localization-vi.md`](localization-vi.md).
|
||||
|
||||
## Adding a language
|
||||
|
||||
@@ -34,6 +34,8 @@ are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-h
|
||||
Cs, "cs", "Čeština", "Latn", include_str!("../locales/cs.json");
|
||||
It, "it", "Italiano", "Latn", include_str!("../locales/it.json");
|
||||
Ka, "ka", "ქართული", "Geor", include_str!("../locales/ka.json");
|
||||
Fi, "fi", "Suomi", "Latn", include_str!("../locales/fi.json");
|
||||
Vi, "vi", "Tiếng Việt", "Latn", include_str!("../locales/vi.json");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+6
-3
@@ -146,8 +146,11 @@ Each edit in a batch has its own undo step. Connect mode also lists `ui_inspect`
|
||||
Ports and connect mode are unchanged.
|
||||
|
||||
Every listed tool has a title and all four MCP hints. Generated command tools are conservatively
|
||||
marked as edits. Helpers with optional output paths are marked as writers; `export` is not
|
||||
idempotent because the default conflict policy chooses a new filename on repeated calls.
|
||||
marked as edits. `export`, `render_photo` and `screenshot` have `readOnlyHint: false` and
|
||||
`destructiveHint: true`: their caller-supplied output paths can replace existing files, even
|
||||
when a path is optional. The image-only `render_preview` and `ui_screenshot` remain read-only
|
||||
and non-destructive. These annotations are client hints, not a file-access sandbox.
|
||||
`export` is not idempotent because the default conflict policy chooses a new filename on repeated calls.
|
||||
Unknown helper argument keys return JSON-RPC `-32602` naming the key and accepted arguments.
|
||||
Generated `cmd_*` tools and nested command params remain free-form: the registry has prose
|
||||
parameter docs, not machine-readable schemas. Existing command validation (including strict
|
||||
@@ -176,7 +179,7 @@ per-request `_meta` receive `resultType: complete` and list/read cache hints; re
|
||||
| `apply_preset {preset, amount?, ids?}` | Apply a preset (ids from `cmd_presets_list`) |
|
||||
| `crop {id?, rect?, angle?, reset?}` | Normalized crop rect `[x0,y0,x1,y1]` and straighten angle; at least one of `rect`, `angle`, `reset: true`. The crop always ends inside the photo: an `angle` alone keeps the crop if it still fits, else shrinks it to the largest of its aspect that does |
|
||||
| `render_photo {id?, size?, format?, path?}` | Render with current settings → **image content** (PNG, or JPEG with `format: "jpeg"`), long edge `size` (default 1024) |
|
||||
| `export {path \| dir, id? \| ids?, format?, longEdge? \| shortEdge? \| width?/height? \| megapixels? \| percent?, dontEnlarge?, ppi?, quality?, colorSpace?, bitDepth?, …}` | Full-quality render to `.png` / `.jpg` / `.tif` / `.webp` / `.avif` / `.jxl` (`jxlLossless`, `jxlEffort`, `jxlProgressive`); `format: "original"` copies the file + an XMP sidecar with the edits, `format: "dng"` writes raw photos as DNG with the edits embedded. No size param = 3000 px long edge; `longEdge: 0` = full size (cropped, native resolution); `width` + `height` fit either orientation; `dontEnlarge` defaults to true |
|
||||
| `export {path \| dir, id? \| ids?, format?, longEdge? \| shortEdge? \| width?/height? \| megapixels? \| percent?, dontEnlarge?, ppi?, quality?, colorSpace?, bitDepth?, …}` | Full-quality render to `.png` / `.jpg` / `.tif` / `.webp` / `.avif` / `.jxl` (`jxlLossless`, `jxlEffort`, `jxlProgressive`); `format: "original"` copies the file + an XMP sidecar with the edits, `format: "dng"` writes raw photos as DNG with the edits embedded. No size param = 3000 px long edge; `longEdge: 0` = full size (cropped, native resolution); `width` + `height` fit either orientation (Lightroom's Dimensions); with `fit: "widthHeight"` they limit the width and the height as the photo stands (Width & Height); `dontEnlarge` defaults to true |
|
||||
|
||||
`get_develop {id}` and `render_photo {id}` use that photo without changing the selection.
|
||||
`set_develop`, `crop` and `export` with a supplied photo `id` end the current edit interaction,
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ All algorithms are our own implementations from published papers (cited in the m
|
||||
|
||||
**HDR** (`hdr.rs`): frames ordered by EXIF exposure (or median luminance); the middle one is the
|
||||
reference. Auto Align: features on exposure-normalised luminance, RANSAC translation / similarity /
|
||||
homography (the simplest model that explains ≥ 97 % of the matches). Exposure ratios are measured
|
||||
homography (the simplest model that explains ≥ 97 % of the matches). Frames are resampled with Lanczos-3, held to
|
||||
the range of their 4 × 4 neighbours so edges don't ring (`lightcraft_raster::sample`); bilinear lost a third of the
|
||||
finest detail. Exposure ratios are measured
|
||||
(median log ratio of well-exposed pixels between brightness neighbours), EXIF is the fallback.
|
||||
Radiance is a weighted mean in linear light, weight `k · (1 − smoothstep(0.8·clip, 0.96·clip, max
|
||||
channel within 1 px))`: a pixel next to clipped photosites was demosaiced from them and reads low in the clipped
|
||||
|
||||
+95
-55
File diff suppressed because one or more lines are too long
@@ -22,6 +22,11 @@ stronger Light sliders, issue #196) without changing a single photo someone has
|
||||
- Changes to how a file is decoded or which camera colour it gets change the source, not how settings
|
||||
are interpreted; the process doesn't reach that stage today.
|
||||
|
||||
**Neutral display tone map** (JPEG, PNG and other display-referred sources with Contrast, Whites and
|
||||
Blacks at 0) is the identity up to white: the SDR shoulder no longer pulls 1.0 down to 0.9875 (8-bit
|
||||
255 to 254, issue #1945). It landed in place as a bug fix, since the tone map promised identity at
|
||||
neutral settings; edited curves keep the shoulder and their V1 look.
|
||||
|
||||
## Adding a process
|
||||
|
||||
1. Add a variant to `Process` (`V2`), append it to `Process::ALL` (oldest first), give it its number in
|
||||
|
||||
+35
-18
@@ -1,6 +1,6 @@
|
||||
# RAW parity: cameras, formats, demosaic, colour and lens profiles
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (integration batch 36: 515 models verified after the CC0 per-model checks; Epson ERF and Mamiya MEF decode; measured Nikon image areas) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** minor (Minolta MRW decoded, 6 bodies verified; lossy JPEG XL DNG tiles with a non-linear declared transfer curve decode; Canon ColorData Kelvin; X-Trans CMOS 4 bodies take the X-T3's spectral matrices) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7)
|
||||
|
||||
Raw processing is the deepest single dimension of Lightroom parity: a photographer whose camera
|
||||
does not decode, or decodes with the wrong colour, cannot switch. This document measures it model by
|
||||
@@ -15,14 +15,14 @@ ranked in [gaps.md](gaps.md). Details per format: [cr3.md](cr3.md),
|
||||
| Measure | Value | Kind |
|
||||
|---|---:|---|
|
||||
| Adobe-listed camera models | **1,446** | measured: [raw/adobe-cameras.tsv](raw/adobe-cameras.tsv) |
|
||||
| Decoded from sensor data and **verified** on a real file | **545** (38%) | measured: [raw/evidence.tsv](raw/evidence.tsv) |
|
||||
| Decoder path should handle it, **unverified** | **651** (45%) | measured from the format rules (631 of them DNG, mostly phones) |
|
||||
| **Preview-only** (embedded JPEG) | **212** (15%) | measured |
|
||||
| Decoded from sensor data and **verified** on a real file | **551** (38%) | measured: [raw/evidence.tsv](raw/evidence.tsv) |
|
||||
| Decoder path should handle it, **unverified** | **656** (45%) | measured from the format rules (631 of them DNG, mostly phones) |
|
||||
| **Preview-only** (embedded JPEG) | **201** (14%) | measured |
|
||||
| **Unsupported** (not imported, refused or decodes wrongly) | **38** (3%) | measured |
|
||||
| Models added since 2017 (Camera Raw 10+): verified / unverified / preview / unsupported | 182 / 542 / 22 / 8 of 754 | measured |
|
||||
| **Share of photographers whose camera fully works** (decodes from sensor data), use-weighted | **~94%** | estimated: measured classes × written usage weights × per-format confidence |
|
||||
| …of which on a body we have verified | **~85%** | estimated, same weights |
|
||||
| Camera colour: Adobe profiles vs ours | 1,500 Adobe Standard profiles, 481 models with Camera Matching profiles vs **5 bundled profiles** + per-file fits + 52 spectral matrices | measured (bundle listing, Adobe list, `assets/camera-profiles`) |
|
||||
| Camera colour: Adobe profiles vs ours | 1,500 Adobe Standard profiles, 481 models with Camera Matching profiles vs **5 bundled profiles** + per-file fits + 52 spectral matrices (plus same-sensor aliases) | measured (bundle listing, Adobe list, `assets/camera-profiles`) |
|
||||
| Lens profiles: Adobe vs ours | **3,635 vs 0** (in-file corrections only) | measured |
|
||||
| **RAW readiness** (decoding 60%, colour 25%, lens 15%) | **~60%** (range 54–64%) | estimated, see [Scores](#scores) |
|
||||
|
||||
@@ -31,6 +31,13 @@ model from the format rules in the script (which mirror `crates/raw/src`) plus t
|
||||
rewrites [raw/coverage.tsv](raw/coverage.tsv) and prints the tables below; `--check` fails when the
|
||||
committed table is stale.
|
||||
|
||||
Folder discovery, desktop file pickers, CLI and MCP share
|
||||
`lightcraft_engine::import::EXTENSIONS`. The list includes formats that may open only from an
|
||||
embedded preview or report a decoder error; an extension is not a promise of sensor decoding.
|
||||
The 2026-10-12 release CLI check of the public DSC-R1 SR2 and DSC-F828 SRF samples found the
|
||||
same photos and decode status through recursive and direct import. DSC-F828 moves from
|
||||
unsupported to preview-only in the counts; sensor-decoding readiness is unchanged.
|
||||
|
||||
## How it was measured
|
||||
|
||||
- **Adobe's list**: helpx "Camera Raw supported cameras" (the live page refuses scripted requests, so
|
||||
@@ -78,7 +85,7 @@ committed table is stale.
|
||||
|
||||
| Maker | Adobe models | verified | unverified | preview | unsupported | Main reason for preview / unsupported |
|
||||
|---|---:|---:|---:|---:|---:|---|
|
||||
| Sony | 149 | 94 | 43 | 9 | 3 | DSLR-A200…A390 and A850 12-bit compressed ARW (preview, #535); A100 preview-only and DSC-F828 refused (#702 fixed the regression; their raws aren't decoded); SRF not imported |
|
||||
| Sony | 149 | 94 | 43 | 10 | 2 | DSLR-A200…A390 and A850 12-bit compressed ARW (preview, #535); A100 remains classified unsupported in the recorded evidence; DSC-F828 SRF imports as preview-only; DSC-V3 preview not verified |
|
||||
| Canon | 134 | 95 | 16 | 21 | 2 | CRW (2000–2004 bodies) preview; EOS-1D / 1Ds TIFF raws not imported |
|
||||
| Samsung | 128 | 10 | 109 | 9 | 0 | compressed SRW (NX1, NX30, NX300, NX500, NX2000, NX3000, NX3300, NX mini, Galaxy NX) |
|
||||
| Apple | 113 | 4 | 109 | 0 | 0 | — (DNG / ProRAW) |
|
||||
@@ -95,8 +102,8 @@ committed table is stale.
|
||||
| Casio | 26 | 0 | 26 | 0 | 0 | — (DNG) |
|
||||
| DJI | 21 | 4 | 17 | 0 | 0 | DNG |
|
||||
| OM Digital Solutions | 7 | 0 | 0 | 7 | 0 | **compressed ORF** |
|
||||
| Kodak, Konica Minolta, Epson, Sigma, GoPro, Contax, others | 56 | 6 | 11 | 24 | 15 | DCR, GPR not imported; KDC, MRW, X3F preview (Epson ERF decodes) |
|
||||
| **All** | **1,446** | **545 (38%)** | **651 (45%)** | **212 (15%)** | **38 (3%)** | |
|
||||
| Kodak, Konica Minolta, Epson, Sigma, GoPro, Contax, others | 56 | 12 | 16 | 13 | 15 | DCR, GPR not imported; KDC, X3F preview (Epson ERF and Minolta MRW decode) |
|
||||
| **All** | **1,446** | **551 (38%)** | **656 (45%)** | **201 (14%)** | **38 (3%)** | |
|
||||
|
||||
## By use (estimated)
|
||||
|
||||
@@ -142,9 +149,10 @@ checked (the per-model CC0 sweeps of October 2026); compressed ORF (OM / Olympus
|
||||
| ARW | packed 12-bit (A900) | ✅ | one CC0 file; A850 unmeasured |
|
||||
| ARW | ARW6 Compressed / HQ (compression 32766; ILCE-7RM6) | ✅ | 8 CC0 files; 12-bit mode unsupported; `docs/arw6-compression.md` |
|
||||
| ARW | 12-bit compressed of DSLR-A200…A390 | ⬜ | preview (#535) |
|
||||
| ARW | A100 ARW; SRF (F828, V3) | ⬜ | refused (#702); SRF not imported |
|
||||
| ARW | A100 ARW | ⬜ | recorded as refused (#702); not rechecked here |
|
||||
| SRF | DSC-F828; DSC-V3 | ⬜ | discovered during folder import; F828 imports as preview-only, V3 has not been checked on a real file |
|
||||
| ARW | ARQ pixel shift | ⬜ | no code |
|
||||
| SR2 | DSC-R1 | ✅ | CC0 3221 sample |
|
||||
| SR2 | DSC-R1 | ✅ | CC0 3221 sample; folder and direct import both decode sensor data |
|
||||
| RAF | uncompressed, lossless and lossy compressed; Bayer and X-Trans; GFX 14/16-bit | ✅ | 24 files from 13 bodies exact |
|
||||
| RAF | pre-X FinePix without a raw IFD | ⬜ | `raf.rs` refuses (26 Adobe models, estimated split) |
|
||||
| ORF | uncompressed 16-bit, 12-bit packed, two-field 12-bit, 12.8 bpp | ✅ | E-1, E-400, XZ-2 corpus; #651 |
|
||||
@@ -155,14 +163,15 @@ checked (the per-model CC0 sweeps of October 2026); compressed ORF (OM / Olympus
|
||||
| SRW | uncompressed, 12-bit packed | ✅ | EX1 corpus; NX10, NX20 |
|
||||
| SRW | compressed 32770 / 32772 / 32773 | ⬜ | refused |
|
||||
| DNG | uncompressed, LJ92, lossy JPEG / Smart Preview, Deflate incl. float, LinearRaw, tiles / strips, row / column interleave | ✅ | 6 corpus DNGs; 4 ILCE-6400 JXL DNGs; 38 Leica Q3 DNGs (lossless JPEG, 14-bit, private check) |
|
||||
| DNG | JPEG XL (DNG 1.7, lossless and lossy) | ✅ | synthetic bit-exact; real lossless and lossy files |
|
||||
| DNG | JPEG XL (DNG 1.7, lossless and lossy) | ✅ | synthetic bit-exact; real lossless and lossy files; lossy tiles keep their declared transfer curve (Lightroom Classic 15.1 LinearRaw DNGs label linear data with the sRGB curve); PQ / HLG tiles preview-only |
|
||||
| DNG | Apple ProRAW (incl. semantic mattes) | ✅ | iPhone 12 Pro corpus; ProfileGainTableMap read but off (ΔE00 19 against Lightroom with it on) |
|
||||
| DNG | phone DNGs (Pixel, Galaxy, Xiaomi…) | 🟡 | Pixel 2 XL corpus; 13 further Apple, Google and Samsung models in the pinned sweep; white-level pile-up fix #548 |
|
||||
| DNG | GoPro GPR (VC-5) | ⬜ | refused |
|
||||
| 3FR / FFF | DNG-like layouts | 🟡 | six Hasselblad models verified on nine files; FFF neutral-matrix colour remains unverified |
|
||||
| 3FR / FFF | DNG-like layouts | 🟡 | six Hasselblad models verified on nine files; read by `vendor::hasselblad`: an FFF has no colour matrix of its own and now takes the camera's from the original 3FR header it embeds in tag `0xC51B` (it used to develop with the neutral matrix); RGGB checked against the camera preview on the CC0 H4D-40 FFF and CFV 3FR; the embedded preview is the uncompressed 8-bit RGB image (no JPEG in these files), handed on as a baseline TIFF |
|
||||
| 3FR / FFF | Hasselblad compression | ⬜ | preview |
|
||||
| ERF | 12-bit in 16-byte blocks (R-D1, R-D1s, R-D1x) | ✅ | 3 CC0 corpus files; crop registered and WB measured against the embedded preview (no WB tag found); `vendor/erf.rs` |
|
||||
| IIQ, MOS, MRW, KDC, X3F | all | ⬜ | preview; an uncompressed MOS, whose IFD0 is the mosaic itself with no Make tag, is recognised by Leaf's data block `0x8606` (#1275) |
|
||||
| MRW | 12-bit packed, MSB first, or 12-bit in big-endian 16-bit words; RGGB and GBRG | ✅ | 4 CC0 corpus files (DiMAGE 7Hi, A1, A200, Dynax 7D) plus DiMAGE 7 and A2 checked against their previews; `vendor/mrw.rs` |
|
||||
| IIQ, MOS, KDC, X3F | all | ⬜ | preview; an uncompressed MOS, whose IFD0 is the mosaic itself with no Make tag, is recognised by Leaf's data block `0x8606` (#1275) |
|
||||
| MEF | uncompressed 12-bit (Mamiya ZD) | ✅ | 1 CC0 corpus file; crop and WB from its uncompressed RGB preview (no JPEG, no WB tag); `vendor/mef.rs` |
|
||||
| DCR, MFW, DXO, GPR, TIFF raws | all | ⬜ | not imported |
|
||||
|
||||
@@ -205,11 +214,11 @@ sample files. A pointer for contributors, not legal advice.
|
||||
|
||||
| Item | Adobe | LightCraft |
|
||||
|---|---|---|
|
||||
| Default profile per model | Adobe Standard for every model (1,500 profile names in the bundle) | the DNG's own matrices; else a pooled profile (**5 bundled**: ILCA-99M2, ILCE-7CR, ILCE-7M4, X-H2S, X-T4); else a guarded per-file fit to the camera's embedded JPEG; else spectral matrices (**52 models**, rawtoaces-data); else a neutral matrix |
|
||||
| Default profile per model | Adobe Standard for every model (1,500 profile names in the bundle) | the DNG's own matrices; else a pooled profile (**5 bundled**: ILCA-99M2, ILCE-7CR, ILCE-7M4, X-H2S, X-T4); else a guarded per-file fit to the camera's embedded JPEG; else spectral matrices (**52 models**, rawtoaces-data, and the bodies sharing their sensors: 6 X-Trans CMOS 4 Fujifilm bodies take the X-T3's); else a neutral matrix |
|
||||
| Camera Matching looks | 481 models (plus phone-maker looks) | none (the per-file JPEG fit imitates the camera's own look) |
|
||||
| Creative profiles | Adobe Color, Portrait, Landscape, Vivid, Monochrome… | our own looks (Color, Neutral, Vivid, Landscape, Portrait, Monochrome) |
|
||||
| DCP / dual-illuminant model | ✅ | dual-illuminant DNG colour (ColorMatrix / ForwardMatrix 1-2, CameraCalibration, AnalogBalance), HueSatMap, LookTable, ProfileToneCurve; external `.dcp` never loaded (clean-room) |
|
||||
| White balance in Kelvin | ✅ for every model | absolute Kelvin where the camera has colour matrices (its own, or one of the 52 spectral rows), also for files whose look is a per-file fit (#730), or where Sony's white-balance presets in the file give the scale (6 bodies, `kelvin_presets`); other raws keep the relative scale (#296) |
|
||||
| White balance in Kelvin | ✅ for every model | absolute Kelvin where the camera has colour matrices (its own, or one of the 52 spectral rows), also for files whose look is a per-file fit (#730), or where the camera's white-balance presets in the file give the scale (6 Sony bodies and the Canon PowerShot G1 X Mark III, `kelvin_presets`); other raws keep the relative scale (#296) |
|
||||
|
||||
**How far the default rendering is from Adobe's** (CIE ΔE; measured where stated):
|
||||
|
||||
@@ -234,7 +243,7 @@ essentials user (the default look is what they keep).
|
||||
| Item | Adobe | LightCraft |
|
||||
|---|---|---|
|
||||
| Profile database | 3,635 profile files, 56 makers, auto-selected | **0** (Adobe LCPs never used; no database of our own) |
|
||||
| In-file corrections | ✅ | DNG opcodes (WarpRectilinear, FixVignetteRadial, GainMap…) applied; Panasonic / Leica RW2 distortion (79 photos, 13 bodies, 11 lenses); Sony tag 0x7037 distortion for the ILCE-7RM4A (2 lenses) and the ILCE-7RM6 (3 lenses, 54 photos; `sony-ilce7rm6-lens-corrections.md`); Nikon NikonNEFInfo distortion (tag `0xc7d5`, about the sensor-shifted optical centre) for the Z 6II and Z f: F-mount lenses on the FTZ within 0.2 px of the camera JPEG, Z lenses within 0.5–1.5 px except the 24-70's wide end (7 px median; `docs/nikon-lens-corrections.md`) |
|
||||
| In-file corrections | ✅ | DNG opcodes (WarpRectilinear, FixVignetteRadial, GainMap…) applied; Panasonic / Leica RW2 distortion (79 photos, 13 bodies, 11 lenses); Sony tag 0x7037 distortion for the ILCE-7RM4A (2 lenses), full-frame Bayer 3:2 ILCE-7CR files with correction Auto or forced Auto (3 lenses; see sony-lens-corrections.md for residuals and limits) and the ILCE-7RM6 (3 lenses, 54 photos; `sony-ilce7rm6-lens-corrections.md`); Nikon NikonNEFInfo distortion (tag `0xc7d5`, about the sensor-shifted optical centre) for the Z 6II and Z f: F-mount lenses on the FTZ within 0.2 px of the camera JPEG, Z lenses within 0.5–1.5 px except the 24-70's wide end (7 px median; `docs/nikon-lens-corrections.md`) |
|
||||
| Vignetting / lateral CA from maker notes | ✅ | ⬜ |
|
||||
| Manual distortion, vignetting, CA, defringe | ✅ | ✅ |
|
||||
|
||||
@@ -269,10 +278,10 @@ works, from [raw/usage-weights.tsv](raw/usage-weights.tsv).
|
||||
| 4 | Nikon packed 14-bit under a compressed tag (Z 6II uncompressed, #769) | +0.2 | 1–2 | ~0.13 | PR #671 exists |
|
||||
| 5 | Sony DSLR-A200…A390 / A850 12-bit compressed ARW | +0.4 | 3–6 | ~0.08 | samples on raw.pixls.us |
|
||||
| 6 | Pre-X FinePix RAF | +0.6 | 4–8 | ~0.08 | samples |
|
||||
| 7 | Sony A100 and DSC-F828 regression (#702) | +0.1 | 1–2 | ~0.07 | samples on raw.pixls.us |
|
||||
| 7 | Sony A100 regression (#702) and DSC-F828 sensor decoding (preview-only) | +0.1 | 1–2 | ~0.07 | samples on raw.pixls.us |
|
||||
| 8 | Canon CRW (2000–2004 bodies) | +1.2 by the uniform tail weight, far less by real use | 6–12 | ≤ 0.1 | samples |
|
||||
| 9 | Nikon HE / HE★ NEF (Z 8, Z 9, Z 6III, Z f, Z50II, Z5II set to HE) | +1.1 | 20–40 | ~0.04 | a pure-Rust JPEG XS decoder |
|
||||
| 10 | Compressed SRW, Hasselblad 3FR, IIQ, X3F, MRW, KDC, DCR, GPR (ERF, MEF done) | +0.6 together | 45–85 | ~0.01 | samples; low use |
|
||||
| 10 | Compressed SRW, Hasselblad 3FR, IIQ, X3F, KDC, DCR, GPR (ERF, MEF, MRW done) | +0.6 together | 45–85 | ~0.01 | samples; low use |
|
||||
|
||||
Beyond decoding:
|
||||
|
||||
@@ -287,6 +296,13 @@ Beyond decoding:
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-18 | patch | X-T30, X-T30 II, X-Pro3, X100V, X-S10 and X-E4 take the X-T3's spectral matrices (same 26.1 MP X-Trans CMOS 4 sensor): an X-T30 raw with a black-and-white (Acros) camera JPEG no longer opens in the muted neutral fallback |
|
||||
| 2026-10-16 | minor | Kelvin white balance from Canon CR2 `ColorData` presets (PowerShot G1 X Mark III) |
|
||||
| 2026-10-15 | minor | DNGs with a look table and no tone curve (Lightroom-written, Adobe Standard) fit their tone to the embedded preview, also Lightroom's 1024 px one: two lossy ILCE-7CM2 DNGs no longer render 0.8 EV dark and flat |
|
||||
| 2026-10-15 | minor | Lossy JPEG XL DNG tiles that declare a non-linear SDR transfer curve (sRGB, BT.709, DCI, gamma) decode with that curve restored instead of falling back to the embedded preview: Lightroom Classic 15.1 LinearRaw DNGs (Rec. 2100 primaries, sRGB curve; 2 private ILCE-7CM2 files) |
|
||||
| 2026-10-15 | minor | Hasselblad 3FR / FFF embedded previews: the uncompressed 8-bit RGB image, handed on as a baseline TIFF (checked on the CC0 H4D-40 FFF, 1217 × 913, and CFV 3FR, 218 × 218, upright through the file's orientation); counts unchanged |
|
||||
| 2026-10-15 | minor | Hasselblad 3FR / FFF get their own reader (`crates/raw/src/vendor/hasselblad.rs`): an FFF takes its colour matrix from the original 3FR header in tag `0xC51B` instead of developing with the neutral matrix; RGGB checked against the camera preview on the CC0 H4D-40 FFF and CFV 3FR; counts unchanged |
|
||||
| 2026-10-12 | minor | SR2/SRF folder discovery and shared CLI/MCP extensions; public DSC-R1 sensor import and DSC-F828 preview-only import agree with direct import. F828 reclassified as preview-only (219 preview, 37 unsupported in the current generated coverage); sensor-decoding scores unchanged |
|
||||
| 2026-10-14 | minor | Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (`coverage_sweep_corpus.rs`, `coverage-sweep.tsv`; `docs/raw/coverage.py` 515 → 545 verified rows); the counts above are not re-measured here. Sensor verification does not establish colour parity (#1277) |
|
||||
| 2026-10-13 | minor | Bundled camera profiles 4 → 5 (ILCA-99M2) |
|
||||
| 2026-10-14 | minor | 21 Sony and Leica models verified (one CC0 raw.pixls.us file each, pinned; `sony_leica_corpus.rs`): 258 verified, 920 unverified; Leica on a verified body 21% → 72%, Sony 91% → 92%, use-weighted 85% and 43% |
|
||||
@@ -311,3 +327,4 @@ Beyond decoding:
|
||||
| 2026-10-14 | minor | 97 Canon CR2, Pentax and Ricoh models verified (one CC0 raw.pixls.us file each, pinned, checked against the camera JPEG; `canon_pentax_corpus.rs`): 334 verified, 844 unverified, 232 preview; 50% of photographers on a verified body; use-weighted 85% unchanged |
|
||||
| 2026-10-14 | minor | Integration batch 36: CC0 per-model verification for 117 Nikon/Fujifilm, 97 Canon CR2/Pentax/Ricoh, 21 Sony/Leica and 23 Olympus/Samsung bodies, 22 more CR3 bodies and 30 more models; Epson ERF and Mamiya MEF decode; measured Nikon image areas (#1053, #1770): 515 verified, 675 unverified, 218 preview, 38 unsupported (re-measured with `coverage.py`) |
|
||||
| 2026-10-15 | minor | By-maker and by-use tables re-measured with `coverage.py` after batches 36–39 (545 verified, 651 unverified, 212 preview, 38 unsupported; ~94% of photographers' cameras fully work, ~85% on a verified body); rows the per-model PRs had updated are current again |
|
||||
| 2026-10-18 | minor | Minolta MRW decoded (`vendor/mrw.rs`): DiMAGE 7, 7Hi, A1, A2, A200 and Dynax / Maxxum 7D verified; 551 verified, 656 unverified, 201 preview (re-measured with `coverage.py`) |
|
||||
|
||||
@@ -66,6 +66,8 @@ def classify(maker, camera, ext_field):
|
||||
return "unverified", "ARW", "ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)"
|
||||
if e == "SR2":
|
||||
return "unverified", "SR2", "SR2 decoder"
|
||||
if e == "SRF":
|
||||
return "unsupported", "SRF", "SRF is discovered; sensor decoding unsupported, usable preview not verified on a real file"
|
||||
if e == "RAF":
|
||||
if RAF_OLD.search(camera):
|
||||
return "preview", "RAF", "pre-X FinePix RAF without a raw IFD"
|
||||
@@ -87,7 +89,9 @@ def classify(maker, camera, ext_field):
|
||||
return "unverified", "MEF", "MEF decoder (uncompressed 12-bit)"
|
||||
if e == "ERF":
|
||||
return "unverified", "ERF", "ERF decoder (12-bit 16-byte blocks)"
|
||||
if e in ("CRW", "3FR", "FFF", "IIQ", "MOS", "KDC", "MRW", "X3F", "RAW", "ORI"):
|
||||
if e == "MRW":
|
||||
return "unverified", "MRW", "MRW decoder (12-bit packed, 16-bit words)"
|
||||
if e in ("CRW", "3FR", "FFF", "IIQ", "MOS", "KDC", "X3F", "RAW", "ORI"):
|
||||
return "preview", e, f"{e} is opened from its embedded JPEG only"
|
||||
return "unsupported", e, f"{e or 'unknown'} is not imported"
|
||||
|
||||
|
||||
+13
-13
@@ -552,17 +552,17 @@ Kodak EasyShare Z1015 IS KDC 2008 preview KDC is opened from its embedded JPEG o
|
||||
Kodak EasyShare Z980 KDC 2008 preview KDC is opened from its embedded JPEG only
|
||||
Kodak EasyShare Z981 RAW 2010 preview RAW is opened from its embedded JPEG only
|
||||
Kodak EasyShare Z990 RAW 2010 preview RAW is opened from its embedded JPEG only
|
||||
Konica Minolta Alpha-5 Digital (China) MRW 2005 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta Alpha Sweet Digital (Japan) MRW 2005 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta DiMAGE 5 MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta DiMAGE 7 MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta DiMAGE 7i MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta DiMAGE 7Hi MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta DiMAGE A1 MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta DiMAGE A2 MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta DiMAGE A200 MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta Maxxum 5D Dynax 5D MRW 2005 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta Maxxum 7D Dynax 7D MRW 2003 preview MRW is opened from its embedded JPEG only
|
||||
Konica Minolta Alpha-5 Digital (China) MRW 2005 unverified MRW decoder (12-bit packed, 16-bit words)
|
||||
Konica Minolta Alpha Sweet Digital (Japan) MRW 2005 unverified MRW decoder (12-bit packed, 16-bit words)
|
||||
Konica Minolta DiMAGE 5 MRW 2003 unverified MRW decoder (12-bit packed, 16-bit words)
|
||||
Konica Minolta DiMAGE 7 MRW 2003 verified raw.pixls.us check: CC0 sample decoded and its WB / CFA matched to the embedded preview (vendor/mrw.rs)
|
||||
Konica Minolta DiMAGE 7i MRW 2003 unverified MRW decoder (12-bit packed, 16-bit words)
|
||||
Konica Minolta DiMAGE 7Hi MRW 2003 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Konica Minolta DiMAGE A1 MRW 2003 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Konica Minolta DiMAGE A2 MRW 2003 verified raw.pixls.us check: CC0 sample decoded and its WB / CFA matched to the embedded preview (vendor/mrw.rs)
|
||||
Konica Minolta DiMAGE A200 MRW 2003 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Konica Minolta Maxxum 5D Dynax 5D MRW 2005 unverified MRW decoder (12-bit packed, 16-bit words)
|
||||
Konica Minolta Maxxum 7D Dynax 7D MRW 2003 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Leaf AFi II 6 MOS 2008 preview MOS is opened from its embedded JPEG only
|
||||
Leaf AFi II 7 MOS 2008 preview MOS is opened from its embedded JPEG only
|
||||
Leaf Aptus 17 MOS 2007 preview MOS is opened from its embedded JPEG only
|
||||
@@ -1306,10 +1306,10 @@ Sony A6400 (ILCE-6400) ARW 2018 verified issue #535: raw.pixls.us bulk check of
|
||||
Sony A6500 (ILCE-6500) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony A6600 (ILCE-6600) ARW 2019 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony A6700 (ILCE-6700) ARW 2022 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony DSC-F828 SRF 2003 unsupported issue #702 (SRF refused); SRF not in the import list
|
||||
Sony DSC-F828 SRF 2003 preview 2026-10-12 release CLI: raw.pixls.us DSC06227.SRF imports as preview-only through recursive and direct import (docs/raw-parity.md)
|
||||
Sony DSC-HX95 ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony DSC-HX99 ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony DSC-V3 SRF 2005 unsupported SRF not in the import list
|
||||
Sony DSC-V3 SRF 2005 unsupported SRF is discovered; sensor decoding unsupported, usable preview not verified on a real file
|
||||
Sony DSC-RX100M7A ARW 2024 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
|
||||
Sony FX3 (ILME-FX3) ARW 2020 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony FX30 (ILME-FX30) ARW 2022 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 86 and column 19.
|
@@ -465,10 +465,10 @@ Sony A900 (DSLR-A900) verified issue #535: raw.pixls.us bulk check of all 100 So
|
||||
Sony A99 (SLT-A99V) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies
|
||||
Sony A99 II (ILCA-99M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony Alpha 1 (ILCE-1)* verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony DSC-F828 unsupported SRF not in the import list (the probe accepts it again since #774, issue #702); raw not decoded
|
||||
Sony DSC-F828 preview 2026-10-12 release CLI: raw.pixls.us DSC06227.SRF imports as preview-only through recursive and direct import (docs/raw-parity.md)
|
||||
Sony DSC-HX95 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony DSC-HX99 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony DSC-V3 unsupported SRF not in the import list
|
||||
Sony DSC-V3 unsupported SRF is discovered; sensor decoding unsupported, usable preview not verified on a real file
|
||||
Sony FX3 (ILME-FX3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony FX30 (ILME-FX30) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
Sony NEX-3 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
|
||||
@@ -528,7 +528,7 @@ Samsung TL350 (WB2000) verified corpus: crates/raw/tests/olympus_samsung_corpus.
|
||||
Samsung NX1000 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
|
||||
Samsung NX1100 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
|
||||
Sony A100 (DSLR-A100) unsupported issue #702: regression, refused as not a readable photo
|
||||
Sony DSC-F828 unsupported issue #702 (SRF refused); SRF not in the import list
|
||||
Sony DSC-F828 preview 2026-10-12 release CLI: raw.pixls.us DSC06227.SRF imports as preview-only through recursive and direct import (docs/raw-parity.md)
|
||||
Epson R-D1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Epson R-D1s verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Epson R-D1x verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
@@ -563,3 +563,9 @@ Samsung Galaxy S8 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pi
|
||||
Samsung Galaxy S8+ verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
|
||||
Sigma fp verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
|
||||
Sigma fp L verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
|
||||
Konica Minolta DiMAGE 7Hi verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Konica Minolta DiMAGE A1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Konica Minolta DiMAGE A200 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Konica Minolta Maxxum 7D Dynax 7D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
|
||||
Konica Minolta DiMAGE 7 verified raw.pixls.us check: CC0 sample decoded and its WB / CFA matched to the embedded preview (vendor/mrw.rs)
|
||||
Konica Minolta DiMAGE A2 verified raw.pixls.us check: CC0 sample decoded and its WB / CFA matched to the embedded preview (vendor/mrw.rs)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 3.
|
@@ -7,11 +7,11 @@ convention; that page's *Two conventions* section has the comparison and what wo
|
||||
|
||||
Sony ARWs record three lens corrections per shot in the raw image IFD: shading (vignetting), lateral chromatic
|
||||
aberration (CA) and distortion, each as a camera setting and a radial table, with copies in the encrypted
|
||||
`SR2SubIFD`. LightCraft **decodes all three** wherever a file carries them and **applies one**: the distortion table
|
||||
of the **ILCE-7RM4A (A7R IVA)** in Bayer ARWs with native 3:2 framing, independently validated with the
|
||||
FE 24–105mm F4 G OSS and FE 200–600mm F5.6–6.3 G OSS (the ILCE-7RM6's distortion is applied too, by its own
|
||||
conversion above). Every other model,
|
||||
and shading and CA on every model, are decoded only: no warp, vignetting or CA correction is added for them. The decoded record stays with the image
|
||||
`SR2SubIFD`. LightCraft **decodes all three** wherever a file carries them and applies distortion here on two validated
|
||||
bodies: **ILCE-7RM4A (A7R IVA)** and **ILCE-7CR (A7CR)**. The A7R IVA keeps its existing Bayer 3:2 conversion,
|
||||
validated with the FE 24-105mm F4 G OSS and FE 200-600mm F5.6-6.3 G OSS. The A7CR uses a separate conversion,
|
||||
limited to full-frame Bayer 3:2 files with distortion Auto or forced Auto. Apart from the separate ILCE-7RM6 converter, other models and shading and CA on every model are
|
||||
decoded only: no warp, vignetting or CA correction is added for them. The decoded record stays with the image
|
||||
(`RawInfo::lens_metadata` / `RawImage::lens_metadata`, types in `crates/raw/src/lens_metadata.rs`, decoder in
|
||||
`crates/raw/src/vendor/arw_lens.rs`) for diagnostics and for the validation work described below.
|
||||
|
||||
@@ -20,7 +20,8 @@ control, CPU/GPU optics path, EXIF orientation handling, and DNG export use the
|
||||
and non-3:2 camera crops are excluded pending validation. An aspect crop may retain the full-frame radial
|
||||
normalization; the decoder does not guess that relationship. Tables of other lengths and rejected tables remain
|
||||
uncorrected, and a table found only in the encrypted `SR2SubIFD` is never applied. The camera's distortion Off
|
||||
setting does not erase its table. Newly imported photos use LightCraft's existing embedded-profile default
|
||||
setting does not erase its table. A7CR Off, missing, unavailable and unknown settings produce no warp. The A7R IVA's
|
||||
existing behavior is unchanged. Newly imported photos use LightCraft's existing embedded-profile default
|
||||
(enabled); the profile correction control can disable or adjust it.
|
||||
|
||||
## What is decoded
|
||||
@@ -67,7 +68,10 @@ setting does not erase its table. Newly imported photos use LightCraft's existin
|
||||
conversion refused the table). A readable table never implies a usable correction; only `Applied` components
|
||||
become opcodes.
|
||||
|
||||
The applied path is unchanged: the validated conversion still receives the raw IFD's `0x7037` table exactly as the
|
||||
For the A7CR, `CameraCorrectionDisabled` records an Off, missing, unavailable or unknown setting. APS-C,
|
||||
aspect crops and lossless M/S remain `NotValidatedMode`. The raw IFD's correction table is required.
|
||||
|
||||
The A7R IVA's applied path is unchanged: its conversion still receives the raw IFD's `0x7037` table exactly as the
|
||||
TIFF reader parsed it, with the same model, framing and fit checks, and produces the same warp. A repeated tag keeps
|
||||
the reader's choice and is only flagged, and a table the reader never reads (past its entry limit) is reported but
|
||||
never applied, so already-catalogued photos, their previews and their stored lens data stay consistent with a fresh
|
||||
@@ -85,17 +89,8 @@ part of this repository.
|
||||
(Auto) everywhere; distortion is 17 (Auto, fixed by the lens) on the nine FE 24–50 files and 0 (Off) on the rest.
|
||||
Every image crop is 9504 × 6336 at (32, 20), in uncompressed, compressed and lossless-L (padded to 9728 × 6656)
|
||||
storage. The decoder reproduces this inventory exactly (see *Local inventory check*).
|
||||
- **Distortion.** The table layout and crop tags match the A7R IVA's, but its interpretation applied unchanged does
|
||||
not reproduce the A7CR camera JPEG at 24 mm: at 2376 px wide, median / 95th-percentile feature residuals were
|
||||
1.61 / 6.17 px (12.98 / 51.19 px uncorrected, 6,539 matches), with a signed radial pattern (about +1.7 px at
|
||||
radii 0.25–0.5, −7.4 px at 0.8–1) that no translation removes. At 50 mm it agrees (0.23 / 1.05 px, from
|
||||
2.21 / 12.25 px). Scaling the raw table by about 10/9, or using the MakerNote's distortion array instead, brings
|
||||
24 mm to about 0.37 / 1.10 and 0.33 / 0.97 px. Those are exploratory comparisons on two files against the camera
|
||||
JPEG, not a model: the MakerNote values are roughly 1.11 times the raw table without one exact ratio, and the
|
||||
A7R IVA was validated against a different reference renderer (Imaging Edge exports). Needed: A7CR Imaging Edge
|
||||
On/Off exports at 24 and 50 mm and with another lens, compared with the raw table at its fixed scale; a decision on
|
||||
the reference (camera JPEG or Imaging Edge); then ILCE-7CR added to the validated models for the full-frame Bayer
|
||||
3:2 mode only. Neither a 10/9 multiplier nor the MakerNote table is used.
|
||||
- **Distortion.** The A7R IVA interpretation misses the A7CR camera JPEG at the wide end. The A7CR now uses the
|
||||
separately measured corrected-radius convention below. It uses the raw IFD table, not the MakerNote array.
|
||||
- **Shading.** The layout decodes, but no public specification gives the sample-to-gain function, its radius
|
||||
convention on the A7CR, or whether the raw samples already contain part of the correction. Brightness ratios on six
|
||||
natural scenes show no consistent missing peripheral brightening. Needed: evenly lit flat fields with shading
|
||||
@@ -112,6 +107,78 @@ part of this repository.
|
||||
- **Other modes.** APS-C, aspect crops and lossless M/S (linear YCbCr) captures were not measured and stay
|
||||
excluded.
|
||||
|
||||
## A7CR camera-JPEG validation
|
||||
|
||||
The A7CR conversion substantially reduces the corrected camera JPEG's geometry mismatch. Its empirical table unit is
|
||||
`15559.300457825264`, fixed by the earlier four-file FE 24-50mm fit before checking other files and lenses.
|
||||
This is a measured approximation for the tested scope, not a Sony specification or a universal Sony constant.
|
||||
|
||||
For output radius `rho` in native crop pixels:
|
||||
|
||||
```
|
||||
spacing = hypot(9504, 6336) / 30
|
||||
S(r) = r * (1 + interpolate(table, r / spacing) / 15559.300457825264)
|
||||
source_radius(rho) = S(rho / zoom)
|
||||
```
|
||||
|
||||
Interpolation is linear. The final segment extends beyond the last knot. Positive samples increase the source
|
||||
radius before the fill zoom; negative samples reduce it.
|
||||
|
||||
The fill zoom makes the maximum of
|
||||
`S(rho / zoom) / rho` on the rectangular crop border equal to one. The optical center is the default crop's
|
||||
pixel-center midpoint. The measured crop is 9504 by 6336 at (32, 20).
|
||||
|
||||
Storage padding affects DNG normalization
|
||||
only, so 9600 by 6376 and 9728 by 6656 storage produce the same physical warp.
|
||||
|
||||
A four-term odd polynomial represents the map as `WarpRectilinear`. A dense 32769-point check rejects a polynomial
|
||||
that folds or differs from the table by more than 0.25 px at a 1440 px long edge. This approximation check is
|
||||
separate from agreement with the camera JPEG. The converter applies no shading or lateral CA.
|
||||
|
||||
Validation uses all 131 owner files numerically, nine public raw.pixls.us codings, one independent public FE 50mm
|
||||
file and the public issue #1300 FE 16-25mm file. Private pixels stay in memory. No private images, names or source
|
||||
identities are saved in the new measurement artifacts.
|
||||
|
||||
The nine private correction-on files cover the FE 24-50mm
|
||||
at 24, 49 and 50 mm. The other 122 private files have correction Off and cover the Tamron 35-150mm and the
|
||||
Voigtlander APO-LANTHAR 28mm and 50mm. Off references validate preservation, not correction-on optics for those lenses.
|
||||
|
||||
Green-channel SIFT (Scale-Invariant Feature Transform) matches at 2376 px are filtered by an independent radial fit. Residuals remove median translation
|
||||
only. They fit no scale, center or lens coefficient to the candidate.
|
||||
|
||||
High-pass correlation uses held-out pixels
|
||||
at 1200 px. File checks hold out the second file and then every fifth file in each lens/focal/setting group, and
|
||||
both public correction-on files. The private checks were also examined in the earlier study, so they are file
|
||||
checks rather than a fresh independent capture campaign.
|
||||
|
||||
The public second-lens checks do not tune the unit.
|
||||
|
||||
The A7R VI study uses a 0.75 px camera validation budget at 1440 px. The reporter has support to 0.933 half
|
||||
diagonals. Its direct table's largest radial-bin residual is 0.590 px at 1200 px (0.708 px at 1440 px).
|
||||
|
||||
The implemented polynomial reaches 0.638 px in a radial bin and 0.692 px in a supported edge region at 1200 px
|
||||
in source coordinates. Actual corrected-raster matches give 0.173/0.605 px median/p95, a 0.763 px radial-bin
|
||||
maximum and a 0.969 px edge-region maximum at 1200 px. That edge region is 1.162 px at 1440 px, so this does not
|
||||
pass a strict 0.75 px budget everywhere measured.
|
||||
|
||||
The unit test against the independently fitted smooth feature curve uses the combined 1.0 px table and
|
||||
polynomial approximation budget; its observed maximum is 0.895 px at 1440 px. These are different metrics.
|
||||
|
||||
The held-out private 24 mm file has weak outer-radius support. No unsupported ideal corner is counted as measured.
|
||||
|
||||
APS-C, aspect crops and lossless M/S are excluded. Portrait captures use the same sensor-coordinate map and the
|
||||
existing EXIF orientation conversion. Other lenses and the 16-25mm's tele end need more correction-on references.
|
||||
|
||||
No Imaging Edge On/Off pairs were measured for this body. Camera JPEGs are the reference for this change.
|
||||
|
||||
This branch supplies the warp for camera-look pairing in PR #1586. It does not duplicate that pairing code.
|
||||
|
||||
`LOOK_VERSION` 12 refreshes smart previews that stored no A7CR lens data; `RENDER_CACHE_VERSION` 69 redraws cached
|
||||
renders.
|
||||
|
||||
Existing edits are preserved. Reload from Disk reads the new lens data for old imports. Enable the
|
||||
profile correction control when an existing edit has it disabled.
|
||||
|
||||
## Local inventory check
|
||||
|
||||
`crates/raw/tests/sony_lens_inventory.rs` is opt-in and does nothing in CI. With `LIGHTCRAFT_SONY_LENS_DIR` naming a
|
||||
|
||||
+12
-11
@@ -1,6 +1,6 @@
|
||||
# Target-app parity: LightCraft vs Adobe Lightroom
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (watermark completeness reassessed after #2052; current checklist recount synchronized) · **Target:** Adobe Lightroom Classic 15.5 (August 2026) and Lightroom 9.6 (desktop)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (Minolta MRW decoded, 6 bodies verified; RAW: 83% of Adobe's models decode, 38% verified after the camera-coverage sweep #1327; Platforms: the Windows x64 installer verified on Windows 11) · **Target:** Adobe Lightroom Classic 15.5 (August 2026) and Lightroom 9.6 (desktop)
|
||||
|
||||
The authoritative assessment of how close LightCraft is to Lightroom. The row-by-row checklist is
|
||||
[parity-checklist.md](parity-checklist.md); the ranked work list is [gaps.md](gaps.md); deep areas
|
||||
@@ -12,7 +12,7 @@ have their own documents: [raw-parity.md](raw-parity.md), [file-format-parity.md
|
||||
|
||||
| Number | Value | Kind |
|
||||
|---|---:|---|
|
||||
| **Feature breadth**, checklist | **83.3%** of 539 in-scope rows (P0 98.3%, P1 94.7%, P2 55.7%) | **measured**: recount of [parity-checklist.md](parity-checklist.md) rows (✅ 1, 🟡 ½, 🚫 left out), the same rule `cargo xtask parity` uses |
|
||||
| **Feature breadth**, checklist | **81.6%** of 532 in-scope rows (P0 98.0%, P1 95.2%, P2 50.0%) | **measured**: recount of [parity-checklist.md](parity-checklist.md) rows (✅ 1, 🟡 ½, 🚫 left out), the same rule `cargo xtask parity` uses |
|
||||
| **Feature breadth**, weighted by use | **~77%** | estimated: feature-area table below |
|
||||
| **Ready for real work** (full target) | **~61%** (range 56–66%) | estimated: weighted sum over the dimensions with written weights, see [below](#ready-for-real-work-mainstream-practitioner-and-essentials-user) |
|
||||
| **Mainstream practitioner** | **~51%** (range 46–56%) | estimated: weekly areas of a working photographer, depth 74.0% × 0.804 × colour 0.85 |
|
||||
@@ -46,15 +46,15 @@ phase #85, duplicate Local entries #22, black GPU exports on an Intel iGPU #78).
|
||||
|
||||
| Dimension | Breadth | Ready for real work | Remaining | Doc |
|
||||
|---|---:|---:|---:|---|
|
||||
| Features (weighted areas below) | ~77% (checklist 83.3%, measured) | ~61% | 600–1,100 h | [Features](#features) |
|
||||
| Features (weighted areas below) | ~77% (checklist 81.1%, measured) | ~61% | 600–1,100 h | [Features](#features) |
|
||||
| ↳ RAW cameras, colour and lens profiles (part of features) | 83% of Adobe's 1,446 models decode (38% verified), measured | ~56% | 255–480 h | [raw-parity.md](raw-parity.md) |
|
||||
| UI/UX fidelity | ~85% | ~70% | 32–54 h | [ui-parity.md](ui-parity.md) |
|
||||
| File formats (non-raw: images, sidecars, presets, catalogs) | ~85% | ~70% | 30–55 h | [file-format-parity.md](file-format-parity.md) |
|
||||
| Hardware | ~50% | ~40% | 40–80 h (+ tethering and printing, counted under Classic modules) | [hardware-parity.md](hardware-parity.md) |
|
||||
| Localization (12 key languages) | 7 of 12 present | ~55% (mean of the 12, measured) | 130–210 h for the target's set; +145–255 h for Hindi, Arabic, Indonesian, Vietnamese, Korean | [localization-parity.md](localization-parity.md) |
|
||||
| Localization (12 key languages) | 8 of 12 present | ~64% (mean of the 12, measured) | 130–210 h for the target's set; +135–239 h for Hindi, Arabic, Vietnamese, Korean | [localization-parity.md](localization-parity.md) |
|
||||
| Performance | ~80% | ~70% | 30–50 h | 24 MP GPU slider 3.9 ms, export 0.3 s (measured, [gpu-pipeline.md](gpu-pipeline.md)); 85k library opens in 1.7–4.7 s; 100k test pending |
|
||||
| Stability | — | ~60% | 30–60 h | never-crash lints and guards workspace-wide, crash-tested journal, 2,561 tests; but CI runs tests only on FreeBSD, user reports keep finding bugs in ✅ areas |
|
||||
| Platforms | macOS, Windows (x64 / x86 / ARM64), Linux (AppImage, deb, rpm, Flatpak, Nix), FreeBSD, web | ~60% | 25–45 h | macOS is where it is tested; Windows installer UI unverified on Windows (#79); web experimental (CPU only) |
|
||||
| Platforms | macOS, Windows (x64 / x86 / ARM64), Linux (AppImage, deb, rpm, Flatpak, Nix), FreeBSD, web | ~60% | 25–45 h | macOS is where it is tested; the Windows x64 installer is verified on Windows 11 (0.9.1) except for a confirmation after uninstalling from Settings → Apps (#892); web experimental (CPU only) |
|
||||
| Ecosystem / plug-ins | ~25% | ~15% | 40–80 h + owner decision | no Lightroom SDK equivalent (export, publish, metadata plug-ins); beyond the target: JSON-lines control channel, MCP server with 28 tools + one per command, CLI |
|
||||
| AI features | ~30% | ~20% | 120–220 h + model decisions | [AI](#ai) (overlaps the masking and Enhance rows; not added twice) |
|
||||
|
||||
@@ -74,7 +74,7 @@ from the checklist sections and the deep-dive documents.
|
||||
| Masking | 8% | 67% | 45% | 60–110 h | checklist K 67.4%; AI masks heuristic or SAM 3 without mirror |
|
||||
| Remove, heal, Enhance (denoise, Raw Details, Super Resolution, Lens Blur) | 7% | 45% | 30% | 70–130 h | checklist I 75%, P 0% |
|
||||
| Presets, profiles, versions, history, sync | 5% | 95% | 85% | 8–15 h | |
|
||||
| Export | 6% | 89% | 75% | 15–30 h | checklist S 86.8% |
|
||||
| Export | 6% | 89% | 75% | 15–30 h | checklist S 88.9% |
|
||||
| Merge and HDR | 3% | 85% | 60% | 20–35 h | merge 88% (HDR merge 🟡: AI Denoise on merged DNGs, JPEG response, memory; gap 29), HDR 70% (no HDR display) |
|
||||
| Views, compare, zoom, culling | 5% | 90% | 70% | 15–30 h | zoom detail P0 gap |
|
||||
| Classic output modules (Map, Book, Slideshow, Print, Web, publish, tethering) | 4% | 15% | 10% | 90–150 h | Classic extras 48% counts many small keys; the modules themselves are mostly ⬜ |
|
||||
@@ -136,12 +136,12 @@ valued from the model-by-model measurement in [raw-parity.md](raw-parity.md):
|
||||
| UI/UX fidelity | 8% | 70% |
|
||||
| File formats (non-raw) | 6% | 70% |
|
||||
| Hardware | 3% | 40% |
|
||||
| Localization | 3% | 55% |
|
||||
| Localization | 3% | 64% |
|
||||
| Performance | 6% | 70% |
|
||||
| Stability | 8% | 60% |
|
||||
| Platforms | 4% | 60% |
|
||||
| Ecosystem / plug-ins | 2% | 15% |
|
||||
| **Weighted sum** | 100% | **61.2% ≈ 61%** (range 56–66%) |
|
||||
| **Weighted sum** | 100% | **61.5% ≈ 61%** (range 56–66%) |
|
||||
|
||||
Mainstream comes out lower than full: its scope is the raw-develop core, where LightCraft is weakest
|
||||
(camera colour, CR3 coverage), and its discounts are multiplicative, while the full sum is lifted by
|
||||
@@ -158,7 +158,7 @@ essentials replaces its guessed 45% raw look with 84% × 0.75 (+1 point).
|
||||
| Discount | Mainstream | Essentials | Evidence |
|
||||
|---|---:|---:|---|
|
||||
| Interaction fidelity / UI clarity | ×0.95 | ×0.95 | brush live preview gaps and feathering (#517, #518); crop artefacts and limits (#731, #742, #770, #771); Compare white-balance selector (#743–#745); low-res slider previews (#330); dialogs taller than the window (#781); mouse-wheel scroll (#714) |
|
||||
| Stability on real machines | ×0.92 | ×0.92 | launch and quit crashes or silent startup failures (#250 macOS 12 Intel, #315 Windows Intel GPU, #431 Intel Mac, #260, #462, #783 Linux / Nouveau, #790 Windows installer); tests run in CI on FreeBSD only (gap 9) |
|
||||
| Stability on real machines | ×0.92 | ×0.92 | launch and quit crashes or silent startup failures (#250 macOS 12 Intel, #315 Windows Intel GPU, #431 Intel Mac, #260, #462, #783 Linux / Nouveau, #790 Windows installer, fixed in 0.9.1); tests run in CI on FreeBSD only (gap 9) |
|
||||
| File exchange with Lightroom users (mainstream) / opening files people send (essentials) | ×0.92 | ×0.95 | Adobe XMP sidecars change colours (#290), XMP CameraProfile and Upright ignored (#204, #205), catalog import fails (#599), same Light values render differently (#523, #209), no write-back to `.lrcat`; for casual users: CR3 beyond verified bodies, compressed ORF, HE NEF open as previews (#473, #485, #556), HEIC trouble (#693) |
|
||||
| **Product** | **×0.804** | **×0.830** | |
|
||||
|
||||
@@ -276,7 +276,6 @@ most of its list; what remains is the data-heavy part (colour, lenses, models) i
|
||||
|
||||
| Date | Checklist (all / P0 / P1 / P2) | Ready for real work | Remaining to full parity | Source |
|
||||
|---|---|---|---|---|
|
||||
| 2026-10-17 | 83.3% / 98.3% / 94.7% / 55.7% (539 rows) | unchanged | unchanged | Current recount synchronized; LR-EXP-WATERMARK reassessed as partial for existing text-layout and font limits (#2052), not a rendering regression |
|
||||
| 2026-10-12 | 81.1% (unchanged) | ~61% full · ~51% mainstream · ~65% essentials | unchanged | 22 more CR3 bodies verified (raw-parity.md) |
|
||||
| 2026-10-10 (RAW) | 81.1% (unchanged) | ~61% full · ~49% mainstream · ~64% essentials | unchanged | RAW measured model by model against Adobe's list; RAW decode and colour as explicit rows |
|
||||
| 2026-10-10 (final) | 81.1% (unchanged) | ~59% full (weighted sum) · ~51% mainstream · ~63% essentials | full 1,000–1,800 h · mainstream 450–840 h · essentials 160–300 h | method aligned with the standard |
|
||||
@@ -301,12 +300,14 @@ P2 items 12–25 h; look tuning, performance, packaging, hardening 25–45 h.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-17 | minor | LR-EXP-WATERMARK reassessed as partial for existing shaping, bidi and font limits (#2052); current checklist recount 83.4% to 83.3% of 539 rows. Stale headline figures synchronized to that recount; readiness and effort estimates unchanged |
|
||||
| 2026-10-19 | minor | Localization ~55% → ~64% (mean of the 12 key languages): Indonesian (`id`) catalog, every message but What's New; full target 61.2% → 61.5% (still ~61%) |
|
||||
| 2026-10-19 | patch | Platforms: the Windows installer is no longer listed as unverified (the 0.9.1 x64 MSI is tested on Windows 11; a confirmation after uninstalling from Settings → Apps, #892, remains); #790 noted as fixed in 0.9.1; percentages and hours unchanged |
|
||||
| 2026-10-14 | minor | Localization ~52% → ~55% (mean of the 12 key languages): de, es, fr, ja and uk catalogs filled |
|
||||
| 2026-10-12 | minor | UI/UX remaining 34–58 h → 32–54 h (canvas tools by keyboard); headline numbers unchanged |
|
||||
| 2026-10-14 | minor | Merge and HDR: merge 100% → 88% (LR-MERGE-HDR ✅ → 🟡 after testing on real 67 MP brackets; gap 29); area percentages unchanged |
|
||||
| 2026-10-14 | minor | Gap 4 (full-resolution zoom) closed: removed from the interaction-fidelity notes, the alpha-gate partial rows and the beta needs; headline numbers and the ×0.95 interaction factor unchanged |
|
||||
| 2026-10-12 | minor | UI/UX remaining 40–70 h → 34–58 h (accessibility audit landed, ui-parity.md); percentages unchanged |
|
||||
| 2026-10-18 | minor | Minolta MRW decodes from sensor data; 6 Konica Minolta bodies verified; headline numbers unchanged |
|
||||
| 2026-10-14 | minor | Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (`coverage_sweep_corpus.rs`, `coverage-sweep.tsv`; `docs/raw/coverage.py` 515 → 545 verified rows); the counts above are not re-measured here. Sensor verification does not establish colour parity (#1277); headline numbers unchanged |
|
||||
| 2026-10-12 | minor | 22 more CR3 bodies verified on one CC0 file each (raw-parity.md): use-weighted share whose camera decodes 85% → 92%, RAW decoding 77% → 83%; full 60.7% → 61.2% (~61%), mainstream depth 72.1% → 74.0% (~49% → ~51%), essentials depth 77.5% → 78.0% (~64% → ~65%) |
|
||||
| 2026-10-12 | minor | 117 Nikon and Fujifilm bodies verified (raw-parity.md): use-weighted share whose camera decodes 85% → 86%, RAW decoding 77% (unchanged); mainstream depth 72.1% → 72.4%, essentials depth 77.5% → 77.6%; headline numbers unchanged |
|
||||
|
||||
+26
-5
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (zoom row: soft proofing sharp at 1:1; canvas tools by keyboard; focus-order test; ~50 unnamed hand-drawn widgets named) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** minor (context menus: a right-click elsewhere on the same widget moves its open menu instead of flashing it, issue #1877; Reference Escape, catalog-local pins and deleted-photo handling verified on Windows; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; earlier: zoom row soft proofing sharp at 1:1, canvas tools by keyboard, focus-order test, ~50 unnamed hand-drawn widgets named) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
|
||||
How LightCraft's interaction compares with Lightroom's: views, panels, tools and handles, modifier
|
||||
keys, shortcuts, zoom, context menus and feel. Rows for individual menu items and shortcuts live in
|
||||
@@ -30,7 +30,7 @@ assessment and the notes that don't fit a row. Visual reference for agents: `pla
|
||||
| Zoom and navigation | Fit / Fill / 1:1 / 2:1 … 11:1, Navigator, spacebar pan | Fit to 800%, pinch zoom, pan, Navigator-like | 60% | diagnostic overlays (Visualize Spots, Point Color range, HDR range) render the whole frame instead of a zoom window; everything else, mask overlays and soft proofing included, is sharp at 1:1–8:1 |
|
||||
| Library culling and metadata | ratings, flags, labels with keys, painter, keywording, metadata panel, filter bar | ratings / flags / labels and advance, keyword list and sets, keywording box, metadata fields, smart albums, filter bar | 85% | painter tool, quick collection, some Classic keys |
|
||||
| Shortcuts | Lightroom desktop and Classic keymaps | Lightroom-desktop keymap with Classic additions, editable | 85% | deliberate differences below; no Classic keymap layer |
|
||||
| Context menus | everywhere | 26 sites (grid, filmstrip, albums, folders, keywords, masks, presets…) | 70% | unaudited against Lightroom's menus item by item |
|
||||
| Context menus | everywhere | 31 sites (grid, filmstrip, albums, folders, keywords, masks, presets, text fields…), all through `widgets::context_menu` (`crates/ui-egui/src/widgets.rs`): a right-click elsewhere while a menu is open closes it and opens the menu there, also on the same widget (issue #1877: that one flashed at the new spot and closed, as egui counts the opening click as a click while open) | 70% | unaudited against Lightroom's menus item by item |
|
||||
| Text fields | native editing behaviour | custom text field (LR-BEHAV-TEXTFIELD) | 70% | IME and selection edge cases |
|
||||
| Accessibility | VoiceOver / Narrator, keyboard navigation | AccessKit tree audited by a test in every view, Develop tool and the main dialogs: every control named, sliders with value, range and Increment / Decrement, dialogs titled, tool sliders reachable with Tab; crop handles, spots, gradient handles and mask pins by keyboard | 75% | brush strokes and red-eye ellipses need the pointer; not tried with a real screen reader ([audit](#accessibility-audit)) |
|
||||
| Feel and responsiveness | GPU-interactive sliders (15.3) | ≤ 16 ms slider budget on GPU; background renders | 75% | colour NR at half resolution, 100k-photo grid test |
|
||||
@@ -66,15 +66,15 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| Create Version | ⌘⇧S (+ ⇧M) | ⇧M (Windows: Ctrl+⇧S) | — |
|
||||
| Select None | ⌘⇧A (+ ⌘D) | ⌘D | — |
|
||||
|
||||
**Library culling (KEYC-RATING):** `0–5`, `6–9` and `P/X/U` reach egui on macOS even when displayed in the native menu (adapted from PR #261; issue #283). `Shift+0–9` applies and advances once; `Shift+P` picks and advances in Photo Grid / Square Grid and retains Presets elsewhere. See [library shortcuts](library-shortcuts.md) for the verified Classic mapping and regression coverage. `` ` `` cycles the flag (unflagged → pick → reject → unflagged) and ⌘↑ / ⌘↓ raise / lower it; the native menu leaves ⌘↑ / ⌘↓ to egui so Compare/Survey act on the active photo. Remaining Classic gaps: filter-bar keys.
|
||||
**Library culling (KEYC-RATING):** `0–5`, `6–9` and `P/X/U` reach egui on macOS even when displayed in the native menu (adapted from PR #261; issue #283). `Shift+0–9` applies and advances once; `Shift+P` picks and advances in Photo Grid / Square Grid and retains Presets elsewhere. See [library shortcuts](library-shortcuts.md) for the verified Classic mapping and regression coverage. F6, F7 and F8 show / hide the filmstrip, the left panel and the right panel (F8 brings back the panel it hid; `view.rightPanel`). `` ` `` cycles the flag (unflagged → pick → reject → unflagged) and ⌘↑ / ⌘↓ raise / lower it; the native menu leaves ⌘↑ / ⌘↓ to egui so Compare/Survey act on the active photo. `\` shows / hides the Filter Bar in Photo Grid / Square Grid and keeps Show Original in Detail. Remaining Classic gaps: ⌘L (Enable Filters).
|
||||
|
||||
**Grid thumbnail size (KEYC-COMPARE):** Classic's `=` and `-` increase and decrease the Grid thumbnail size (same keys on macOS and Windows; `+` also works for the larger size here, as Shift+`=` or the keypad). They step the bottom-bar slider by 30 pt inside its 90–480 pt range, in Photo Grid, Square Grid and People only; Detail, Compare, Survey and Reference ignore them, and ⌘= / ⌘− stay Zoom In / Out. See [library shortcuts](library-shortcuts.md).
|
||||
|
||||
**Library navigation keys (KEYC-COMPARE):** in Photo Grid and Square Grid, `Home` / `End` make the first / last photo of the view the active, selected one, and `Shift+Left` / `Shift+Right` add the previous / next photo of the view to the selection (the added photo becomes active; the keys never remove a photo from the selection, and do nothing at either end of the view). They wait in the other views, in the full-screen preview, while a text field or a dialog has the keyboard, and when you gave the key to a command; they are listed under Fixed keys in Help → Keyboard Shortcuts. `Cmd+O` (`Ctrl+O` on Windows and Linux) runs File → Open Library… ; View → Visualize HDR has no default key (`Shift+H` cycles the Tool Overlay, issue #224; `Cmd+Shift+H` stays the Histogram). Covered by `crates/ui-egui/src/tests_keymap.rs`; the macOS native menu path for `Cmd+O` is not exercised by the headless tests.
|
||||
|
||||
**Still missing / broken:**
|
||||
- No command yet: F1 help (verify the rest of the old list: full screen, settings, stacks,
|
||||
visualize spots and merges have commands now).
|
||||
- Tab / ⇧Tab (hide the side panels / all panels): Tab moves the keyboard focus here (accessibility), so it is
|
||||
not bound; F5 (hide the top panel) is not bound either.
|
||||
- ⌘M / ⌘H / ⌘Q / ⌘W rely on the platform window defaults (unverified).
|
||||
|
||||
|
||||
@@ -126,10 +126,30 @@ unnamed was in the way.
|
||||
Not done: brush strokes and red-eye ellipses still need a pointer. Nobody has tried the app with
|
||||
a real screen reader on any platform yet.
|
||||
|
||||
## Reference view lifecycle
|
||||
|
||||
Escape leaves Reference for Detail, preserving the active photo and its edits; a second Escape
|
||||
returns to the grid. Existing dialogs, tools and Lights Out retain their higher-priority Escape
|
||||
handling. The reference stays pinned when leaving the view, but a successful Open/New Library
|
||||
clears it: numeric photo IDs are local to each catalog. A refused switch keeps the current pin,
|
||||
including Open/New refused while an import or its folder scan is running.
|
||||
|
||||
`photo.setReference` refuses missing or Recently Deleted photos without replacing the current
|
||||
pin. Deleting a pinned reference stops drawing it; re-entering Reference falls back to a live
|
||||
active photo. Restoring the photo permits pinning it again. An empty catalog cannot supply one.
|
||||
|
||||
Verification: four new behavioral regressions failed on upstream `76b5377` before the fix.
|
||||
The patched Windows x64 app on RTX 5080 / DX12 passed Escape, edit preservation, delete/restore,
|
||||
and New/Open Library checks using isolated catalogs. Saved exposure survived reopening. Native
|
||||
1400 × 900 captures did not fall back to headless rendering. Control-channel input does not
|
||||
establish OS keyboard delivery or cross-platform acceptance.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-15 | patch | Context menus: a right-click elsewhere on the same widget while its menu is open moves the menu to the pointer; before it flashed at the new spot and closed (issue #1877); every site uses `widgets::context_menu`; 70% unchanged |
|
||||
| 2026-10-15 | patch | KEYC-RATING: `\` shows / hides the Filter Bar in Photo Grid and Square Grid (Show Original in Detail); ⌘L is the filter-bar key left |
|
||||
| 2026-10-14 | patch | Shortcuts: Visualize HDR loses its default ⇧H, which cycles the Tool Overlay (issue #224); both PRs had claimed it |
|
||||
| 2026-10-12 | minor | Accessibility: crop handles, spots, gradient handles and mask pins operable by keyboard with AccessKit names and values; Tab order test for the right panels; hand-drawn icons and rows named; 55% → 75%; UI remaining 34–58 h → 32–54 h |
|
||||
| 2026-10-13 | minor | Shortcuts: Tool Overlay modes for spot and mask pins on ⇧H (H stays Remove; issue #224), listed with the deliberate differences; dropped from the missing list |
|
||||
@@ -138,5 +158,6 @@ a real screen reader on any platform yet.
|
||||
| 2026-10-13 | minor | Shortcuts: Home / End and Shift+Left / Right in the two library grids, Cmd+O (Open Library…) and Shift+H (Visualize HDR) get bindings and keyboard tests; percentages and the dated counts above are unchanged |
|
||||
| 2026-10-14 | minor | Zoom row: soft proofing gets zoom windows; LR-VIEW-ZOOM gap closed |
|
||||
| 2026-10-12 | minor | Accessibility: an AccessKit tree audit test over every view, Develop tool and 13 dialogs; unnamed fields, slider ranges and the Auto Stack slider fixed; 30% → 55%; UI remaining 40–70 h → 34–58 h |
|
||||
| 2026-10-21 | minor | Reference Escape exits to Detail; catalog-local pins and missing/deleted reference handling verified with regressions and native Windows smoke |
|
||||
| 2026-10-11 | minor | Zoom row: Compare and Reference get zoom windows; what remains of LR-VIEW-ZOOM is overlays and soft proofing |
|
||||
| 2026-10-10 | major | Created: measured command and shortcut counts, assessment by area, estimates; moved the shortcut conflicts section here from the checklist |
|
||||
|
||||
@@ -32,7 +32,7 @@ Open them from the **Window** menu, or press the key shown next to each one:
|
||||
| Panel | Key |
|
||||
|-------------------|-------------|
|
||||
| **Crop & Rotate** | `C` |
|
||||
| **Remove** | `H` |
|
||||
| [**Remove**](remove-distractions.md) | `H` |
|
||||
| **Masking** | `M` |
|
||||
| **Presets** | `Shift`+`P` |
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@ order: 20
|
||||
- [Import photos](import-photos.md)
|
||||
- [Rate, flag and label photos](cull-photos.md)
|
||||
- [Edit a photo](edit-a-photo.md)
|
||||
- [Remove distractions from a photo](remove-distractions.md)
|
||||
- [Export photos](export-photos.md)
|
||||
- [Move over from Lightroom](import-lightroom-catalog.md)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: Remove distractions from a photo
|
||||
description: Paint over a small distraction, see when the repair takes effect, and adjust or undo it.
|
||||
order: 35
|
||||
verified: 0.8.0
|
||||
---
|
||||
|
||||
## Remove a distraction
|
||||
|
||||
Paint over the distraction and release the mouse button. LightCraft applies the repair automatically; there is no **Apply** button.
|
||||
|
||||
For example, remove a small speck from a plain background:
|
||||
|
||||
1. Double-click the photo in the grid to open it in **Detail** view. If it is not in your library yet, [import the photo](import-photos.md) first.
|
||||
2. Press `H`, choose **Window** > **Remove**, or click the **Remove** tool at the far right.
|
||||
3. Choose **Remove** at the top of the panel.
|
||||
4. Adjust **Size** so the brush covers the speck. Set **Opacity** to 100 for a full repair. **Feather** softens the edge.
|
||||
5. Click once for a round spot, or hold the mouse button and drag to paint over a longer distraction.
|
||||
6. Release the mouse button. LightCraft adds the repair and updates the photo. The painted area is not waiting for confirmation. Allow the preview to finish updating.
|
||||
7. Press `E` to open **Edit** and inspect the result without the repair outlines. Return with `H` if you need to [adjust the repair](#adjust-or-undo-a-repair).
|
||||
8. [Export the photo](export-photos.md) to save an edited copy. For a rendered photo with the repair, choose a format such as **JPEG**.
|
||||
|
||||
## Adjust or undo a repair
|
||||
|
||||
Each click or completed stroke creates one repair, called a spot in the panel.
|
||||
|
||||
1. Open **Remove** and click the repair's pin on the photo to select it. Clicking an existing pin selects that repair instead of adding another one.
|
||||
2. Change **Size**, **Feather** or **Opacity** to adjust the selected repair. Drag its target pin to move the repaired area.
|
||||
3. For **Remove**, **Heal** or **Clone**, drag the source outline to a cleaner area if the copied detail looks wrong. Or click **Refresh Source (/)** to ask LightCraft to choose another source. It may report that no other source fits.
|
||||
4. To replace the painted shape or try another mode, click **Delete (⌫)**, choose the mode, and paint again. The mode buttons choose the mode for new repairs.
|
||||
5. To undo your last change, choose **Edit** > **Undo**. To delete a particular repair, select its pin and click **Delete (⌫)**.
|
||||
|
||||
## Other repair modes
|
||||
|
||||
Choose the mode before painting a new repair.
|
||||
|
||||
| Mode | What it does in LightCraft 0.8.0 |
|
||||
|---|---|
|
||||
| **Remove** | Chooses a nearby source patch and blends its detail with the tone around the repaired area. You can move or refresh the source afterward. |
|
||||
| **Content-Aware** | Rebuilds the painted area from patches of its surroundings. It does not use one source patch, so moving or refreshing a source does not change the fill. |
|
||||
| **Heal** | Uses the same blending as **Remove** in this release. It also starts with an automatically chosen source that you can move or refresh. |
|
||||
| **Clone** | Copies the source patch's colour and detail, with the edge and strength controlled by **Feather** and **Opacity**. It also starts with an automatically chosen source. |
|
||||
|
||||
## Common questions
|
||||
|
||||
### I painted an area. Why does it look unchanged?
|
||||
|
||||
1. Release the mouse button and let the preview update.
|
||||
2. Check **Opacity**. At 0, a repair has no visible effect. Select the repair and raise it to 100.
|
||||
3. Check that the brush covers the distraction. Clicking an existing pin only selects a repair.
|
||||
4. Turn off **Visualize Spots (A)** to judge the normal photo. This black-and-white view helps reveal specks; it does not apply a repair.
|
||||
5. If **View** > **Show Original** is on, choose it again to return to the edited photo.
|
||||
6. If the copied patch is unsuitable, adjust its source or delete the repair and paint again.
|
||||
|
||||
### Is there an Apply button?
|
||||
|
||||
No. A click adds a round repair. A brush stroke is applied when you release the mouse button. Selecting another panel does not discard it.
|
||||
|
||||
### Why do I see circles, pins or an arrow?
|
||||
|
||||
These mark the repaired area and, for a selected repair that uses one, its source. They let you select and move the repair. Press `E` to inspect the photo without them. They are not part of the exported photo.
|
||||
|
||||
### How do I see the original?
|
||||
|
||||
1. Choose **View** > **Show Original** to see the original.
|
||||
2. Choose **View** > **Show Original** again to return to the edited photo.
|
||||
|
||||
### Is my original photo overwritten?
|
||||
|
||||
No. Repairs are stored as edits in the library. Export an edited copy when you want a file to share; see [Export photos](export-photos.md).
|
||||
@@ -14,6 +14,7 @@ You import photos, sort them with ratings, flags and albums, and edit them witho
|
||||
- [Import photos](guides/import-photos.md)
|
||||
- [Rate, flag and label photos](guides/cull-photos.md)
|
||||
- [Edit a photo](guides/edit-a-photo.md)
|
||||
- [Remove distractions from a photo](guides/remove-distractions.md)
|
||||
- [Export photos](guides/export-photos.md)
|
||||
- [Move over from Lightroom](guides/import-lightroom-catalog.md)
|
||||
- [A raw photo opens as "Preview only"](troubleshooting/preview-only-raw.md)
|
||||
|
||||
+8
-1
@@ -111,7 +111,9 @@ are the same in every version, so serve them with `Cache-Control: no-cache` (not
|
||||
grant it, a notice says the library may be evicted under storage pressure.
|
||||
- **File ▸ Back Up Library…** downloads a zip of the library files and every stored original
|
||||
(`originals/<hash>/<file name>`, so an unzipped backup is browsable; entries are stored, not
|
||||
compressed; at most 4 GB). **File ▸ Restore Library from Backup…** reads such a zip into a
|
||||
compressed; at most 4 GB). While a save is failing it refuses to start and says how many
|
||||
changes are unsaved and why: the zip copies the stored library files, so it would miss them
|
||||
(issue #1508). **File ▸ Restore Library from Backup…** reads such a zip into a
|
||||
new library folder (`library-restored-<time>/`; originals already stored are skipped, every
|
||||
entry's checksum is verified, and each restored original must match its content hash) and
|
||||
only then switches to it (`active-library` names the folder in use) and reloads: the previous
|
||||
@@ -121,6 +123,11 @@ are the same in every version, so serve them with `Cache-Control: no-cache` (not
|
||||
writes, so commands report `saved in memory but not written`, the top bar shows the unsaved
|
||||
warning (as on the desktop, see `docs/control-protocol.md`), and saving is retried every
|
||||
2 s until it works (`web.stats` → `saveError`).
|
||||
- A failed read at start-up is not taken for a missing file: only `NotFoundError` means absent.
|
||||
When a stored library file (or the `active-library` pointer) exists but can't be read, the
|
||||
library isn't opened and nothing is saved, so the stored copy stays as it was; the window says
|
||||
which file failed (issue #1507). A thumbnail index that can't be read keeps the stored
|
||||
thumbnails instead of deleting them as unindexed.
|
||||
- A picked or dropped photo whose bytes can't be stored is not added (it would be gone after
|
||||
a reload); a notice says why.
|
||||
- One tab at a time: the page holds a Web Lock (`navigator.locks`, `lightcraft-library`); a
|
||||
|
||||
@@ -1,7 +1,159 @@
|
||||
# What's new in LightCraft
|
||||
|
||||
## 2026-10-21
|
||||
|
||||
### Sharper resampling in HDR and panorama merges
|
||||
- Merged photos (HDR and panorama) are resampled with a sharper filter (Lanczos-3 instead of bilinear). Moving a
|
||||
photo by half a pixel used to lose a third of its finest detail; now it keeps most of it, without halos around
|
||||
hard edges such as clipped highlights.
|
||||
|
||||
### Scrolling past editing sliders
|
||||
- Leaving a selected slider disarms its vertical mouse-wheel adjustment (issue #1355), even when the pointer leaves
|
||||
and comes back between two screen updates. Scrolling back over it now scrolls the panel instead of unexpectedly
|
||||
changing the photo. Click or drag the slider again to adjust it with the vertical wheel; keyboard arrow
|
||||
adjustments and an ongoing drag still work. A slider reached with Tab takes the arrow keys but not the wheel:
|
||||
click it to scroll-adjust it. Sideways scrolling still adjusts the slider under the pointer without selecting it
|
||||
first.
|
||||
|
||||
### A pinch no longer re-renders the photo at every zoom step
|
||||
- Pinching the loupe now keeps the size the fit view is rendered at until the gesture is quiet, as it already did for
|
||||
the render sizes and windows. Before, each frame of a pinch asked for a new full-frame render at a new size.
|
||||
|
||||
### Fujifilm lens corrections that match the camera
|
||||
- X-T4 raws now read the lens's own distortion and vignetting tables the way the camera does. Many photos from zoom
|
||||
lenses got no distortion correction at all (more than half of those checked from the XF16-55 and XF70-300), and
|
||||
corners came out up to 6.5 % brighter than in the camera's JPEG. The corrected photo now lines up with the camera's
|
||||
framing. Other recent Fujifilm bodies whose tables have nine knots are read the same way, but only the X-T4 was
|
||||
measured. Thumbnails are redrawn when the library next shows them.
|
||||
|
||||
### Reference view reliability
|
||||
- Escape now leaves Reference for Detail without changing the active photo or its edits; the next
|
||||
Escape returns to the grid. A dialog, tool or Lights Out still handles Escape first.
|
||||
- Opening or creating a different library clears the pinned reference. Photo IDs belong to one
|
||||
catalog; a pin no longer silently becomes another library's photo with the same numeric ID.
|
||||
- Missing or Recently Deleted photos cannot be pinned. Deleting the pinned reference stops drawing
|
||||
it; re-entering Reference chooses a live photo, and restoring the deleted photo makes it pinnable again.
|
||||
In Recently Deleted, Set as Reference Photo is greyed out, and a refusal says to restore the photo.
|
||||
- Switching library from Reference view goes to the grid instead of an empty Reference canvas. A refused
|
||||
or failed switch keeps both the reference and the Compare pair.
|
||||
- `photo.setReference` refuses an `id` that is not a photo id (a string, a negative or fractional
|
||||
number) instead of pinning the active photo; only an absent or null `id` means the active photo.
|
||||
|
||||
## 2026-10-20
|
||||
|
||||
### Smart albums check File Path rules faster
|
||||
- A smart album rule on File Path (contains, doesn't contain, is, isn't, starts with, ends with, is empty, isn't
|
||||
empty) no longer builds new text for every photo. On a synthetic library of 312,000 photos, a File Path "contains"
|
||||
or "doesn't contain" rule took about 17 ms per pass instead of about 62 ms, around 3.5 times faster, with the same
|
||||
photos matched; about 6 ms of that is only reading the paths. Paths and rule text with letters beyond A–Z are
|
||||
compared as before.
|
||||
|
||||
## 2026-10-19
|
||||
|
||||
### AI Denoise on DirectX 12 with NVIDIA GTX 10-series cards
|
||||
- AI Denoise on the GPU under DirectX 12 (the Windows default) gives the same result as on the CPU again on an NVIDIA
|
||||
GeForce GTX 1070. The driver ran the denoise network's output stores wrong, so every second pixel's colour
|
||||
channels came out shifted and the denoised photo was wrong without any error. Vulkan was not affected.
|
||||
|
||||
### The Before label stays in view when zoomed in
|
||||
- Zoomed in, the "Before" label (\ key or the Before view) now sits at the canvas' corner instead of leaving the
|
||||
screen with the photo's corner (issue #2267).
|
||||
|
||||
### Synchronize Folder on a folder that is up to date
|
||||
- When the scan finds nothing to import, relink, remove or read, the Synchronize Folder dialog says the library is up
|
||||
to date with the folder and has one Close button. It used to show four greyed choices, two of them still ticked,
|
||||
and a Synchronize button that started a job with nothing to do. Files it skipped (already in the library elsewhere,
|
||||
or unreadable) are still listed.
|
||||
|
||||
|
||||
### Hidden Local locations come back one at a time
|
||||
- With several locations removed from Local, "Show N hidden locations" now lists them, so each comes back on its own
|
||||
(Restore All is there too). The row used to bring every hidden location back at once (issue #2114).
|
||||
|
||||
## 2026-10-18
|
||||
|
||||
### Thumbnails of Lightroom HDR and panorama merges in a second
|
||||
- Grid and filmstrip thumbnails of DNGs that Lightroom merged (HDR, Panorama, HDR Panorama) now develop from the
|
||||
small copies of the raw image Lightroom keeps inside the file (256 to 2048 px), not from the whole merge. A 23836 ×
|
||||
7078 drone panorama's 512 px thumbnail took 38 s and now takes about a second, with the same look; your edits still
|
||||
apply. Loupe previews larger than the biggest copy, 1:1 views and exports still read the full image.
|
||||
|
||||
### Fewer false "not imported" warnings for Lightroom raws
|
||||
- Importing raws with Lightroom XMP sidecars no longer lists settings Lightroom writes only for itself as adjustments
|
||||
that weren't imported: `CompatibleVersion`, the camera and lens profile checksums, the lens profile's name, file and
|
||||
setup, the crop's "constrain to image" option, and the lens profile's strength when it is at its default of 100 %.
|
||||
A raw sidecar used to name all of them for every photo. Settings LightCraft really doesn't apply, such as `Look`,
|
||||
are still reported.
|
||||
|
||||
|
||||
### Fill Location from GPS: no more neighbouring town in City
|
||||
- The offline place names list towns of 15,000 people or more, so a photo taken in a smaller one got the nearest larger
|
||||
town in City, sometimes another municipality (issue #2242). The Fill Location tooltip and the command help now say so,
|
||||
and `metadata.reverseGeocode` takes `maxDistance` (metres): past it, City stays empty (State and Country still fill)
|
||||
and the reply names the town it found as `nearestCity`.
|
||||
|
||||
### A preset that couldn't be saved is no longer lost silently
|
||||
- When presets.json can't be written (a full disk, or the browser's storage quota), the new or changed preset now
|
||||
counts as unsaved, like other changes: the top bar warns, saving is retried, and quitting asks first (issue #2266).
|
||||
Before, the failure was only logged, and the preset could be lost without any warning.
|
||||
|
||||
### Portrait crop ratios stay portrait
|
||||
- A crop ratio no longer flips to the photo's orientation (issue #2253): after X turns the crop portrait, picking
|
||||
another ratio (4 × 5, 2 × 3…) keeps it portrait, as in Lightroom, and a Custom ratio taller than wide gives a
|
||||
portrait crop.
|
||||
|
||||
## 2026-10-16
|
||||
|
||||
### Synchronize Folder shows that it is listing the folder
|
||||
- A folder with tens of thousands of photos, above all on a network share, can take minutes to list before
|
||||
Synchronize Folder checks a single photo. Its row in the activity stack used to sit at "0 of 64085" with an empty
|
||||
bar all that time, as if it had stopped. It now says how many folders and files it has listed so far, with a bar
|
||||
that moves on its own, and then counts the photos it checks. The dialog says the same.
|
||||
- ✕ on the row stops the listing too, not only the checks after it.
|
||||
|
||||
### The photo grid keeps up with imports into a large library
|
||||
- While photos are imported into a large library, the grid takes in each batch as it arrives instead of reading
|
||||
every photo of the view again: at about 180,000 photos a batch cost the window a tenth of a second, over and over
|
||||
for as long as the import ran. Changing one photo's capture time or crop is quicker too.
|
||||
|
||||
## 2026-10-15
|
||||
|
||||
### Photo Merge takes each photo once
|
||||
- A merge that names the same photo more than once (through `ids` on the control channel, MCP or the CLI) now merges
|
||||
it once (issue #1947). Naming one photo three times no longer "merges" it with itself, and a long repeated list no
|
||||
longer decodes the photo again for every entry.
|
||||
|
||||
### Constrain Crop tightens the crop you see
|
||||
- Geometry ▸ Constrain Crop now changes the crop itself, as in Lightroom (issue #1927). Ticking it, or moving a
|
||||
Geometry slider, Upright or a lens correction while it is ticked, shrinks the crop about its centre until the blank
|
||||
corners left by the correction are gone, and the Crop tool shows the tighter crop. Before, it only applied when the
|
||||
photo was shown outside the Crop tool, so in the Crop panel, where the checkbox is, nothing seemed to change. A crop
|
||||
you draw by hand is kept as drawn, and unticking keeps the tighter crop.
|
||||
|
||||
### Web: no backup that misses unsaved changes
|
||||
- In the browser version, File ▸ Back Up Library… no longer reports success while a save is failing (storage full or
|
||||
cleared): the backup copies the stored library, so it left out the changes that couldn't be saved (issue #1508). It
|
||||
now refuses to start and says how many changes are unsaved and why. LightCraft retries the save with every change;
|
||||
back up once nothing is left unsaved.
|
||||
|
||||
### Web: an unreadable library is not opened
|
||||
- In the browser version, a library file that exists but can't be read at start-up is no longer taken for a missing
|
||||
one (issue #1507). LightCraft used to open the library without it, showing an incomplete catalog that the next save
|
||||
would write over the stored copy. It now leaves the library unopened, saves nothing and says which file couldn't be
|
||||
read; reload the page to try again.
|
||||
|
||||
## 2026-10-14
|
||||
|
||||
### Model downloads time out on a name that doesn't resolve
|
||||
- Model downloads (AI denoise, faces, SAM) give up on a server name that doesn't resolve within the connect timeout,
|
||||
and Cancel stops a download that is still looking the name up (issue #1532). The lookup used to block with no time
|
||||
limit, and each of a server's addresses then got the whole connect timeout of its own.
|
||||
|
||||
### Sony A7CR distortion correction
|
||||
|
||||
- Full-frame 3:2 Bayer A7CR raws with distortion Auto or forced Auto now use their embedded distortion table. The corrected render is closer to the camera JPEG geometry. Off files keep their original geometry. APS-C, aspect crops and lossless M/S remain excluded.
|
||||
- The warp also supplies the geometry needed by the lens-aligned camera-look fit in PR #1586. The pairing code is a separate change. Details and numeric validation are in `docs/sony-lens-corrections.md`.
|
||||
|
||||
### Lightroom radial masks on the right side
|
||||
- Radial masks brought over from Lightroom Classic (catalog import, XMP sidecars, edits inside DNGs and presets) now
|
||||
apply their effect inside the circle, as Lightroom does, instead of outside it (issue #1815). A mask inverted in
|
||||
@@ -152,10 +304,21 @@
|
||||
⇧-click and the line tapers from the old values to the new ones. The whole line is one undo step.
|
||||
`mask.brushStroke {connect: true}` does the same for agents.
|
||||
|
||||
### Local folders you browse are kept
|
||||
- Browsing a folder under Local now records it as browsed, so the photos in folders you use aren't forgotten after
|
||||
the number of days set in Settings → Performance. Only the `library.browse` command recorded it before, so a
|
||||
folder browsed every day from the sidebar was forgotten anyway and read again from scratch on the next visit,
|
||||
which is slow on a network share.
|
||||
|
||||
### Hard-edged Heal and Clone spots
|
||||
- A spot with Feather at 0 no longer draws black pixels exactly on its radius. This also fixes spots at 0% Opacity
|
||||
changing pixels along their edge. The hard edge now leaves pixels on the radius unchanged.
|
||||
|
||||
### Search finds people
|
||||
- Typing a person's name in Search Photos finds the photos with that named face (issue #1449).
|
||||
- Quotes keep several words together: `person:"Jane Doe"` finds exactly that person, `"jane doe"` those words side
|
||||
by side, and `keyword:"new york"` a keyword of two words.
|
||||
|
||||
### Where the camera focused
|
||||
- **View ▸ Show Focus Point** (⌥F) draws the autofocus frame the camera recorded over the photo, in Detail, Compare
|
||||
and Survey, following rotation and crops; the Info panel says how it focused (`AF-C · Wide · Animal Eye Tracking`).
|
||||
|
||||
+30
-2
@@ -10,7 +10,7 @@ other tools), and let LightCraft pick up edits made elsewhere.
|
||||
|---|---|
|
||||
| Name | `<stem>.xmp` by default (`IMG_0001.CR3` → `IMG_0001.xmp`); `<file>.xmp` (`IMG_0001.CR3.xmp`) with `naming: "full"`. Reading accepts either (preferred first) and `.XMP`. With stem naming, files sharing a stem don't share a sidecar: see *Shared names* below. |
|
||||
| Write | `photo.saveMetadataToFile {ids?}` (Photo ▸ Save Metadata to File, ⌘S), or automatically after every change with `library.xmpPreferences {autoWrite: true}` (File ▸ Automatically Write Changes into XMP). Slider drags are written once, when the drag ends; undo/redo rewrite the sidecar. An existing sidecar is **merged into, never replaced** (see *Saving into an existing sidecar*). Written atomically (temp file, fsync, rename). The result lists `written`, `merged` and `backups`. |
|
||||
| Read | On import (`library.import`, the report counts `sidecars`), and `photo.readMetadataFromFile {ids?}` (one undo step). For raw/DNG files without a sidecar, the XMP embedded in the file is used. `photo.readMetadataFromFile` never writes the sidecar, even with auto-write on: writing back what it has just read would add `lc:settings`, which later reads prefer over another app's `crs:` edits (issue #934). |
|
||||
| Read | On import (`library.import`, the report counts `sidecars`), and `photo.readMetadataFromFile {ids?}` (one undo step). Manual rereading uses embedded XMP in raw/DNG and JPEG files when no sidecar exists. A discovered sidecar that cannot be read or parsed is reported in `failed`, without applying embedded values. `photo.readMetadataFromFile` never writes the sidecar, even with auto-write on: writing back what it has just read would add `lc:settings`, which later reads prefer over another app's `crs:` edits (issue #934). A file arriving in an import is ranked as if it were catalogued already: it reads its own `<file>.xmp` when another file of its stem (a raw first) owns the shared stem sidecar (issue #1905). |
|
||||
| Preferences | `library.xmpPreferences {autoWrite?, naming?: stem\|full}`, stored in the library's `prefs.json`. |
|
||||
|
||||
What we write (standard namespaces, so other tools can read the metadata):
|
||||
@@ -41,6 +41,29 @@ When foreign `crs:` settings are only partly transferable, `library.import` incl
|
||||
report. Warnings identify unsupported profile names, saved Upright geometry that will be recalculated, and other
|
||||
unmapped adjustments; they do not change the source packet or become catalog edits.
|
||||
|
||||
Manual rereading and undo leave the JPEG original unchanged. Manual rereading also leaves sidecar files unchanged,
|
||||
even with automatic XMP writing on. With automatic XMP writing off (the default), undo/redo leave sidecars unchanged;
|
||||
with it enabled, subsequent edits and undo/redo can trigger the existing sidecar writer. `photo.allMetadata` uses
|
||||
its already loaded file bytes for embedded XMP, with an existing sidecar taking precedence.
|
||||
|
||||
Manual JPEG rereading uses the first standard XMP APP1 before the first SOS (scan) or EOI (end) marker.
|
||||
Scanning is limited to a 16 MiB header span, including SOI, segment headers and skipped payloads, and 4096
|
||||
marker/fill steps after SOI. Non-APP1 payloads are skipped without loading them. Each APP1 payload must be
|
||||
complete and is at most 65,533 bytes. Header extraction succeeds for the first complete
|
||||
standard XMP packet within both limits, including a packet ending exactly at either limit; otherwise it
|
||||
fails without a whole-file fallback, including on I/O errors or an incomplete APP1 whose available bytes
|
||||
form parseable XMP. A valid JPEG with
|
||||
XMP beyond those limits therefore cannot be manually reread through this path. Extended XMP assembly and
|
||||
packets between scans are not supported by manual JPEG rereading.
|
||||
|
||||
Packet completeness is stricter than the shared best-effort JPEG extractor; it is not strict whole-JPEG
|
||||
validation. A complete early XMP packet can be returned without examining the remaining file. Import and
|
||||
`photo.allMetadata` retain their existing extraction paths. Command regressions in
|
||||
`crates/engine/src/tests_xmp.rs` cover metadata/undo and sidecar precedence, an over-nested XMP packet and a
|
||||
truncated APP1 without an XMP signature. Separate reader cases in `sidecar::jpeg_header_tests` use small
|
||||
counted readers and injected limits for span/step boundaries, complete versus truncated APP1, early stopping,
|
||||
short reads and I/O errors.
|
||||
|
||||
## Saving into an existing sidecar
|
||||
|
||||
A sidecar may already hold another application's data — e.g. its `crs:` develop settings and `xmpMM:History` — often
|
||||
@@ -74,6 +97,10 @@ library share a stem, the stem sidecar belongs to one of them — a raw first, o
|
||||
the others write and read `<file>.xmp` (`IMG_0001.JPG.xmp`), falling back to the stem sidecar for reading when they
|
||||
have none. So saving one photo never overwrites the other's metadata (`Session::sidecar_naming`).
|
||||
|
||||
An import ranks the files it is adding the same way, before they are catalogued: a TIFF or JPEG arriving beside a
|
||||
raw of its stem reads its own `<file>.xmp` and takes nothing from the raw's `IMG_0001.xmp`, whether it arrives
|
||||
with the raw or after it (issue #1905).
|
||||
|
||||
## Face regions (MWG)
|
||||
|
||||
LightCraft reads face, pet, focus and barcode regions in the Metadata Working Group's region schema
|
||||
@@ -192,11 +219,12 @@ mask: `CorrectionName` → name, `CorrectionAmount` → Amount, inactive correct
|
||||
| `LocalExposure2012` | `adjust.exposure` | stored as a fraction, ×4 EV |
|
||||
| `LocalContrast2012`, `LocalHighlights2012`, `LocalShadows2012`, `LocalWhites2012`, `LocalBlacks2012`, `LocalClarity2012`, `LocalTexture`, `LocalDehaze`, `LocalTemperature`, `LocalTint`, `LocalSaturation`, `LocalHue`, `LocalSharpness`, `LocalLuminanceNoise`, `LocalMoire`, `LocalDefringe`, `LocalToningSaturation` | the matching `adjust.*` | stored −1..1, ×100 |
|
||||
| `LocalToningHue` | `adjust.color_hue` | degrees |
|
||||
| other `Local*` sliders (`LocalGrain`…) | — | no counterpart in our masks: reported as `Mask: LocalGrain` when set (`LocalCurveRefineSaturation` 100 is neutral) |
|
||||
| `Mask/Gradient` (`FullX/Y`, `ZeroX/Y`) | linear gradient | full effect at the Full point |
|
||||
| `Mask/CircularGradient` (`Top/Left/Bottom/Right`, `Angle`, `Feather`, `Flipped`) | radial gradient | box fractions → long-edge radii (presets assume 3:2; sidecars use the photo's shape). `Flipped="true"` means effect inside: in `MaskGroupBasedCorrections` with `Version` 2 (what Lightroom writes for an ordinary radial) and in the legacy `CircularGradientBasedCorrections` Radial Filter, whose default (no `Flipped`) is outside. A `MaskGroupBasedCorrections` radial without `Version` is inside whatever `Flipped` says, and its `Feather` is halved (Lightroom rescales it that way when it upgrades one). Inverting is `MaskInverted` |
|
||||
| `Mask/Paint` (`Dabs` "d x y", `Radius`, `Flow`, `CenterWeight`, `MaskValue`) | brush | one brush component per correction; `MaskValue` ≤ 0 erases |
|
||||
| `Mask/Image` `MaskSubType` 1 / 2 | Subject / Sky | other AI selections are reported, not guessed |
|
||||
| `Mask/RangeMask` `Type` 2 / 3 (`LumRange`, `DepthRange`) | luminance / depth range | lightness converted to our range scale; colour ranges are reported |
|
||||
| `Mask/RangeMask` `Type` 2 / 3 (`LumRange`, `DepthRange`) | luminance / depth range | lightness converted to our range scale; a range at full strength at white (third `LumRange` value 1) stays open above our scene white; colour ranges are reported |
|
||||
| `MaskBlendMode` 0 / 1 / 2, `MaskInverted` | add / subtract / intersect, invert | |
|
||||
|
||||
Applying a preset adds its masks to the photo's own (masks the photo already has are not added twice), and the Amount
|
||||
|
||||
Reference in New Issue
Block a user