Decides D21 by measurement. The photo gallery holds Lightroom 6 exports of raws in the library, each carrying its Camera Raw settings; clustered by those settings, 663 had no look applied. On 60 of them with their raws, a third held out, the held-out MSE against Lightroom's JPEG was about 1200 for 0.20.0's sigmoid (0.7 EV darker and flatter), 224 for the DNG reference curve after baseline exposure, and about 140 once its input is bent by 1.5/1.4 about grey. So the curve choice defaults to the DNG reference, keeping its index (sidecars record it), and the default contrast is 1.5. Contrast under the DNG reference curve is now a power relative to REFERENCE_CONTRAST (1.4), where the table is untouched; the sigmoid at that contrast still matches the retired base curve. JPEGs are unaffected: the view transform skips a rendered source.
392 lines
24 KiB
Markdown
392 lines
24 KiB
Markdown
# Camera profiles — DCP tables on top of the matrix
|
||
|
||
Design for the deferred half of **FR-DEV-3e** ([requirements.md](requirements.md)): the
|
||
`HueSatMap` and `LookTable` of a DNG camera profile, read from the DNG that carries one or from a
|
||
`.dcp` file, and applied after the matrix. Draft of 2026-10-02, recorded as **D20**.
|
||
|
||
---
|
||
|
||
## 1. What we are matching
|
||
|
||
Lightroom renders a raw through a *profile* before any slider moves. A profile is the matrix
|
||
DarkRoom already applies, plus two lookup tables indexed by hue, saturation and value:
|
||
|
||
- **`ProfileHueSatMap`** — a calibration. It corrects what a 3×3 cannot: a sensor whose reds and
|
||
oranges sit in the wrong place relative to its blues, which no linear map fixes. Two copies, one
|
||
per calibration illuminant, interpolated like the matrices.
|
||
- **`ProfileLookTable`** — a rendering intent: hue shifts of up to ±18° by hue, and saturation
|
||
and value scales that vary with brightness. The difference between Adobe Standard, Adobe Color
|
||
and Adobe Vivid is largely this table, together with each profile's tone curve.
|
||
|
||
Without them a raw renders through the matrix alone, which is accurate on a ColorChecker and is
|
||
not what Lightroom showed for the same file.
|
||
|
||
**What the tables do *not* do is make a photograph more saturated.** Measured after the build
|
||
(2026-10-02), on four of the library's 6D DNGs rendered at defaults: Adobe Standard's tables
|
||
*lower* mean saturation by 3–9 %, and the look at 200 % lowers it further. The 6D's look table
|
||
scales saturation by 0.925 in its darkest value rows and by 1.0 from about a fifth of full scale
|
||
up, and its HueSatMap adds about 1 %. Adobe Standard was tuned to sit under Camera Raw's default
|
||
RGB tone curve, which raises saturation in the shadows and midtones, and the look's dark-tone
|
||
desaturation offsets it. Without that curve (§6) the offset is all that is left. On `_MG_9080`, Lightroom 6's own preview
|
||
measures 0.49 mean HSV saturation; the matrix alone renders 0.38, Adobe Standard's tables 0.35.
|
||
That preview also carries whatever was edited in Lightroom, so it is not a clean reference — but
|
||
the direction is unambiguous: **the gap to Lightroom's colour is mostly tone, not the profile's
|
||
tables.** The tables still matter for hue: they are what puts each body's reds, skin and foliage
|
||
where Adobe put them.
|
||
|
||
**What the library holds** (catalog of 2026-10-02): 17,286 of its raws are Canon EOS 6D. The
|
||
9,348 DNGs were written by Lightroom 6.14 and every one sampled embeds *Adobe Standard* with both
|
||
tables — `HueSatMapDims 90 30 1`, `LookTableDims 36 8 16`, `ProfileEmbedPolicy 0` ("allow
|
||
copying"), no `ProfileToneCurve`. The 7,938 CR2s from the same body carry no profile. So the
|
||
tables Lightroom used are already on disk for half the library, and their licence lets them be
|
||
applied to the other half.
|
||
|
||
## 2. The model, as the DNG specification states it
|
||
|
||
Each table is a grid of `(hueShift°, satScale, valScale)` triples over HSV, stored with
|
||
saturation varying fastest, then hue, then value:
|
||
|
||
```
|
||
index = v · (hueDivs · satDivs) + h · satDivs + s
|
||
```
|
||
|
||
Lookup follows the DNG SDK's `RefBaselineHueSatMap`:
|
||
|
||
- **HSV** is the SDK's: `v = max(r,g,b)`, `s = (v − min)/v`, `h ∈ [0, 6)` from which channel
|
||
leads. Grey has `s = 0` and is untouched by construction.
|
||
- **Hue wraps**: `hueDivs` samples over 360°, the last interpolating to the first.
|
||
- **Saturation** samples `0..=1` at `satDivs` points; linear between.
|
||
- **Value** samples `0..=1` at `valDivs` points when `valDivs > 1`; a table with `valDivs = 1` is
|
||
"2.5-D" and ignores value. `ProfileHueSatMapEncoding` / `ProfileLookTableEncoding` = 1 means the
|
||
value axis is indexed by the sRGB-encoded value; 0 (the default, and the 6D's) means linear.
|
||
- **Apply**: `h += hueShift · 6/360`, `s = min(s · satScale, 1)`, `v ·= valScale`; back to RGB.
|
||
- **Space**: linear ProPhoto (ROMM) primaries, D50 white — the space the forward matrix lands in.
|
||
- **Two illuminants**: `HueSatMapData1/2` are interpolated entry by entry, with the same mired
|
||
weight the matrices use. `LookTable` is single.
|
||
|
||
Two departures, both forced by D19's unbounded scene-linear values (the SDK runs these on `[0, 1]`):
|
||
|
||
1. **Value is not clamped.** The SDK writes `min(v · valScale, 1)`; here `v · valScale`, unbounded.
|
||
For lookup only, the value axis reads `min(v, 1)`, so a highlight above 1.0 uses the table's
|
||
brightest row. Where the encoding is sRGB the scale is defined on the encoded value; it is
|
||
applied as the ratio `decode(enc(v′)·valScale)/v′` at `v′ = min(v, 1)`, so a value above 1.0
|
||
gets the brightest row's ratio rather than a clip.
|
||
2. **A colour outside ProPhoto passes through.** A negative component has no SDK HSV. Such a
|
||
colour is outside every surface colour a camera records under normal light. It is left
|
||
unmodified rather than floored, because flooring it clips a value D19 says nothing may clip.
|
||
|
||
## 3. Where it sits
|
||
|
||
```
|
||
… camera matrix ─► vignetting(5) ─► exposure(20) ─► camera_profile(25) ─► contrast(30) ─► … ─► view transform
|
||
│
|
||
working → ProPhoto ─► HueSatMap ─► LookTable ─► ProPhoto → working
|
||
```
|
||
|
||
**A scene operation at order 25, not part of the matrix snippet.** Three reasons:
|
||
|
||
- **The matrix stays what it is.** `cam_to_srgb` is unchanged and still runs where D19 put it, and
|
||
so does every reader of it: the mask pass's copy, the white-balance picker's, the camera-space
|
||
tap. The tables add a conversion into ProPhoto and back *inside* their own fragment, through two
|
||
constant matrices (§3.1). A photograph with no profile composes exactly the shader it does today.
|
||
- **It commutes with what runs before it.** HSV hue and saturation are invariant under a uniform
|
||
gain, and vignetting and exposure are uniform gains. So a 2.5-D table — every Adobe HueSatMap
|
||
seen, and the 6D's — gives the same answer before or after them. That lets one position serve
|
||
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 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
|
||
`DemosaicedImage` next to `color_matrix`. `dr-gpu` uploads them to a storage buffer at
|
||
`@binding(8)` and writes their dimensions into the base uniform block. Every render path that
|
||
reaches `AdjustPass` therefore gets them without being told: develop, export, previews, the tablet.
|
||
A path that had to call a setter on the graph would be a path that one day forgot to, and an export
|
||
that differed from the screen would be the result.
|
||
|
||
### 3.1 The two constants
|
||
|
||
`P⁻¹` is `ColourSpace::ProPhoto.from_linear_srgb()` — the conversion `dr-types` already derives
|
||
from the two spaces' chromaticities, adapting D65 to D50 by Bradford, which the export path uses to
|
||
write ProPhoto files — and `P` is its inverse. The fragment uses `P⁻¹` going in and `P` coming
|
||
out. Each row of both is scaled to sum to one, so working-space white is ProPhoto white exactly and
|
||
a neutral reaches the tables at `s = 0`. For a profile with forward matrices this recovers the
|
||
SDK's ProPhoto colour to within the difference between that derivation and `forward_to_srgb`'s
|
||
published Bradford constants, which is rounding.
|
||
|
||
## 4. Where a profile comes from
|
||
|
||
In this order, first match wins:
|
||
|
||
1. **The profile embedded in the DNG being opened.** It is what the file says, and it was made for
|
||
the matrices the file carries. Read from the root IFD through rawler's parsed `IFD`, as
|
||
`read_dng_matrices` already reads the forward matrices — no second TIFF parser.
|
||
2. **A `.dcp` in the profiles directory** whose `UniqueCameraModel` matches the body — compared
|
||
case-insensitively against the DNG's `UniqueCameraModel` where there is one, and against
|
||
`make + " " + model` otherwise ("Canon EOS 6D"). A DCP is a whole profile: its matrices replace
|
||
the file's, because its tables were built against its forward matrix. The file's as-shot neutral
|
||
is kept. If several match, the first by file name wins, so the choice is stable.
|
||
3. **None.** The matrix alone, as today.
|
||
|
||
The profiles directory is `profiles/` under the platform data directory (`dr_plat::dirs`), loaded
|
||
once per process. A DCP is a TIFF with the magic `IIRC` (0x4352) in place of 42; rawler's
|
||
`GenericTiffReader` already accepts it.
|
||
|
||
**Copying an embedded profile out.** A DNG whose profile has `ProfileEmbedPolicy` 0 ("allow
|
||
copying") or 3 ("no restrictions") can have that profile saved as a `.dcp` into the profiles
|
||
directory. That is how the 6D's CR2s get Adobe Standard: open a 6D DNG, choose *Use this profile
|
||
for every Canon EOS 6D*. Policies 1 ("embed if used") and 2 ("embed never") offer no such action.
|
||
The written file carries the profile's name, copyright and policy unchanged.
|
||
|
||
**Nothing is shipped.** Adobe's profiles are Adobe's; the application ships no `.dcp` and copies
|
||
none on its own. A profile reaches the directory because the photographer put it there or asked
|
||
for it to be copied from their own file.
|
||
|
||
## 5. The control
|
||
|
||
A develop operation, `camera_profile`, `[colour]`, order 25, hand-written (`rust:`) because it
|
||
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` |
|
||
|
||
`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
|
||
"more vivid than Adobe Standard" this started from. The HueSatMap is a calibration and is not
|
||
scaled: `apply` is its only switch.
|
||
|
||
**Always composed while `apply` is on**, as the view transform is: a profile at its defaults *is*
|
||
the rendering, not an edit, so an untouched photograph writes no parameters and still renders
|
||
through its profile. The fragment branches on the uniform that says whether the source has tables,
|
||
so a JPEG, or a raw with none, pays one uniform read. The branch is uniform across the dispatch.
|
||
|
||
**Mask layers.** A layer may offset `look` (blended as a setting, which is linear) but not `apply`;
|
||
the photograph has one profile.
|
||
|
||
**The panel says which profile is in use**, as the lens line does: *Adobe Standard (in the file)*,
|
||
*Adobe Standard (Canon EOS 6D.dcp)*, or *No profile for this camera — matrix only*. The copy-out
|
||
action sits on that line.
|
||
|
||
## 6. Not done, and why
|
||
|
||
- ~~**`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.**~~ *Done after 0.20.0: §13.*
|
||
- **Rec.2020 working primaries** stay deferred (D19); nothing here depends on them.
|
||
|
||
## 7. What it costs
|
||
|
||
- **Every DNG with an embedded profile renders differently** — more saturated, which is the point.
|
||
Previews rendered before the change keep the old look until rendered again, as with D19.
|
||
- **Tablet and desktop must be released together.** No schema change, and the sidecar gains only
|
||
ordinary parameters, but two builds render the same DNG differently.
|
||
- **One storage-buffer binding** in every generated shader's layout (a one-entry placeholder when
|
||
there are no tables), and two vec4 slots in the base uniform block.
|
||
- **Per pixel**: two 3×3 multiplies, two HSV round trips, and 4 + 8 buffer reads (bilinear
|
||
HueSatMap, trilinear LookTable). Small next to the fused pass it joins.
|
||
|
||
## 8. Acceptance
|
||
|
||
- **Parsing.** The 6D DNG's embedded profile parses to `90×30×1` and `36×8×16`, its policy to 0,
|
||
its name to "Adobe Standard"; a `.dcp` written from it parses back to the same tables bit for
|
||
bit.
|
||
- **The CPU reference matches the SDK's algorithm**: grey passes through; a table of
|
||
`(0°, 1, 1)` everywhere is the identity to 1e-6; a uniform `satScale` of 1.2 scales HSV
|
||
saturation by 1.2; hue interpolation wraps between the last and first column.
|
||
- **The shader agrees with the CPU reference** on a device, within two 8-bit codes of the
|
||
display-encoded readback (the only readback the adjust pass has), over 256 colours that tables
|
||
of tens of degrees and ±30 % saturation move, and over the library's real Adobe Standard tables
|
||
(`dr-gpu/tests/camera_profile.rs`).
|
||
- **Scene-referred.** A value above 1.0 leaves the stage above 1.0 (`scene_referred_until_the_view`
|
||
covers the operation).
|
||
- **Neutral.** `apply` off renders to the bit what a source with no tables renders.
|
||
- **Two illuminants.** A HueSatMap at blend weight 0 is Data1, at 1 is Data2.
|
||
- **Matching.** An embedded profile beats a directory one; a DCP for "Canon EOS 6D" matches a CR2
|
||
whose rawler make/model is "Canon"/"EOS 6D"; no match leaves the matrix.
|
||
- **Subjective.** A 6D DNG rendered here at defaults is visibly closer to the same file in
|
||
Lightroom 6 with Adobe Standard than the matrix-only render, side by side.
|
||
|
||
## 9. Vivid presets
|
||
|
||
Independent of the tables, and — given §1's measurement — the part of this change that actually
|
||
answers "more colourful". Shipped in the same change: a *Vivid* section of read-only presets
|
||
(`presets/vivid.drpl`) for the "more colourful than the default" request. They use only operations
|
||
every photograph has — vibrance, saturation, the colour mixer, colour grading, contrast — so they
|
||
work on JPEGs and on bodies with no profile, and change only what they name (FR-DEV-6):
|
||
|
||
- **Vivid** — vibrance and a little saturation and contrast: the general-purpose one.
|
||
- **Vivid, strong** — the same, pushed, with deeper blacks.
|
||
- **Vivid landscape** — greens, blues and azure skies, skin bands left alone.
|
||
- **Vivid warm** — oranges and yellows up, a warm highlight cast: golden hour.
|
||
- **Vivid portrait** — vibrance (which protects skin) with the orange and red bands held back.
|
||
|
||
Measured on `_MG_9080` (mean HSV saturation; Lightroom's preview 0.49, DarkRoom's default 0.35):
|
||
Vivid 0.40, Vivid strong 0.44, Vivid landscape 0.46, Vivid warm 0.38, Vivid portrait 0.37 — the
|
||
last two move particular bands, not the whole frame. Rendered with `cargo run --release -p dr-gpu
|
||
--example develop -- FILE.dng out.ppm "preset:Vivid"`.
|
||
|
||
They are bounded by the existing `bundled.rs` tests: every key names a real parameter, every value
|
||
is inside its control's range, and every preset changes something.
|
||
|
||
## 10. Build order
|
||
|
||
1. `dr-decode`: parse the tables (embedded and `.dcp`), the profiles directory, matching, blending,
|
||
the `.dcp` writer. CPU reference of the lookup. Unit tests against the library's 6D DNG,
|
||
skipped when it is absent.
|
||
2. `dr-pipeline`: the `camera_profile` operation, the base-block slots, `@binding(8)`, the WGSL
|
||
lookup; composition tests.
|
||
3. `dr-gpu`: carry the tables on `DemosaicedImage`, upload and bind them; the shader-versus-CPU
|
||
test on a device.
|
||
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. DNG reference tone (D21)
|
||
|
||
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. 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
|
||
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 |
|
||
|---|---|---|
|
||
| Sigmoid | D19's log-logistic curve | — (a choice) |
|
||
| DNG reference | the profile's curve, else ACR3, via RGBTone in ProPhoto | every raw (D21) |
|
||
|
||
*Decided 2026-10-03:* the DNG reference is the default, at contrast 1.5 — measured against
|
||
Lightroom exports of photographs with neutral look settings (D21 has the table).
|
||
|
||
*Amended earlier on 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 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
|
||
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 the reference's untouched.
|
||
|
||
**What the ACR curve gives up** is D19's shoulder. Values above display white clip, as they do
|
||
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
|
||
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` renders through the reference tone when that curve is chosen.
|
||
|
||
## 13. Profiles sync
|
||
|
||
The profiles directory travels with the library, in the server folder that already carries what
|
||
every device must agree on: `<library root>/.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** (`<camera> <profile>.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.
|
||
- 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.
|
||
- 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.
|
||
|
||
## 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.
|