From b58873ef57b8537dadb43852418f82bf0ed54e76 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 3 Oct 2026 10:33:12 -0400 Subject: [PATCH] Spec baseline exposure, Camera Raw tone and profile sync (D21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit camera-profiles.md §11-§14 close what 0.20.0 left open. Baseline exposure is the file's plus the profile's offset, applied as a gain on the camera matrix, and a copied profile carries the DNG's baseline so a CR2 lands at the same brightness. The view transform gains a Camera Raw curve — the profile's ProfileToneCurve, else the ACR3 default — applied Camera Raw's way, on the outer channels in linear ProPhoto, and it is the default for every raw (D21, the user's choice). Profiles sync through .darkroom-derived/profiles on the server as a step of the derived sync pass, not as a catalog table. --- docs/dev/camera-profiles.md | 125 +++++++++++++++++++++++++++++++++--- docs/dev/requirements.md | 23 +++++++ 2 files changed, 138 insertions(+), 10 deletions(-) diff --git a/docs/dev/camera-profiles.md b/docs/dev/camera-profiles.md index 1667e57..7ece3bf 100644 --- a/docs/dev/camera-profiles.md +++ b/docs/dev/camera-profiles.md @@ -172,22 +172,17 @@ action sits on that line. ## 6. Not done, and why -- **`ProfileToneCurve` is read and ignored.** D19 gives tone to the view transform, one for every - body, and rejected per-body curves as its defaults. A profile's curve is a per-body curve. If it - comes back, it comes back as an option of the view transform, not as a stage here. The 6D's - Adobe Standard has none, so the case that matters today loses nothing. -- **`BaselineExposure` is not applied** (it is not today either). Adobe Standard was tuned with it, - and the 6D's is +0.25 EV. Separate change; it moves every photograph's brightness. +- ~~**`ProfileToneCurve` is read and ignored.**~~ *Done after 0.20.0: §12, D21.* It came back as + an option of the view transform, as this bullet said it would, and that option is the default + for raws. +- ~~**`BaselineExposure` is not applied.**~~ *Done after 0.20.0: §11.* - **The interpolation follows the as-shot neutral, not the white-balance slider**, as the matrix does. Camera Raw re-blends on every temperature change; doing so here means the matrix moves too, which is its own change. - **Masks select on the matrix's colour.** A colour-range mask sees colour before the profile, as it sees colour before every other operation. Deterministic, and a mask is drawn on the picture the user sees only approximately anyway. -- **The profiles directory does not sync.** A CR2 rendered on a desktop with a copied 6D profile - and on a tablet without one will differ. The panel line says which profile each device used, so - the difference is visible rather than silent. Syncing the directory with the library is the - follow-up. +- ~~**The profiles directory does not sync.**~~ *Done after 0.20.0: §13.* - **Rec.2020 working primaries** stay deferred (D19); nothing here depends on them. ## 7. What it costs @@ -256,3 +251,113 @@ is inside its control's range, and every preset changes something. 4. `dr-ui`: profile line, copy-out action, labels; the profiles directory set at start-up on desktop and Android. 5. The *Vivid* presets. + +--- + +# After 0.20.0: tone, exposure and sync + +0.20.0 shipped the tables and §1's measurement showed they were not the gap to Lightroom's colour. +The three items §6 left open are closed here. Draft of 2026-10-03. + +## 11. Baseline exposure + +`BaselineExposure` (DNG tag 50730) is the stops a converter adds so a camera's middle grey lands +where its maker meant it; the 6D's DNGs say +0.25. `BaselineExposureOffset` (51109) is a profile's +correction to it. The DNG SDK's total is their sum, and so is this one's. + +- **Applied as a gain on the camera matrix** in `dr-gpu` when a raw is uploaded: + `cam_to_srgb · 2^total`. A uniform gain commutes with every scene operation before the view + transform, and the camera-space tap and the white-balance probe read camera RGB before the + matrix, so neither changes. `RawImage::color_matrix` itself stays the file's: a merge writes a + linear DNG from it and must not bake a gain into the pixels it also declares in a tag. +- **A copied profile carries the DNG's baseline.** When an embedded profile is saved as a `.dcp` + (§4) its `BaselineExposureOffset` is written as the DNG's `BaselineExposure` plus the profile's + own offset. A CR2 has no baseline of its own, so its total is then the DNG's: the two files of + one body render at one brightness. The cost: a DNG that embeds no profile, carries its own + baseline, and matches a copied `.dcp` counts the baseline twice. Every Adobe-written DNG embeds + its profile, so that DNG is a hand-made one. + +## 12. Camera Raw tone (D21) + +Lightroom's richer colour is its tone curve (§1). Camera Raw renders a raw through the profile's +`ProfileToneCurve`, or, for a profile that has none — Adobe Standard among them — through the +*ACR3 default curve*, a 1025-point table published in the DNG SDK and carried by RawTherapee +(GPLv3) as `adobe_camera_raw_default_curve`. + +**How it is applied** is half of what it does. Camera Raw does not run the curve on each channel: +`RefBaselineRGBTone` runs it on the largest and smallest channel and places the middle one at the +same fraction between them as before. Hue is kept; saturation rises where the curve is steep — +the shadows and midtones — which is exactly where Adobe Standard's look table desaturated to +compensate. It runs in linear ProPhoto, on values clipped to `[0, 1]`, and its output is linear. + +**As the view transform**, not as a stage. D19 has one rendering, last; this is a second kind of +that rendering, chosen by a new parameter on `view_transform`: + +| `curve` | What it is | Default for | +|---|---|---| +| Camera Raw | the profile's curve, else ACR3, via RGBTone in ProPhoto | every raw (D21) | +| Sigmoid | D19's log-logistic curve | — | + +A JPEG is still not rendered again (FR-DEV-3j). Film simulation still replaces the view transform +when a stock is chosen. + +**The two sliders keep meaning something** under Camera Raw: + +- `white` (stops above grey at which the scene reaches display white) sets the input scale: + `2^(4 − white)`. At its default of 4 the scale is 1 — sensor white is display white, as in + Camera Raw. +- `contrast` bends the input about middle grey before the curve, as a power of + `contrast / 1.4`: 1 at its default, so the curve is Camera Raw's untouched. + +**What the ACR curve gives up** is D19's shoulder. Values above display white clip, as they do +in Camera Raw; highlight recovery is the highlights slider's job before it. Sigmoid stays one +click away for a photograph that wants the shoulder. + +**A profile's own curve** is a list of `(x, y)` pairs. It is resampled at decode onto the same +1025 points with a natural cubic spline, the DNG SDK's `dng_spline_solver`. A curve that is the +identity is treated as absent, as RawTherapee does. + +**On the GPU** the curve rides in the profile buffer (`@binding(8)`) after the tables: a third +header entry gives its length, then the samples. The placeholder bound for a source without a +profile carries the ACR3 curve, so a CR2 with no `.dcp` still renders through Camera Raw's tone. + +## 13. Profiles sync + +The profiles directory travels with the library, in the server folder that already carries what +every device must agree on: `/.darkroom-derived/profiles/`. The scanner excludes +its parent, as it excludes the trash. + +- **A step of the derived sync pass** (`derived_sync::run`), after the catalog and before the + place file, and like the place file it never fails the pass. It lists the server folder and the + local one; uploads every local `.dcp` the server lacks, or holds at a different size; downloads + every one the device lacks, reading through a placeholder where the library is a synced folder, + and writing `.tmp` then renaming so a half-written file is never parsed. If it fetched + anything, it reloads the profile set. +- **Files are immutable and named for what they hold** (` .dcp`), so a name and a + size say whether two copies are the same. Two devices that copy the same profile write the same + name; neither wins over anything. +- **Not a catalog table.** A schema change stops an older peer merging the catalog at all + (see the memory of 0.13.3), and a blob of ~120 KB would ride in every catalog upload. +- **One directory per install**, the union of every library's profiles. A profile describes a + camera, not a library, so a profile one library brought is right for the same camera in + another. +- **Not handled: deleting.** There is no way to remove a profile from the app; a file removed by + hand on one device comes back from the server on the next pass. A tombstone list is the + follow-up if deleting is added. + +## 14. Acceptance for §11–§13 + +- The ACR3 table is 1025 points from 0 to 1, monotone, and matches RawTherapee's values. +- RGBTone: grey goes through the curve unchanged in hue; a colour keeps its hue (the middle + channel's fraction between the outer two is unchanged); a curve that is the identity changes + nothing; the shader agrees with the CPU reference on a device. +- Camera Raw is the default curve for a raw; Sigmoid at the defaults renders to the bit what 0.20.0 + rendered without baseline exposure. +- A DNG with `BaselineExposure` +0.25 renders a flat grey 0.25 EV brighter than the same pixels + with none; a `.dcp` copied from it carries `BaselineExposureOffset` 0.25 and gives a CR2 the same + total. +- A profile resampled from `(0,0) (0.5,0.6) (1,1)` passes through its points. +- Sync: a `.dcp` present only locally is uploaded; one present only on the server is downloaded, + parsed and matched on the next decode; a file of the same name and size is left alone. +- Measured again on `_MG_9080`: mean saturation at defaults closer to Lightroom's 0.49 than 0.20.0's + 0.35. diff --git a/docs/dev/requirements.md b/docs/dev/requirements.md index bb2753f..44eeb36 100644 --- a/docs/dev/requirements.md +++ b/docs/dev/requirements.md @@ -2437,6 +2437,7 @@ Rationale, evidence, and the eliminated alternatives are recorded in | D12 | Scope versus pace | **DECIDED 2026-09-19** — settled by events; full scope stands, no v1 date | | D18 | Derived images | **DECIDED 2026-09-19** — a merge writes a new source file; no multi-source Version | | D19 | Scene-referred pipeline | **DECIDED 2026-09-27** — edits on unbounded scene-linear colour; one view transform, last; per-body base curves retired | +| D21 | Camera Raw tone for raws | **DECIDED 2026-10-03** — the view transform gains a Camera Raw curve (profile's, else ACR3 default, via RGBTone in ProPhoto), the default for every raw; Sigmoid stays a choice | | D20 | DCP camera profiles | **DECIDED 2026-10-02** — HueSatMap and LookTable as a scene operation after exposure; embedded profile first, then a matched `.dcp`; tone curve not applied; none shipped | ### D11 — product positioning @@ -2772,6 +2773,28 @@ matrix does. *What it costs.* Every DNG with an embedded profile renders differently; previews refresh only when rendered again; tablet and desktop release together. The profiles directory does not sync yet. +### D21 — Camera Raw tone for raws · **DECIDED 2026-10-03** + +**The view transform has two curves, and Camera Raw's is the default for every raw.** It is the +profile's `ProfileToneCurve`, or the ACR3 default curve where the profile has none or there is no +profile, applied Camera Raw's way — on the largest and smallest channel in linear ProPhoto, the +middle placed proportionally — after `BaselineExposure`. D19's sigmoid stays as the other choice. +Design: [camera-profiles.md](camera-profiles.md) §11–§13. + +*Why.* Measured on the library's 6D DNGs after D20 (camera-profiles.md §1): the profile tables +lowered saturation, because Adobe's look tables were tuned to sit under this curve. The user's +complaint was that Lightroom's rendering is more colourful, and this curve is most of the reason. +Chosen by the user over limiting it to raws with a profile, or making it opt-in. + +*What it reverses in D19.* D19 rejected per-body curves as defaults because their provenance was +unknown. A DCP's curve and the ACR3 table have known provenance — Adobe's, published — and so the +objection that retired the base curves does not apply. D19's other half stands: nothing before +the view transform clamps, and the curve is the view transform, last. + +*What it costs.* Every raw renders differently again, and highlights above display white clip +where the sigmoid rolled them off; Sigmoid is one click away. Previews refresh only when rendered +again; tablet and desktop release together. + ### D16 — plugin licensing · **OPEN, post-v1** > Deferred with §3.10 on 2026-09-19. Still to be answered before the format is published as