Compare commits
12
Commits
209ae36163
...
e22128c16b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e22128c16b | ||
|
|
81ea9359bc | ||
|
|
be37218696 | ||
|
|
5bff453d37 | ||
|
|
61cd226719 | ||
|
|
87d758bd81 | ||
|
|
427a572aad | ||
|
|
01197ca37d | ||
|
|
f9154ad12e | ||
|
|
1fdfb5990c | ||
|
|
7558053932 | ||
|
|
6bccc2db13 |
+12
-2
@@ -148,9 +148,9 @@ screen looks like, [`tools/manual`](tools/manual/README.md) says how to record
|
||||
it again. The pre-commit hook regenerates the matrix, the gesture book and the
|
||||
page; CI runs all three checks.
|
||||
|
||||
## Two invariants the build defends
|
||||
## Three invariants the build defends
|
||||
|
||||
Worth knowing before you trip one, because both failures name a requirement
|
||||
Worth knowing before you trip one, because each failure names a requirement
|
||||
rather than a line:
|
||||
|
||||
- **No operation may be named in `ui/`** (FR-DEV-3a). Special-casing one
|
||||
@@ -162,6 +162,16 @@ rather than a line:
|
||||
`order:`, a filename disagreeing with its `id:`, a default outside its own
|
||||
range, an expression naming something that is not a parameter. Each error
|
||||
names the key you got wrong and exits rather than panicking.
|
||||
- **No verdict is written without a user action** (FR-CULL-13). A rating,
|
||||
flag, colour label or trash membership is the photographer's to set, never a
|
||||
signal's. `tools/traceability/src/verdicts.rs` finds every write of one in
|
||||
the shipped code — the catalog setters, SQL that assigns those columns, the
|
||||
sidecar's judgement amendment — and holds each to a reviewed list with its
|
||||
reason: inside a Slint `on_*` callback, writing for callers that are checked
|
||||
in turn, or carrying a verdict made elsewhere, such as a sidecar pull or the
|
||||
sync merge. A new write fails `cargo test` (the `traceability` crate's tests,
|
||||
part of the workspace run) until it is listed, and so does a listed one that
|
||||
has gone; `cargo run -p traceability -- verdicts` prints the list.
|
||||
|
||||
## Commit messages
|
||||
|
||||
|
||||
Generated
+25
-25
@@ -1265,7 +1265,7 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "darkroom-android"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"dr-plat",
|
||||
@@ -1278,7 +1278,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darkroom-desktop"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dr-plat",
|
||||
@@ -1454,7 +1454,7 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
|
||||
|
||||
[[package]]
|
||||
name = "dr-bench"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dr-catalog",
|
||||
@@ -1471,7 +1471,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-catalog"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-face",
|
||||
"dr-plat",
|
||||
@@ -1486,7 +1486,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-decode"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-types",
|
||||
"env_logger",
|
||||
@@ -1500,7 +1500,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-export"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-decode",
|
||||
"dr-gpu",
|
||||
@@ -1519,7 +1519,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-face"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-inference-engine",
|
||||
"env_logger",
|
||||
@@ -1532,7 +1532,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-film"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -1541,7 +1541,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-gpu"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"dr-decode",
|
||||
@@ -1559,7 +1559,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-inference-engine"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"libloading",
|
||||
@@ -1574,7 +1574,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-ingest"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-plat",
|
||||
"dr-types",
|
||||
@@ -1586,7 +1586,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-lens"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"lensfun",
|
||||
"log",
|
||||
@@ -1594,7 +1594,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-pano"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-decode",
|
||||
"dr-inference-engine",
|
||||
@@ -1608,7 +1608,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-pipeline"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-types",
|
||||
"log",
|
||||
@@ -1617,7 +1617,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-plat"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"android-native-keyring-store",
|
||||
"dr-types",
|
||||
@@ -1633,7 +1633,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-preset-xmp"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-pipeline",
|
||||
"log",
|
||||
@@ -1643,7 +1643,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-segment"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-inference-engine",
|
||||
"env_logger",
|
||||
@@ -1656,7 +1656,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-sync"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dr-plat",
|
||||
@@ -1670,7 +1670,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-sync-folder"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dr-sync",
|
||||
@@ -1682,7 +1682,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-sync-nextcloud"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dr-decode",
|
||||
@@ -1704,7 +1704,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-thumbs"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-types",
|
||||
"jpeg-encoder",
|
||||
@@ -1716,7 +1716,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-types"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1725,7 +1725,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-ui"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1773,7 +1773,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dr-xmp"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"dr-types",
|
||||
"log",
|
||||
@@ -7109,7 +7109,7 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "traceability"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ members = [
|
||||
exclude = ["third_party"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.92"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
@@ -31,7 +31,10 @@ capture sharpening, noise reduction, lens correction, spectral film
|
||||
simulation. Crop, straighten and correct converging verticals, spot repair,
|
||||
and local adjustments over masks the model draws — click a subject or a
|
||||
category, then paint, subtract a gradient or keep only where two selections
|
||||
agree, grow or shrink the edge. Focus peaking and a raw histogram for judging
|
||||
agree, grow or shrink the edge. A mask's sliders add to the photograph's, the
|
||||
film's among them, so a sky can be burned in on the print as a darkroom
|
||||
printer would. Hot and dead photosites are mended before the demosaic, with
|
||||
nothing to set. Focus peaking and a raw histogram for judging
|
||||
what is recoverable. Presets, with a collection shipped in the application —
|
||||
everyday corrections, and a look for each measured colour, cinema and
|
||||
black-and-white stock — and Lightroom presets imported as looks that leave a
|
||||
@@ -93,7 +96,7 @@ controls, its place in the chain and its tests.
|
||||
|
||||
## Where it stands
|
||||
|
||||
**0.18.1**, twenty-seven tagged releases in. 192 numbered requirements in
|
||||
**0.18.2**, twenty-eight tagged releases in. 192 numbered requirements in
|
||||
scope, 84% of them claimed by code and [traced to it](docs/dev/traceability.md);
|
||||
the rest are written down rather than merely absent.
|
||||
|
||||
|
||||
+22
-6
@@ -41,18 +41,34 @@ matters — see [`src/bake.rs`](src/bake.rs) for the argument:
|
||||
1. **A 3×3 matrix**, linear sRGB to the three layers' exposure. Exact, not an
|
||||
approximation: the reconstructed scene spectrum is linear in the sRGB
|
||||
triple, so the integral collapses into nine numbers.
|
||||
2. **Three 1D curves**, log exposure to density, sampled at 256 points.
|
||||
3. **One 32³ lookup**, density to linear sRGB — dye absorption, the print
|
||||
through the negative, the paper, the viewing illuminant and the chromatic
|
||||
adaptation, all of which take exactly three numbers in.
|
||||
2. **Three 1D curves**, log exposure to density, sampled at 256 points — one
|
||||
row per development time the datasheet measures. Push picks between the
|
||||
rows, and interpolating them is exact, because density is linear in push
|
||||
between two measured processes.
|
||||
3. **One 32³ lookup**, density to linear sRGB — dye absorption, the viewing
|
||||
illuminant and the chromatic adaptation, all of which take exactly three
|
||||
numbers in. A printed negative is two: the film's cube ends at the paper's
|
||||
log exposure through the negative, the enlarger's exposure is added there,
|
||||
and the paper's own curve row and cube take it to linear sRGB.
|
||||
|
||||
Per pixel that is a matrix multiply, three curve taps and one texture fetch.
|
||||
Splitting 2 from 3, rather than baking one LUT over exposure, is measured
|
||||
Per pixel that is a matrix multiply, a handful of curve taps and one texture
|
||||
fetch — two for a print. Splitting 2 from 3, rather than baking one LUT over exposure, is measured
|
||||
rather than assumed: the curve carries all the sharp shape and the dye mixing
|
||||
is smooth, so folding the curve into the 3D lookup would need it three times
|
||||
larger for the same error. At 32³ the worst interpolation error is about 0.003
|
||||
in linear sRGB, below one 8-bit code value, and there is a test that says so.
|
||||
|
||||
**No slider is baked.** Camera exposure is a gain before the matrix, push
|
||||
chooses between curve rows, print exposure is the addition between the two
|
||||
cubes, and format sets the grain; each reaches the shader as a uniform that is
|
||||
linear in what it does. That is what lets a mask layer hold its own film
|
||||
settings, and a pixel under several layers take the weighted average of them.
|
||||
Only the enlarger's filtration is solved at bake time, against the
|
||||
photograph's exposure — an enlarger has one filtration for the whole print —
|
||||
so the film's Exposure, set on the whole photograph, is the one slider that
|
||||
rebakes. The stock and its
|
||||
paper are the photograph's; a layer has no picker.
|
||||
|
||||
## Adding a stock
|
||||
|
||||
If spektrafilm has it, add its name to `STOCKS` in
|
||||
|
||||
@@ -347,6 +347,8 @@ RawImage (sensor data, CPU)
|
||||
│ upload
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ hot/dead photosites │ repaired on the mosaic (FR-RAW-3)
|
||||
├─────────────────────┤
|
||||
│ black/white levels │ integer normalise
|
||||
├─────────────────────┤
|
||||
│ demosaic │ Bayer or Markesteijn (X-Trans, FR-RAW-5)
|
||||
@@ -379,8 +381,29 @@ RawImage (sensor data, CPU)
|
||||
|
||||
Working precision is f16 in a linear wide-gamut space, quantising once at the output transform.
|
||||
|
||||
**A hot or dead photosite is repaired before the demosaic, not after.** Past it, one photosite of
|
||||
nonsense is a coloured cross three pixels wide that no later stage can tell from detail. The pass
|
||||
(`shaders/hot_pixels.wgsl`, run by `Demosaicer::run` into a second buffer) replaces a photosite that
|
||||
stands apart from every same-colour photosite in its 5×5 window *and* from each of its eight
|
||||
immediate neighbours with the nearest value that neighbourhood vouches for. The second test is what
|
||||
keeps a star: real light arrives through a lens and lights a patch, so its neighbours are lit too. A
|
||||
6×6 sensor-anchored colour tile serves Bayer and X-Trans alike, every path that demosaics gets it,
|
||||
and there is no setting.
|
||||
|
||||
**A mask layer runs inside this chain, not after it.** Its settings are offsets to the global ones
|
||||
(`mask::offset_onto`), and each operation a layer touches is composed at the operation's own place:
|
||||
the global fragment and each layer's combined fragment read the same input, and the pixel moves by
|
||||
each layer's weighted difference, `c_g + Σ wᵢ(cᵢ − c_g)`. At full weight that is the combined
|
||||
setting exactly and at zero the global result exactly, so global contrast −30 under a layer at −20
|
||||
is contrast −50 where contrast runs, never −30 now and −20 again later — which is what the layer
|
||||
chain did before 0.18.1, and how the shadows of a night shot went magenta. A photograph with no
|
||||
layers composes to the same shader byte for byte. The film is the one exception, because it is a
|
||||
rendering rather than an adjustment and cross-fading two developments is not what a region of a
|
||||
pushed negative looks like: an operation that `blends_settings` has its uniforms averaged by mask
|
||||
weight instead, and runs once (FR-DEV-3f).
|
||||
|
||||
There is a second reduction that does not hang off the bottom of this chain. The raw histogram
|
||||
(FR-CULL-3) taps the demosaiced scene-linear texture directly — the box four rows from the top —
|
||||
(FR-CULL-3) taps the demosaiced scene-linear texture directly — the demosaic box's output —
|
||||
because what it measures is the file rather than the render. See §5.5.
|
||||
|
||||
### 5.3 Tiling and scheduling
|
||||
|
||||
+5
-2
@@ -510,8 +510,11 @@ Failures increment `attempts` and set `not_before` to an exponential backoff. Af
|
||||
count the job is marked failed and attached to its image as a typed error (NFR-ARCH-4) — one
|
||||
corrupt file does not stall the queue, and the user can see which files failed and why.
|
||||
|
||||
**A job runner never touches the UI executor**, and `Interactive` work runs on the decode pool with
|
||||
the I/O pool behind it (ARCH §7.1).
|
||||
**A job runner never touches the UI executor**, and `Interactive` work runs on the decode executor
|
||||
with the I/O executor behind it (ARCH §7.1). Those are named rather than pooled today: thumbnails
|
||||
on demand start as `decode:thumbs` and the sweeps as `decode:thumb-sweep` and `decode:metadata`,
|
||||
through `dr_ui::executors::spawn` and each on a thread of its own, and the thread counts §7.1 gives
|
||||
are a budget nothing yet enforces (NFR-ARCH-1).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -325,9 +325,12 @@ without measuring them:
|
||||
Stated because §7 of [display-and-extension.md](display-and-extension.md) asks
|
||||
for it, and because each of these could move the numbers.
|
||||
|
||||
- **Local adjustments.** The mask stack is a separate chain per layer and is not
|
||||
in any row above. `render_masked` takes them and the fused shader addresses
|
||||
them per layer, so a heavily masked edit costs more than `all`.
|
||||
- **Local adjustments.** Not in any row above. Since 0.18.1 a layer is no longer
|
||||
a separate chain after the global one: each operation a layer touches runs a
|
||||
second fragment at its own place in the chain, blended by the layer's mask
|
||||
([architecture.md §5.2](architecture.md)), and `render_masked` binds the mask
|
||||
array the fused shader samples. A heavily masked edit therefore costs more
|
||||
than `all`, by roughly one fragment per touched operation per layer.
|
||||
- **Spot repairs.** These add detail passes, and their cost is per spot.
|
||||
- **Lens corrections.** Not part of `EditGraph::default_chain` — they are built
|
||||
from a matched profile — so the `point` row does not include the warp chain.
|
||||
|
||||
+32
-2
@@ -38,6 +38,14 @@ work of 0.15.0 and 0.16.0 left open.
|
||||
in §5 is rewritten around what the Flatpak has still not shown; albums brought the first SAF code,
|
||||
which FR-PLAT-AND-1's entry now describes, along with why its new tag overstates it.
|
||||
|
||||
**And for 0.18.2.** FR-CULL-13's write-path half is held by a test now, and §2 records the evidence
|
||||
half it leaves; NFR-ARCH-1's executors are named and guarded but not pooled, recorded in §4. Three
|
||||
things closed without having had an entry: a mask layer's film settings, which the panel offered
|
||||
and which moved nothing until 0.18.2 (FR-DEV-3f); hot and dead photosites, repaired on the mosaic
|
||||
before the demosaic, for Bayer and X-Trans alike and with no setting (FR-RAW-3; the defect-map
|
||||
reader in `dr-decode` is still not wired in, and a CR2 carries no map for it to read); and the
|
||||
tablet's scrollers, which §4a now describes.
|
||||
|
||||
---
|
||||
|
||||
## 1. Plugins — post-v1 since 2026-09-19
|
||||
@@ -91,7 +99,7 @@ somebody reads the matrix.
|
||||
## 2. Culling — the stated differentiator, half built
|
||||
|
||||
[D11](requirements.md) names culling "the core differentiator". FR-CULL-1 through -5 and -8 through
|
||||
-13 are built. Two are not.
|
||||
-12 are built, and FR-CULL-13 is half built. Two are not built at all.
|
||||
|
||||
**FR-CULL-3 — Raw-truth overlays. Built, all three bullets.** Focus peaking is
|
||||
`core/dr-gpu/src/focus.rs` and `ui/dr-ui/src/peaking.rs`; the raw histogram and the raw clipping
|
||||
@@ -133,6 +141,17 @@ capture instant and size), `ui/dr-ui/src/duplicates.rs` proves each group the sa
|
||||
compares their edits, and a group is folded onto one survivor with the others trashed in one
|
||||
transaction, from the Duplicate originals review in the sidebar and in Settings.
|
||||
|
||||
**FR-CULL-13 — Evidence, never verdicts. The write-path half.** Every write of a rating, flag,
|
||||
colour label or trash membership in the shipped code is enumerated by
|
||||
`tools/traceability/src/verdicts.rs`, which parses the tree with `syn` and holds each site to a
|
||||
reviewed list with its reason: a key, click or tap, a function writing for callers that are checked
|
||||
in turn, or a verdict carried from elsewhere (a sidecar or `.xmp` pull, the sync merge, the catalog
|
||||
mirrored to a file, duplicates consolidation). An unlisted write, or a listed one that is gone,
|
||||
fails `cargo test -p traceability`. What is not built is the evidence itself: chips for clipping,
|
||||
focus, burst membership and face counts on the grid cell and in FR-CULL-4's mode, shown as absent
|
||||
rather than zero, with a filter per signal. Eye state is the one signal shown today, on People's
|
||||
face cells and as a library filter.
|
||||
|
||||
**FR-CULL-6 — Compare and survey.** Absent. No side-by-side view, no synchronised zoom or pan.
|
||||
This is the one of the four with no adjacent machinery at all, and it is also the one that most
|
||||
directly distinguishes culling from browsing.
|
||||
@@ -160,7 +179,7 @@ place.
|
||||
|
||||
---
|
||||
|
||||
## 4. The render path — FR-DSP-2, FR-DSP-4, NFR-RES-2
|
||||
## 4. The render path — FR-DSP-2, FR-DSP-4, NFR-RES-2, NFR-ARCH-1
|
||||
|
||||
**FR-DSP-2 — Tiled computation. Unbuilt, and under challenge.** [architecture.md §6.2](architecture.md)
|
||||
calls for tiling "from day one" on the grounds that retrofitting it is a rewrite. It was not built,
|
||||
@@ -199,6 +218,17 @@ and no spill. Spike S6 — a tiled pipeline on a
|
||||
mid-range Android device with an image larger than available GPU memory — is the one that would
|
||||
settle both this and FR-DSP-2, and there is no evidence it has run.
|
||||
|
||||
**NFR-ARCH-1 — Named executors. Named and guarded, not bounded.** `dr_ui::executors` names the
|
||||
five executors of [architecture.md §7.1](architecture.md) with their thread counts, every
|
||||
long-lived worker in `dr-ui` and the Android entry point starts through its `spawn` as
|
||||
`<executor>:<role>`, and `net_runtime`'s `block_on` fails a debug or test build on the UI thread.
|
||||
The counts are a budget and not yet a limit: each job still gets a thread of its own, and a pool
|
||||
sized from `Executor::threads` is where NFR-ARCH-2's priority classes would live. The guard covers
|
||||
`block_on` only — not a synchronous file read or a catalog query on the UI thread, which the library
|
||||
and People screens make on a click by design ([catalog.md §1](catalog.md)). The two mask workers in
|
||||
`masks_ui.rs` still call `std::thread::spawn`, and the threads the core crates start are outside the
|
||||
module.
|
||||
|
||||
---
|
||||
|
||||
## 4a. Develop, masks and the keyboard — what the 0.15.0 and 0.16.0 work left open
|
||||
|
||||
@@ -269,6 +269,17 @@ identification, and camera-native colour matrices. Demosaic quality shall be sel
|
||||
least a fast method for preview and a high-quality method for export (FR-EXP-9 requires export to
|
||||
use the latter).
|
||||
|
||||
*Status (2026-09-27), defective photosites.* Hot and dead photosites are repaired on the mosaic,
|
||||
before the demosaic, where each is still one wrong value rather than a coloured cross three pixels
|
||||
wide (`core/dr-gpu/src/shaders/hot_pixels.wgsl`). A photosite is repaired only where it stands apart
|
||||
from every same-colour photosite in its 5×5 window and from each of its eight immediate neighbours,
|
||||
which leaves stars and glints alone, and it takes the value of its brightest (or, when dead,
|
||||
darkest) same-colour neighbour, so nothing is invented. A 6×6 sensor-anchored colour tile serves
|
||||
Bayer and X-Trans alike; export and every other path that demosaics get the repair, and there is no
|
||||
setting. `core/dr-gpu/tests/hot_pixels.rs` renders a frame with and without a defect and compares
|
||||
the finished pixels. The DNG defect map `dr_decode::defects` reads is not used: few files carry
|
||||
one, and a CR2 none.
|
||||
|
||||
**FR-RAW-4 — Robustness.** A malformed or hostile RAW file shall not crash the application or
|
||||
compromise the process. Decode failures are reported per-file and do not abort a batch.
|
||||
|
||||
@@ -315,6 +326,13 @@ once, at the final export or display stage.
|
||||
- Crop, straighten, rotate, flip
|
||||
- Local adjustments: linear gradient, radial gradient, and brush masks
|
||||
|
||||
*Resolved 2026-09-26:* a mask layer's settings are **offsets to the photograph's**, applied at each
|
||||
operation's own place in the chain — global contrast −30 under a layer at −20 is −50 inside the
|
||||
mask, applied once. A moved switch or choice replaces the global one, and an offset that brings an
|
||||
operation back to neutral undoes the global setting inside the mask. Layers used to run as a second
|
||||
chain after every global operation, which compounded the two edits in ways neither slider showed
|
||||
([architecture.md §5.2](architecture.md); `core/dr-gpu/tests/local_adjustments.rs`).
|
||||
|
||||
**FR-DEV-3a — Self-describing operations.** Every processing operation shall declare its own
|
||||
parameters through a descriptor, so that adding an operation requires no changes to frontend code.
|
||||
An operation declares *what* its parameters are; the frontend decides *how* to present them.
|
||||
|
||||
@@ -411,6 +411,16 @@ tract, Slint's compiler, wgpu — so with the cargo cache warm it is minutes and
|
||||
better part of half an hour. Worth noting because the runner is one machine and the legs run in
|
||||
parallel on it; if it starts starving the desktop leg, `needs: desktop` serialises them.
|
||||
|
||||
**Disk is the tighter budget.** The runner has one 99 GB disk shared with its container images. At
|
||||
rest it holds about 23 GB; the desktop leg's restored target cache, the models and the dependency
|
||||
build bring it to about 78 GB before a test runs, and v0.18.0's run ended at 92 GB used. v0.18.1's
|
||||
release build then died with "No space left on device", so that tag has no release page. Since
|
||||
then the desktop leg deletes its test executables, `target/debug/examples` and
|
||||
`target/debug/incremental` after the Test step and before the release build — they are relinked
|
||||
whenever a source changes, and the cache exists for the dependency rlibs — and prints the disk
|
||||
again beside its Disk before and after lines. A second target's cache on the same disk is the
|
||||
first thing to look at if a leg runs out again.
|
||||
|
||||
---
|
||||
|
||||
## 8. Requirements
|
||||
|
||||
+10
-4
@@ -235,7 +235,10 @@ take the crop back or keep it.
|
||||
`Local` in the rail turns the column into a mask stack. `Find subjects` runs a
|
||||
segmentation model over the photograph; what it recognises appears as a list
|
||||
of categories with how much of the frame each covers. Click one and it is a
|
||||
mask — then every slider below edits only that region.
|
||||
mask — then every slider below edits only that region. A mask's slider adds to
|
||||
the photograph's own rather than repeating it: contrast −20 in the mask over
|
||||
−30 on the whole frame is −50 there, and +30 in the mask cancels the frame's
|
||||
−30 inside it.
|
||||
|
||||

|
||||
|
||||
@@ -270,13 +273,16 @@ sensor does not. The list opens over the column and scrolls on its own — by
|
||||
wheel, drag or flick, or with `Up`, `Down` and `Enter` — down to the
|
||||
black-and-white stocks at its end.
|
||||
|
||||

|
||||
|
||||
The film's sliders work on a mask as they do on the whole photograph, the way
|
||||
a printer dodges and burns: in a layer, `Print exposure` darkens or lightens
|
||||
a printer dodges and burns: in a layer, `Print Exposure` darkens or lightens
|
||||
that region of the print, `Push` develops it further, and the stock stays the
|
||||
one the photograph was made on. Where layers overlap, the photograph takes the
|
||||
average of what they ask for.
|
||||
average of what they ask for. Below, a negative stock on an alpine frame, then
|
||||
a gradient over the sky whose `Print Exposure` burns it in.
|
||||
|
||||

|
||||

|
||||
|
||||
### History, snapshots, presets
|
||||
|
||||
|
||||
@@ -306,7 +306,10 @@ take the crop back or keep it.</p>
|
||||
<p><code>Local</code> in the rail turns the column into a mask stack. <code>Find subjects</code> runs a
|
||||
segmentation model over the photograph; what it recognises appears as a list
|
||||
of categories with how much of the frame each covers. Click one and it is a
|
||||
mask — then every slider below edits only that region.</p>
|
||||
mask — then every slider below edits only that region. A mask's slider adds to
|
||||
the photograph's own rather than repeating it: contrast −20 in the mask over
|
||||
−30 on the whole frame is −50 there, and +30 in the mask cancels the frame's
|
||||
−30 inside it.</p>
|
||||
<figure><img loading="lazy" src="media/local-categories.png" alt="What the model found in an urban scene: ground, architecture, sky, vegetation"><figcaption>What the model found in an urban scene: ground, architecture, sky, vegetation</figcaption></figure>
|
||||
<figure><img loading="lazy" src="media/local-segment.png" alt="The sky chosen: tinted on the photograph, and the column now scoped to it"><figcaption>The sky chosen: tinted on the photograph, and the column now scoped to it</figcaption></figure>
|
||||
<p>A mask is a stack of parts. Paint into it, subtract a gradient from it, grow
|
||||
@@ -329,12 +332,14 @@ named stock; below it, the print exposure and push controls a film has and a
|
||||
sensor does not. The list opens over the column and scrolls on its own — by
|
||||
wheel, drag or flick, or with <code>Up</code>, <code>Down</code> and <code>Enter</code> — down to the
|
||||
black-and-white stocks at its end.</p>
|
||||
<figure><img loading="lazy" src="media/film.gif" alt="Opening the film list, scrolling it, choosing Velvia, then holding Before"><figcaption>Opening the film list, scrolling it, choosing Velvia, then holding Before</figcaption></figure>
|
||||
<p>The film's sliders work on a mask as they do on the whole photograph, the way
|
||||
a printer dodges and burns: in a layer, <code>Print exposure</code> darkens or lightens
|
||||
a printer dodges and burns: in a layer, <code>Print Exposure</code> darkens or lightens
|
||||
that region of the print, <code>Push</code> develops it further, and the stock stays the
|
||||
one the photograph was made on. Where layers overlap, the photograph takes the
|
||||
average of what they ask for.</p>
|
||||
<figure><img loading="lazy" src="media/film.gif" alt="Opening the film list, scrolling it, choosing Velvia, then holding Before"><figcaption>Opening the film list, scrolling it, choosing Velvia, then holding Before</figcaption></figure>
|
||||
average of what they ask for. Below, a negative stock on an alpine frame, then
|
||||
a gradient over the sky whose <code>Print Exposure</code> burns it in.</p>
|
||||
<figure><img loading="lazy" src="media/film-local.gif" alt="Kodak Ektar 100 on the whole frame, a gradient turned to cover the sky, its Print Exposure raised to burn the sky in, then holding Before"><figcaption>Kodak Ektar 100 on the whole frame, a gradient turned to cover the sky, its Print Exposure raised to burn the sky in, then holding Before</figcaption></figure>
|
||||
<h3 id="history-snapshots-presets">History, snapshots, presets</h3>
|
||||
<p>Every change is a step; <code>Undo</code> and the History panel walk them. <code>Snapshot</code>
|
||||
keeps the current state under a name. <code>Presets…</code> saves the settings to
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -4,7 +4,7 @@
|
||||
# makes `makepkg -si` in this directory install what you are actually working
|
||||
# on. Swap `source` for a tagged tarball when there is something to release.
|
||||
pkgname=darkroom
|
||||
pkgver=0.18.1
|
||||
pkgver=0.18.2
|
||||
# Back to 1 with the version: a new pkgver is a new archive name, so there is
|
||||
# nothing for makepkg to reuse and nothing for a release number to disambiguate.
|
||||
pkgrel=1
|
||||
|
||||
@@ -57,6 +57,7 @@ TEAPOT = '_MG_4918'
|
||||
PANO_FIRST, PANO_LAST = '_MG_8320', '_MG_8331'
|
||||
NY_FIRST, NY_LAST = '_MG_8393', '_MG_8397'
|
||||
ALPS_ROW = ['_MG_8322', '_MG_8328'] # the second row of the alps, first and last
|
||||
ALPS_SKY = '_MG_8330' # an upright alpine frame, its top third sky and cloud
|
||||
TOWER = '_MG_8693' # towers shot looking up: verticals that converge
|
||||
ROAD = '_MG_8672' # a road under a sky, with marks on it: masks and repair
|
||||
ROAD_MARK = (0.305, 0.794) # a dark mark on its tarmac
|
||||
@@ -1005,6 +1006,64 @@ def film():
|
||||
undo_all()
|
||||
|
||||
|
||||
FILM_LOCAL = 'Kodak Ektar 100' # a negative, so it is printed and has a print exposure
|
||||
|
||||
|
||||
def choose_film(stock):
|
||||
"""`stock` chosen from the film list, which is open at its top."""
|
||||
px, py, _, _ = film_list_origin()
|
||||
vx0, vy0, vx1, vy1 = dr.rect(f'{stock}@Text')
|
||||
dr.click(px + (vx0 + vx1) // 2, py + (vy0 + vy1) // 2)
|
||||
pause(3)
|
||||
|
||||
|
||||
def gradient_over_top(to_fy):
|
||||
"""The linear gradient just added, turned to fall from the top of the
|
||||
frame and its middle moved to `to_fy`. A new one is laid across the
|
||||
middle facing right, its rotate handle below the centre: that handle,
|
||||
dragged round to the right, turns it a quarter to face up."""
|
||||
cx, cy = dr.photo(0.5, 0.5)
|
||||
handles = [(int(h['x'] + h['w'] / 2), int(h['y'] + h['h'] / 2))
|
||||
for h in dr.matches('id:GradientHandles::drag')]
|
||||
rx, ry = max(handles, key=lambda p: p[1])
|
||||
dr.drag(rx, ry, cx + (ry - cy), cy, 25)
|
||||
pause(1.0)
|
||||
dr.drag(cx, cy, *dr.photo(0.5, to_fy), 25)
|
||||
pause(1.2)
|
||||
|
||||
|
||||
@scene(media=['film-local.gif'], sources=MASK_SRC + ['core/dr-film/src/**', 'core/dr-pipeline/src/ops/film_sim.rs'])
|
||||
def film_local():
|
||||
"""A negative stock on the whole photograph, then a gradient over the
|
||||
sky whose Print Exposure burns it in, as a printer would under the
|
||||
enlarger, the mask drawn as its edge so the print shows through; Done
|
||||
Masking, then Before held."""
|
||||
at_develop(ALPS_SKY)
|
||||
in_column('Film@Button')
|
||||
film_list_open()
|
||||
choose_film(FILM_LOCAL)
|
||||
tool('Local')
|
||||
rec('film-local')
|
||||
pause(0.6)
|
||||
dr.click(*in_column('Linear@Button'))
|
||||
pause(1.5)
|
||||
gradient_over_top(0.36)
|
||||
dr.click(*in_column('Edge@RadioButton')) # an outline, so the tint hides nothing
|
||||
pause(1.2)
|
||||
slide('Print Exposure', 28, 30)
|
||||
pause(2.0)
|
||||
dr.click_on('Done Masking@Button')
|
||||
pause(2.0)
|
||||
hold_before(1.6)
|
||||
pause(1.0)
|
||||
cut()
|
||||
tool('Local') # masks shown tinted again, as the other scenes
|
||||
dr.click(*in_column('Tint@RadioButton')) # expect; offered while one exists
|
||||
pause(0.6)
|
||||
tool('Photo')
|
||||
undo_all()
|
||||
|
||||
|
||||
FILM_LAST = 'Ilford HP5 Plus' # the last stock `DevelopSession::film_choices` lists
|
||||
FILM_ROWS = 28 # None and the 27 camera stocks
|
||||
FILM_ROW, FILM_LIST_MAX = 32, 320
|
||||
|
||||
Reference in New Issue
Block a user