mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-13 09:20:07 -06:00
* LR-PROF-IMPORT: import the user's own DNG camera profiles (.dcp)
profile.import (File > Import Profiles & Presets, drag & drop) now also
reads DNG Camera Profiles: the user's own .dcp files, e.g. made with a
colour-checker tool. They appear in the profile browser and menu under
Camera Profiles with dcp:<camera>-<file> ids; profiles.camera lists them
and profile.deleteCamera / profile.deleteImported remove them. A library
on disk keeps a copy in Profiles/. No profile ships with the app and the
repository holds none: tests build theirs in memory (write_dcp).
lightcraft_raw::dcp parses the file per DNG spec ch. 6 (magic RC in
place of 42; IFD 0 read through the existing DNG colour builders) and
DcpProfile::applied_to puts its matrices, illuminants and look in place
of a raw file's, keeping the shot's white balance, analog balance and
baseline exposure.
Choosing one (develop.profile) replaces the raw file's colour model when
it is decoded, before the starting colour and the white-balance scale
(WbScaleOf) are worked out, so the matrix path takes over from the fitted
JPEG look (CR3/CR2/ARW...) and white balance follows the profile. A
profile without a tone curve of its own (most chart-made ones) keeps the
photo's contrast: the DNG's tone curve, else the tone of the camera-JPEG
fit. The profile travels with SourceRef::File into FileLoader /
PairLoader (new Option<&DcpProfile> argument); a decoded source records
the profile it was made with and is not reused for another. Smart
previews and the web render worker keep the file's own colour; rendered
(non-raw) photos ignore it.
This reverses the documented "Adobe profile formats deliberately
unsupported" stance for user-supplied files only; docs updated.
Split out of #1012.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: build against upstream main (kelvin_presets, 3-arg loaders)
DcpProfile::applied_to keeps the file's white-balance presets (new
ColorData::kelvin_presets upstream): they are camera facts like as-shot
neutral. Upstream's new FileLoader test closures take the profile argument.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: hover previews and browser thumbnails develop the hovered camera profile
The loupe's hover preview (preview_job) and the profile browser's thumbnails (variant_job) took
the photo's own decoded source, so hovering an imported camera profile showed the built-in colour
(and hovering a built-in profile while one was applied kept the camera profile's). Both now decode
as the hovered settings develop: the source follows the settings' camera profile, as the main
render does, and a variant's cache key already follows its settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: build against upstream main (face-tile test loader and render_bench take the camera profile)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: the catalog's staging-link test module is Unix-only (unused import broke clippy on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: same-folder export test expects the '/'-joined subfolder (failed on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: folder-drop tests build widget ids with the Folders tree's '/' paths (failed on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: rustfmt after merging main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a real D750 camera profile as a pinned test corpus, checked against Lightroom
dieli's CC0 Nikon D750 ColorChecker set (Calibrite PROFILER dual-illuminant DCP,
matrix-only; the two chart NEFs; Lightroom Classic 15.6 renders with the profile)
is fetched by cargo xtask corpus --download into corpus/d750-colorchecker/, pinned
to commit f37f4054 and by sha256; nothing is committed.
Tests (skip without the corpus): the profile parses as matrix-only with
Cool White Fluorescent + D55; the matrices blend by inverse temperature; the
matrix-only path inverts the colour matrix and adapts to D50; it takes over the
chart NEFs' colour; imported and applied end to end, the charts develop within
mean dE2000 3.84 (flash) / 3.35 (indoor) of Lightroom's renders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a single .dcp file is a camera profile only, not also an unreadable LUT
profile.import read every plain file that was neither a folder nor a .zip as a .cube LUT, so importing
one .dcp imported the camera profile and also reported it as failed ("stream did not contain valid
UTF-8"). Only .cube files are read as LUTs now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: review fixes for imported camera profiles
- A library moved or restored elsewhere finds its camera profiles in its own Profiles/ folder.
- A grid thumbnail is cut from the decoded preview only when that preview used the same camera profile.
- Render and source cache keys carry a fingerprint of the profile file, so a deleted or replaced profile never
has its old renders stand in.
- Camera profiles can be favourites, and favourites/recent entries survive reopening the library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: smart previews without the camera profile, current-profile source facts, unsupported files reported
CodeRabbit review of 4ef025d9:
- Build Smart Previews develops the proxy without the photo's imported camera profile and never takes a cached
source decoded with it (the denoised picture is kept); before, only the stale-proxy refresh left it out.
- Session::source_info reports decoder facts only from a cached source developed with the camera profile the
photo uses now, else the catalog's.
- profile.import reports a file that is neither .cube, .dcp nor .zip as failed instead of passing over it.
Each with a regression test in tests_dcp.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: the Before side decodes with its own camera profile; a failed profile copy fails that file only
- build_job (and region_job_before through it) picks the Before render's source with the Before
settings, so it never reuses or decodes a source developed with the edit's camera profile.
- profile.import: a .dcp whose copy into the library fails is reported in `failed`; the profiles
imported with it stay imported and are saved, and nothing half-written is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a cached smart preview stands in while the original is offline
A smart preview carries no camera profile, so with an imported profile the profile check
rejected the cached proxy and every render decoded it again (it gives the same picture). It
now stands in, for its own level and for the thumbnail cut from it, while the original is
known to be offline; otherwise the profile check is as before, so once the original is back
the photo is decoded from it with its profile. Photos without an imported profile keep main's
behaviour. Test `tests_dcp::a_cached_smart_preview_stands_in_while_the_original_is_offline`
(fails without the fix).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: deleting a camera profile removes the library's copy before forgetting it
profile.deleteCamera now removes the library's copy of the profile file
first. A copy that is already gone is fine; any other error (file in use,
no access) fails the command and keeps the profile listed, so the library
never holds a profile file it no longer lists. Test:
a_profile_whose_copy_cannot_be_deleted_stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: deleting a camera profile never follows `..` out of the profiles folder
A listed path with a `..` component can start with the profiles folder and
still lead out of it; such a path is not treated as the library's copy, so
deleting the profile leaves that file alone. Test:
deleting_a_profile_never_follows_dot_dot_out_of_the_profiles_folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: both tone fits of a DNG see its lens opcodes; no side effect in the profile-tag comparison
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a hover preview's key carries the camera profile file's fingerprint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: unusable camera profiles not offered; file's WB scale and the profile's tone kept
Review of #1063 (CodeRabbit, outside-diff findings):
- a camera profile whose file is missing or damaged when the library opens
is no longer offered in the profile browser or accepted by develop.profile
(it silently developed with the raw's own colour); the entry stays, so it is
offered again once the file is back.
- a raw on the relative white-balance scale stays relative with a camera
profile (its matrices turned it to Kelvin, so existing edits were read as
absolute temperatures and the catalog and quick previews disagreed).
- with a camera profile its tone curve, then the tone kept for a curve-less
profile, come before the fitted camera look's tone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: one Camera Profiles group; the web loader keeps a raw's own colour
Review of #1063 (CodeRabbit):
- a LUT group named "Camera Profiles" that didn't sort last made the profile
menu list that group twice; it is now one group, last, with both kinds.
- without web workers a render ran inline and the web loader applied the
selected camera profile, unlike the worker (the documented web behaviour
keeps the raw's own colour); the loader now leaves it out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: camera profile files are read at most to the 64 MiB limit
A .dcp larger than the parser accepts was read whole into memory before
being refused. One bounded read (lightcraft_raw::dcp::read_dcp) stops one
byte past the limit and is used for import, reopening a library and the
duplicate check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a test that a hostile hue/saturation map header in a camera profile drops the map (FredApps, #1063)
Dimensions far past the data, past u32 products or zero, an entry count of 4 billion values and a
data offset past the end each drop the map while the profile's matrix still reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: parity checklist keeps both sides of the merge
* LR-PROF-IMPORT: tests added on main take the file loader's camera-profile argument
* Merge main; a thumbnail test takes the file loader's camera-profile argument
* Docs: one camera colour row, with main's notes and the import note
* Process key tests take the profile salt argument
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
84 lines
6.8 KiB
Markdown
84 lines
6.8 KiB
Markdown
# File format parity
|
||
|
||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (LR-PROF-IMPORT: the user's own `.dcp` camera profiles import; Render to DNG as an export format) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||
|
||
Every format Lightroom reads or writes, with our support, fidelity and tests. Camera raw formats are
|
||
deep enough to have their own document: [raw-parity.md](raw-parity.md).
|
||
|
||
**How measured:** the target's document types come from the installed Lightroom 9.6
|
||
`Info.plist` (`CFBundleDocumentTypes`: catalog `mcat`, `xmp`, toolkit / module / library bundles, and
|
||
the raw-and-image list: jpg, tif, dng, heic and 27 raw extensions) plus Lightroom Classic's documented
|
||
import and export formats. Ours come from `crates/codecs/src/sniff.rs`, `crates/codecs/src/encode.rs`,
|
||
`crates/engine/src/export.rs`, `crates/engine/src/import.rs` and the tests named below.
|
||
|
||
## Images (non-raw)
|
||
|
||
| Format | Target reads | Target writes | LightCraft reads | LightCraft writes | Notes / tests |
|
||
|---|---|---|---|---|---|
|
||
| JPEG | ✅ | ✅ | ✅ (parallel decoder, ICC, EXIF orientation) | ✅ quality, size limit, ICC, metadata; Huffman tables optimized per image (issue #1373) | `crates/codecs/src/jpeg.rs`, `crates/codecs/src/jpeg_par.rs`, export tests |
|
||
| TIFF | ✅ 8/16/32-bit | ✅ 8/16-bit, LZW/ZIP | ✅ 1–64-bit integer, 16–64-bit float, bilevel, CCITT fax, WhiteIsZero | ✅ 8/16-bit, 32-bit float linear, none / LZW / ZIP | `crates/tiff`, `crates/codecs/src/tiff_codec.rs` |
|
||
| PNG | ✅ | ✅ | ✅ | ✅ | |
|
||
| PSD / PSB | ✅ (maximized-compatibility composite) | ✅ (Classic: PSD export, edit-in round trip) | ✅ merged composite | ⬜ (external-editor round trip writes 16-bit TIFF) | `crates/codecs/src/psd.rs` |
|
||
| HEIC / HEIF | ✅ | ⬜ (Classic) / HDR only | ✅ in every release build (`crates/heif`, heic-rs) | ⬜ | `heif` feature on by packaging scripts |
|
||
| AVIF | ✅ | ✅ (HDR) | ⬜ recognised, not decoded (no permissive pure-Rust AV1 decoder) | ✅ 8/10-bit, PQ HDR | |
|
||
| JPEG XL | ✅ | ✅ | ✅ (`jxl-oxide`) incl. DNG 1.7 JXL tiles | ✅ lossy / lossless, 8/16-bit, PQ HDR (`jixel`, native only; ⬜ web) | `crates/engine/src/tests/export.rs` |
|
||
| WebP | not in its document types | ⬜ | ✅ | ✅ | beyond target |
|
||
| GIF, BMP | ⬜ | ⬜ | ✅ | ⬜ | beyond target |
|
||
| DNG (from raw) | ✅ | ✅ incl. lossy, embedded original | ✅ | ✅ uncompressed, LJ92, Deflate or lossy (JPEG tiles, optional long edge); ⬜ embedded original | `crates/raw/src/dngwrite.rs` |
|
||
| Render to DNG (15.5) | — | ✅ | — | 🟡 export format "Rendered DNG" (linear float DNG, edit baked in); not yet added to the catalog | `crates/raw/src/dngwrite.rs` (`write_rendered_dng`) |
|
||
| Original + sidecar | — | ✅ | — | ✅ | |
|
||
| HDR gain map JPEG (ISO 21496-1) | ✅ | ✅ | ✅ (gain map read) | ✅ | `crates/codecs/src/gainmap.rs` |
|
||
| PQ / HLG HDR | ✅ | ✅ AVIF, JXL, TIFF | — | 🟡 PQ AVIF, PQ JPEG XL, float TIFF; no HLG | |
|
||
| Contact sheet / print PDF | — | ✅ (Print module: PDF / JPEG / printer) | — | 🟡 paginated contact-sheet PDF (A4 / Letter, 150 dpi, sRGB) | `crates/engine/src/contact_sheet.rs` |
|
||
| Video (MP4, MOV, AVCHD…) | ✅ playback, trim, edit | ✅ export | 🟡 catalogued (kind, duration) | ⬜ | see video gap |
|
||
|
||
## Colour spaces and profiles
|
||
|
||
| Feature | Target | LightCraft |
|
||
|---|---|---|
|
||
| Export colour spaces | sRGB, Adobe RGB, Display P3, ProPhoto, Rec.2020, custom ICC | ✅ sRGB, Display P3, Adobe RGB-compatible, ProPhoto, Rec.2020 with gamut mapping (CPU and GPU), embedded ICC; ⬜ custom ICC output profiles; AVIF stays sRGB |
|
||
| Input ICC (JPEG / TIFF / PNG) | ✅ | ✅ matrix / TRC and LUT profiles (moxcms) |
|
||
| Soft proofing with printer profiles | ✅ | 🟡 |
|
||
|
||
## Metadata, sidecars, presets and catalogs
|
||
|
||
| Format | Target | LightCraft | Notes |
|
||
|---|---|---|---|
|
||
| XMP sidecars (`crs:` develop settings, ratings, keywords, regions) | read / write | ✅ read / write, manual (⌘S) and automatic | [xmp-interop.md](xmp-interop.md) |
|
||
| EXIF / IPTC / XMP embedded on export | ✅ | ✅ with metadata filters | `crates/meta` |
|
||
| Develop presets `.xmp` | ✅ | ✅ import (mapping of supported settings) | |
|
||
| Legacy presets `.lrtemplate` | ✅ (converted) | ✅ import | |
|
||
| Camera / creative profiles (`.dcp`, `.xmp` profiles) | ✅ | 🟡 the user's own `.dcp` camera profiles import (`profile.import`) and replace a raw file's colour model; none ship with the app; `.xmp` profiles ⬜; `.cube` LUT profiles ✅ | |
|
||
| Lens profiles `.lcp` | ✅ | ⬜ deliberately | |
|
||
| Other apps' presets | — | ✅ Luminar `.lmp` / `.mplumpack`, `.zip` bundles | beyond target |
|
||
| Lightroom Classic catalog `.lrcat` | native | 🟡 import (ratings, flags, labels, keywords, collections, virtual copies, supported develop settings; read-only pure-Rust SQLite incl. WAL) | [lightroom-catalog-import.md](lightroom-catalog-import.md) |
|
||
| Lightroom (cloud) library `.lrlibrary` | native | ⬜ | cloud library format, local cache only |
|
||
| Write back to `.lrcat` | native | ⬜ (one-way migration by design) | |
|
||
| Keyword lists (text) | import / export | ✅ | |
|
||
| GPX track logs | ✅ | ✅ | `crates/meta/src/gpx.rs` |
|
||
| Export presets, metadata presets, filename templates | ✅ (`.lrtemplate`) | ✅ own JSON | not interchangeable |
|
||
| Lightroom plug-ins (`.lrplugin`, `.lrdevplugin`) | ✅ | ⬜ | see ecosystem in [target-app-parity.md](target-app-parity.md) |
|
||
|
||
## Scores
|
||
|
||
| Part | Weight | Ready for real work | Kind |
|
||
|---|---:|---:|---|
|
||
| Standard image import / export | 35% | ~85% | estimated (table above; missing: AVIF read, Render to DNG in the catalog) |
|
||
| Sidecars, metadata, presets | 25% | ~85% | estimated |
|
||
| Lightroom catalog migration | 25% | ~50% | estimated (one-way, approximate rendering of unsupported settings) |
|
||
| HDR, print, video output | 15% | ~35% | estimated |
|
||
| **Non-raw file formats** | 100% | **~70%** | estimated; raw is in [raw-parity.md](raw-parity.md) (~50%) |
|
||
|
||
Remaining: **26–47 h** (AVIF decode 8–15 h if a permissive decoder is written or found; PSD write 4–8 h; lossy DNG and Render to DNG 4–8 h; HLG 3–5 h; catalog import depth 8–12 h).
|
||
|
||
## Revision history
|
||
|
||
| Date | Change | Summary |
|
||
|---|---|---|
|
||
| 2026-10-12 | minor | LR-PROF-IMPORT: the user's own DNG camera profiles (`.dcp`) import as camera profiles; none ship |
|
||
| 2026-10-10 | major | Created from the code and the installed Lightroom's document types |
|
||
| 2026-10-10 | minor | JPEG XL export (#247): JXL write ✅ (native only), PQ JPEG XL HDR |
|
||
| 2026-10-12 | minor | Render to DNG: "Rendered DNG" export format (linear 16-bit float DNG of the rendered photo) |
|
||
| 2026-10-12 | minor | Lossy DNG export (JPEG tiles, `dngCompression: "lossy"`); PSD write had already landed (#953), so it leaves the remaining hours |
|
||
| 2026-10-13 | patch | JPEG write (#1373): Huffman tables made for each image, 12–14 % smaller files at the same pixels; statuses unchanged |
|