Call the reference curve what it is, and drop wording that reads as copying

The view transform's second curve is the DNG SDK's published reference
rendering — the ACR3 default curve applied by RefBaselineRGBTone — so
it is the "DNG Reference" curve in the panel, D21 and the code, not a
name borrowed from another product. Comments and docs that justified a
choice by another editor doing it ("as their Amount", "so a
photographer arriving from it finds the name") now give the actual
reason. The Vivid presets no longer describe themselves as reaching
for another editor's look; they are DarkRoom's own.

Factual mentions stay: which program wrote the library's DNGs, what
was measured against, and preset import. camera-profiles.md gains §15,
on starting a photograph from the edit it already carries.
This commit is contained in:
2026-10-03 14:22:49 -04:00
parent e12783da9f
commit 8294e6b59f
12 changed files with 68 additions and 52 deletions
+1 -1
View File
@@ -228,7 +228,7 @@ fn camera_raw_tone_agrees_with_its_cpu_reference() {
dr_pipeline::view::DEFAULT_WHITE,
)
},
"Camera Raw tone",
"DNG reference tone",
);
}
+1 -1
View File
@@ -61,7 +61,7 @@ fn sigmoid_chain() -> EditGraph {
#[test]
fn camera_raw_tone_agrees_with_the_acr3_curve() {
// TRACES: FR-DEV-3j
// D21: a raw with no profile, Camera Raw's curve chosen, renders a grey
// D21: a raw with no profile, the DNG reference curve chosen, renders a grey
// through the ACR3 default curve, which the profile buffer's placeholder
// carries.
let Some(ctx) = ctx() else {
+2 -6
View File
@@ -1,12 +1,8 @@
drpl 1
# Vivid: more colour than the default rendering, for the photographer used
# to Lightroom's richer starting point (camera-profiles.md §9).
# Vivid: more colour than the default rendering (camera-profiles.md §9).
#
# That difference is mostly Camera Raw's tone curve, not the camera
# profile: Adobe Standard's look table, which D20 applies, desaturates dark
# tones (camera-profiles.md §1). So these do the work themselves, and work
# on every photograph — a JPEG, a body with no profile. They lean on
# These do the work themselves, and work on every photograph — a JPEG, a body with no profile. They lean on
# vibrance before saturation: vibrance lifts muted colours most and holds
# skin back, so a frame gets richer before anything in it looks painted.
# Saturation, which moves every colour alike, is used sparingly on top.
+10 -10
View File
@@ -1,16 +1,16 @@
//! TRACES: FR-DEV-3j | FR-DEV-3e
//! Camera Raw's tone, as a rendering the view transform can choose (D21).
//! The DNG SDK's reference tone, as a rendering the view transform can
//! choose (D21).
//!
//! Lightroom's colour is mostly its tone curve (camera-profiles.md §1): the
//! The DNG specification's reference rendering runs a raw through the
//! profile's `ProfileToneCurve`, or the ACR3 default for a profile with none.
//! Half of what the curve does is *how* it is applied. Camera Raw's
//! Half of what the curve does is *how* it is applied. The SDK's
//! `RefBaselineRGBTone` runs it on the largest and the smallest channel, and
//! places the middle channel at the fraction between them it had before. Hue
//! is kept; saturation rises wherever the curve is steeper than the
//! diagonal, which for the ACR3 curve is the shadows and the midtones —
//! where Adobe Standard's look table desaturated to compensate.
//! diagonal, which for the ACR3 curve is the shadows and the midtones.
//!
//! It runs in linear ProPhoto, as Camera Raw does, on values clipped to
//! It runs in linear ProPhoto, as the SDK does, on values clipped to
//! `[0, 1]`; its output is linear and goes to the output transform as the
//! sigmoid's does. The curve is read from the profile buffer
//! (`ops::camera_profile::profile_buffer`), which always carries one.
@@ -22,14 +22,14 @@ use crate::ops::camera_profile::{mul, working_prophoto};
use crate::view::{DEFAULT_CONTRAST, DEFAULT_WHITE, SCENE_GREY};
/// The input scale for a white point: 1 at the default, so sensor white is
/// display white as in Camera Raw; each stop of `white` above it halves the
/// display white as in the SDK's reference; each stop of `white` above it halves the
/// input.
pub fn input_scale(white: f32) -> f32 {
(DEFAULT_WHITE - white).exp2()
}
/// The power the input is bent by about middle grey: 1 at the default
/// contrast, so the curve is Camera Raw's untouched.
/// contrast, so the curve is the reference's untouched.
pub fn contrast_power(contrast: f32) -> f32 {
contrast / DEFAULT_CONTRAST
}
@@ -50,7 +50,7 @@ fn rgb_tone(curve: &[f32], p: [f32; 3]) -> [f32; 3] {
}
/// TRACES: FR-DEV-3j
/// The view transform's Camera Raw rendering of one working-space colour.
/// The view transform's DNG reference rendering of one working-space colour.
pub fn apply_reference(curve: &[f32], c: [f32; 3], contrast: f32, white: f32) -> [f32; 3] {
let (to, back) = working_prophoto();
let scale = input_scale(white);
@@ -73,7 +73,7 @@ fn camera_raw_curve(x: f32) -> f32 {
return mix(profile_table[base + i].x, profile_table[base + i + 1u].x, s - f32(i));
}
// Camera Raw's RGBTone: the curve on the largest and smallest channel, the
// The SDK's RGBTone: the curve on the largest and smallest channel, the
// middle one kept at its fraction between them, so hue survives.
fn camera_raw_tone(c: vec3<f32>, scale: f32, power: f32, grey: f32) -> vec3<f32> {
var p = PROFILE_FROM_WORKING * c * scale;
+4 -4
View File
@@ -48,7 +48,7 @@ pub const LOOK: ParamId = ParamId("look");
/// The look's strength at which the LookTable is applied as the profile
/// states it, in percent.
pub const DEFAULT_LOOK: f32 = 100.0;
/// Twice the profile's look: Lightroom's *Amount* reaches the same.
/// Twice the profile's look.
pub const MAX_LOOK: f32 = 200.0;
/// Entries of the buffer's header, before the entries themselves: one
@@ -130,7 +130,7 @@ fn wgsl_mat(m: &[f32; 9]) -> String {
/// The working space to ProPhoto and back, as WGSL constants, and where
/// the profile buffer's sections begin. A helper of its own because the
/// view transform's Camera Raw curve needs it too, and helpers are emitted
/// view transform's DNG reference curve needs it too, and helpers are emitted
/// once each, in the order first asked for.
pub(crate) static PROPHOTO_HELPER: LazyLock<Helper> = LazyLock::new(|| {
let (to, back) = working_prophoto();
@@ -390,7 +390,7 @@ if (hue_sat_dims.x > 0.0 || look_dims.x > 0.0) {
/// The curve is always there: the profile's own where it has one, Camera
/// Raw's ACR3 default otherwise — including in the placeholder every source
/// without a profile binds, whose tables are absent, so a raw with no
/// profile still renders through Camera Raw's tone (D21).
/// profile still has the reference tone curve when it is chosen (D21).
pub fn profile_buffer(tables: Option<&ProfileTables>) -> Vec<[f32; 4]> {
let header = |t: Option<&HueSatTable>| match t {
Some(t) => [
@@ -706,7 +706,7 @@ mod tests {
fn the_buffer_puts_the_header_first_and_the_look_after_the_hue_sat_map() {
let bare = profile_buffer(None);
assert_eq!(bare[..2], [[0.0; 4]; 2], "no tables");
assert_eq!(bare[2][0], 1025.0, "and Camera Raw's default curve");
assert_eq!(bare[2][0], 1025.0, "and the reference default curve");
assert_eq!(bare.len(), HEADER_ENTRIES + 1025);
let t = tables(
Some(uniform(2, 2, 1, [1.0, 2.0, 3.0])),
+2 -2
View File
@@ -36,7 +36,7 @@ pub const WHITE: ParamId = ParamId("white");
/// TRACES: FR-DEV-3j
/// Which curve renders: D19's sigmoid (index 0, the default) or Camera
/// Raw's (D21). Which of the two should be the default is open — the
/// Lightroom-matching work decides it — so the default is what 0.20.0
/// measurement work decides it — so the default is what 0.20.0
/// rendered.
pub const CURVE: ParamId = ParamId("curve");
@@ -161,7 +161,7 @@ impl Operation for ViewTransform {
"\
// Skipped for an already-rendered source: a JPEG is a display rendering
// already, and rendering it again would compress it twice.
// D19's sigmoid by default, Camera Raw's tone by choice (D21).
// D19's sigmoid by default, the DNG reference tone by choice (D21).
if (!non_linear) {
if (mode > 0.5) {
c = camera_raw_tone(c, cr_scale, cr_power, cr_grey);
+1 -1
View File
@@ -117,7 +117,7 @@ pub struct ProfileTables {
pub look: Option<HueSatTable>,
/// The profile's `ProfileToneCurve`, resampled onto
/// [`crate::tone::TONE_SAMPLES`] points; `None` where it has none, and
/// the view transform's Camera Raw curve then uses
/// the view transform's DNG reference curve then uses
/// [`crate::tone::ACR3_DEFAULT`] (D21).
pub tone_curve: Option<Vec<f32>>,
}
+2 -2
View File
@@ -1,10 +1,10 @@
//! TRACES: FR-DEV-3j | FR-DEV-3e
//! Camera Raw's tone curves, as data (D21).
//! The DNG SDK's reference tone curve, as data (D21).
//!
//! # Where the numbers come from
//!
//! [`ACR3_DEFAULT`] is the "ACR3 default" tone curve of Adobe's DNG SDK
//! (`dng_tone_curve_acr3_default`): what Camera Raw renders a raw through
//! (`dng_tone_curve_acr3_default`): what the SDK's reference renders a raw through
//! when its camera profile carries no `ProfileToneCurve` of its own, which is
//! true of Adobe Standard. The values are RawTherapee's
//! `adobe_camera_raw_default_curve` (`rtengine/dcp.cc`, GPLv3), copied
+35 -14
View File
@@ -95,7 +95,7 @@ Two departures, both forced by D19's unbounded scene-linear values (the SDK runs
both tables, which is where the second reason matters:
- **The look sees exposure.** The SDK applies `LookTable` after its exposure ramp, so a look that
desaturates highlights finds the highlights the photographer chose. At 25 it does too. Contrast,
tone and the colour controls come after it, as they do in Camera Raw.
tone and the colour controls come after it, as they do in the DNG SDK's reference rendering.
**Tables are per source, like the matrix.** They are decoded with the raw, interpolated once at
decode (the HueSatMap blend uses the as-shot neutral, as the matrix does) and carried on
@@ -151,7 +151,7 @@ reads a buffer no declaration can name:
| Parameter | Kind | Default | Meaning |
|---|---|---|---|
| `apply` | Bool | on | Use the profile's tables, or the matrix alone |
| `look` | Scalar 0–200 | 100 | Strength of the `LookTable`, as Lightroom's *Amount* |
| `look` | Scalar 0–200 | 100 | Strength of the `LookTable` |
`look` scales the look's deltas: `hueShift · a`, `1 + (satScale − 1)·a`, `1 + (valScale − 1)·a`,
with `a = look/100`, scales floored at 0. At 200 the look is twice as strong, which is the
@@ -277,14 +277,14 @@ correction to it. The DNG SDK's total is their sum, and so is this one's.
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)
## 12. DNG reference tone (D21)
Lightroom's richer colour is its tone curve (§1). Camera Raw renders a raw through the profile's
The DNG SDK's reference rendering runs 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:
**How it is applied** is half of what it does. The reference 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
@@ -296,25 +296,25 @@ that rendering, chosen by a new parameter on `view_transform`:
| `curve` | What it is | Default for |
|---|---|---|
| Sigmoid | D19's log-logistic curve | every raw, for now |
| Camera Raw | the profile's curve, else ACR3, via RGBTone in ProPhoto | — (D21: the default is open) |
| DNG reference | the profile's curve, else ACR3, via RGBTone in ProPhoto | — (D21: the default is open) |
*Amended 2026-10-03:* Camera Raw was the default in the first draft. The measurement it rested on
*Amended 2026-10-03:* the DNG reference was the default in the first draft. The measurement it rested on
compared against Lightroom renders of *edited* photographs; see D21. The default is decided by
measuring against Lightroom exports of unedited ones.
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:
**The two sliders keep meaning something** under the DNG reference curve:
- `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.
the SDK's reference.
- `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.
`contrast / 1.4`: 1 at its default, so the curve is the reference'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
in the SDK's reference; 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
@@ -323,7 +323,7 @@ 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.
profile carries the ACR3 curve, so a CR2 with no `.dcp` renders through the reference tone when that curve is chosen.
## 13. Profiles sync
@@ -355,8 +355,8 @@ its parent, as it excludes the trash.
- 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.
- Sigmoid is the default curve; at its defaults it renders to the bit what 0.20.0 rendered,
apart from 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.
@@ -365,3 +365,24 @@ its parent, as it excludes the trash.
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.
## 15. The photographer's earlier edit
What §1 and D21 first took for a difference in rendering is an edit. Every DNG in the library
carries, in its embedded XMP, the develop settings it was given before it came to DarkRoom — a
consistent house style: per-colour saturation (blue +58, aqua +50, yellow and purple +23, orange
+13, green +10), highlights −40, blacks −20, with a second variant (vibrance −10, blue +31). Those
settings, not the profile and not the tone curve, are why the same photographs looked richer
before.
- **Translated on open** (`dr_preset_xmp::read_embedded`), the HSL bands onto the colour mixer —
aqua to cyan and purple to violet, the nearest of its twelve by hue — and the rest as the preset
importer already did.
- **Applied only to a photograph DarkRoom has no edit of**, and only on positive evidence: no
sidecar beside a local file, or a server that answered "no such file" with nothing cached
(`FetchedSidecar::absent`). An edit that failed to arrive is not an absent one, and this would
otherwise be saved over it.
- **One undoable step, "Earlier Edit"**, then an ordinary edit, saved with the photograph. Export
applies it the same way, so a photograph never opened exports as opening it would show.
- **The translation is one for one for now.** Measurement against the earlier exports (the
`lr-fit` work) may scale individual bands.
+2 -2
View File
@@ -2437,7 +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 | **PARTLY DECIDED 2026-10-03** — the view transform gains a Camera Raw curve (profile's, else ACR3 default, via RGBTone in ProPhoto) as a choice; which curve is the default is open, pending measurement against Lightroom exports |
| D21 | DNG reference tone for raws | **PARTLY DECIDED 2026-10-03** — the view transform gains the DNG reference curve (profile's, else ACR3 default, via RGBTone in ProPhoto) as a choice; which curve is the default is open, pending measurement against Lightroom exports |
| 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
@@ -2773,7 +2773,7 @@ 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 · **PARTLY DECIDED 2026-10-03**
### D21 — DNG reference tone for raws · **PARTLY DECIDED 2026-10-03**
*Amended the same day:* the default is **not** decided. The measurement below was against
Lightroom previews of photographs carrying the user's Lightroom edits — a house look of HSL
File diff suppressed because one or more lines are too long
+6 -7
View File
@@ -213,9 +213,8 @@ fn catalogued(key: &str) -> Option<&'static str> {
// the pipeline, and the photographer is choosing how the scene's range
// is fitted onto the screen.
"op.view_transform" => "Tone Mapping",
// The DNG camera profile's tables (D20). "Camera Profile", as Camera
// Raw calls the same thing, so a photographer arriving from Lightroom
// finds it under the name they know.
// The DNG camera profile's tables (D20). "Camera Profile", the DNG
// specification's own name for what the file carries.
"op.camera_profile" => "Camera Profile",
// Parameters
@@ -226,15 +225,15 @@ fn catalogued(key: &str) -> Option<&'static str> {
"param.learned_denoise.apply" => "Apply",
"param.learned_denoise.grain" => "Keep grain",
"param.camera_profile.apply" => "Use Profile",
// The LookTable's strength, as Lightroom's profile "Amount".
// The LookTable's strength.
"param.camera_profile.look" => "Look Amount",
"param.view_transform.contrast" => "Contrast",
// In stops above middle grey: where the scene reaches display white.
"param.view_transform.white" => "White Point",
// The view transform's curve (D21): Camera Raw's tone, as Lightroom
// renders a raw, or D19's sigmoid with its highlight shoulder.
// The view transform's curve (D21): the DNG SDK's reference
// rendering, or D19's sigmoid with its highlight shoulder.
"param.view_transform.curve" => "Curve",
"param.view_transform.curve.camera_raw" => "Camera Raw",
"param.view_transform.curve.camera_raw" => "DNG Reference",
"param.view_transform.curve.sigmoid" => "Sigmoid",
"param.temperature" => "Temperature",
"param.tint" => "Tint",