From 6b99f67f47db54665828e218e8fbd529f53cdc08 Mon Sep 17 00:00:00 2001
From: Duncan Tourolle
Date: Sat, 26 Sep 2026 22:37:00 -0400
Subject: [PATCH] Develop a mask layer's film on its own settings
A layer offered the film's sliders and they moved nothing: its copy of
the node was never given the stock, so it stayed inactive. Film now
works in a layer the way the other adjustments do, as offsets to the
photograph's settings, but blended as settings rather than as results,
since a film is a rendering and cross-fading two developments is not
what a region on a pushed film looks like.
- dr-film bakes no slider. Exposure is a gain in the shader; push
interpolates the stock's measured processes, one curve row each; the
print is split at the paper's log exposure, so print exposure is an
addition between two lookups and exact at any setting. The enlarger
stays balanced at the photograph's exposure.
- film_sim reads all four settings as uniforms, format one-hot over a
grain count per format, so every uniform is linear in what it does.
- Operation::blends_settings lets the composer average each overlapping
layer's uniforms with the global ones by mask weight, the global
setting taking whatever weight the layers leave, and run the fragment
once. Three layers at full weight give the mean of their settings.
- The stock picker is hidden on a layer. Only the photograph's exposure
re-solves the print balance; push, print exposure and format need no
rebake at all now.
---
core/dr-film/src/bake.rs | 555 +++++++++++++++++++++------
core/dr-gpu/examples/frame_budget.rs | 5 +-
core/dr-gpu/src/adjust.rs | 28 +-
core/dr-gpu/tests/film_sim.rs | 269 ++++++++++++-
core/dr-pipeline/src/history.rs | 4 +-
core/dr-pipeline/src/lib.rs | 4 +-
core/dr-pipeline/src/mask.rs | 129 ++++++-
core/dr-pipeline/src/operation.rs | 113 +++++-
core/dr-pipeline/src/ops/film_sim.rs | 325 +++++++++++-----
core/dr-pipeline/src/ops/mod.rs | 2 +-
core/dr-pipeline/src/preset.rs | 4 +-
core/dr-pipeline/src/sidecar.rs | 4 +-
core/dr-pipeline/src/state.rs | 4 +-
docs/dev/requirements.md | 9 +
docs/dev/traceability.md | 76 ++--
docs/manual/README.md | 6 +
docs/manual/index.html | 5 +
ui/dr-ui/src/develop/render.rs | 280 ++++++++++----
ui/dr-ui/src/develop/tabs.rs | 8 +
ui/dr-ui/src/develop_ui.rs | 13 +-
20 files changed, 1484 insertions(+), 359 deletions(-)
diff --git a/core/dr-film/src/bake.rs b/core/dr-film/src/bake.rs
index 5689beb..f7a5b83 100644
--- a/core/dr-film/src/bake.rs
+++ b/core/dr-film/src/bake.rs
@@ -21,14 +21,16 @@
//! curves and dyes, the viewing illuminant, the adaptation — all of it takes
//! three numbers in and gives three numbers out. So it bakes into one small
//! 3D lookup, and the per-pixel cost is a matrix multiply, three curve taps
-//! and one texture fetch.
+//! and one texture fetch. A print is two: the film's lookup ends at the
+//! paper's log exposure, where the enlarger's exposure is an addition, and
+//! the paper's curve and lookup take it from there — see [`Paper`].
//!
//! Splitting 2 from 3 rather than baking a single LUT over exposure is
//! deliberate and measured: the curve carries all of the sharp shape and the
//! dye mixing is smooth, so putting the curve in the 3D LUT would force it
//! three times larger for the same error.
-use crate::profile::Profile;
+use crate::profile::{Profile, CURVE_SAMPLES};
use crate::spectrum::{illuminant, Spectrum, Viewing};
use crate::tables::{SPECTRUM, SRGB_BASIS};
@@ -47,7 +49,19 @@ pub const MID_GREY: f32 = 0.184;
/// that on: the error is already under what the output can represent.
pub const LUT_SIZE: usize = 32;
-/// What to develop, and how.
+/// TRACES: FR-DEV-3f
+/// The most development times a stock may measure: one curve row, and one
+/// push station, each. Every stock shipped measures five; the ceiling is what
+/// the shader's fixed uniform block can hold.
+pub const MAX_CURVE_ROWS: usize = 8;
+
+/// What to develop: the materials, and where the enlarger is balanced.
+///
+/// **Not how far, and not how bright.** Push, print exposure and camera
+/// exposure are [`Settings`], evaluated per pixel against these tables, so
+/// that a mask layer can hold its own and a pixel under it can take the
+/// weighted average of everyone's (FR-DEV-3f). What is left here is what a
+/// photograph has one of.
pub struct Recipe<'a> {
/// The stock the picture was taken on.
pub film: &'a Profile,
@@ -55,17 +69,15 @@ pub struct Recipe<'a> {
/// what a reversal stock wants and what makes a negative come out orange
/// and inverted — that being what a negative actually looks like.
pub print: Option<&'a Profile>,
- /// Camera exposure, in stops.
- pub exposure_ev: f32,
- /// Enlarger exposure, in stops. Ignored without a `print`.
- pub print_exposure_ev: f32,
- /// TRACES: FR-DEV-3f
- /// Development, in stops of push. Positive develops longer.
+ /// The camera exposure the enlarger is balanced at, in stops. Ignored
+ /// without a `print`.
///
- /// Ignored by a stock measured at one process, of which there are many —
- /// see [`crate::profile::Profile::curves_at_push`], which returns the one
- /// measured curve rather than inventing a pushed one.
- pub push_stops: f32,
+ /// The *photograph's* exposure, never a region's. An enlarger has one
+ /// filtration for the whole print: a negative exposed a stop brighter in
+ /// one corner prints a stop darker there, and that difference is the
+ /// picture — balancing it away per pixel would erase every local exposure
+ /// change a layer made.
+ pub exposure_ev: f32,
}
impl<'a> Recipe<'a> {
@@ -76,12 +88,27 @@ impl<'a> Recipe<'a> {
film,
print,
exposure_ev: 0.0,
- print_exposure_ev: 0.0,
- push_stops: 0.0,
}
}
}
+/// TRACES: FR-DEV-3f
+/// What a pixel is developed with, against a [`Baked`] stock.
+///
+/// The shader's uniforms, as the CPU sees them: every field is linear in what
+/// the tables are indexed by, which is what lets the composer blend several
+/// layers' settings into one before the fragment runs.
+#[derive(Debug, Clone, Copy, Default, PartialEq)]
+pub struct Settings {
+ /// Camera exposure, in stops: a gain on the scene.
+ pub exposure_ev: f32,
+ /// Development, in stops of push. Positive develops longer. Nothing for a
+ /// stock measured at one process, of which there are many.
+ pub push_stops: f32,
+ /// Enlarger exposure, in stops. Nothing without a print.
+ pub print_exposure_ev: f32,
+}
+
/// A recipe reduced to three tables.
///
/// Plain `f32` with a documented layout, and no notion of a texture: what to
@@ -89,16 +116,31 @@ impl<'a> Recipe<'a> {
/// the whole model be tested on the CPU.
#[derive(Debug, Clone)]
pub struct Baked {
- /// Linear sRGB to the three layers' log₁₀ exposure, before the log — row
- /// `l`, column `c` is layer `l`'s response to sRGB channel `c`.
+ /// Linear sRGB to the three layers' exposure, before the log — row `l`,
+ /// column `c` is layer `l`'s response to sRGB channel `c`. At unit gain:
+ /// [`Settings::exposure_ev`] is applied per pixel.
pub exposure_matrix: [[f32; 3]; 3],
- /// The characteristic curves, `CURVE_SAMPLES` samples per layer, uniform
- /// over `[curve_log_min, curve_log_max]`.
+ /// The characteristic curves: `curve_rows` rows of `CURVE_SAMPLES`
+ /// samples, row after row, each uniform over
+ /// `[curve_log_min, curve_log_max]`.
+ ///
+ /// Row `r` is the stock as measured at its `r`th development time, which
+ /// is push [`Self::push_stations`]`[r]`. The rows are the measurements
+ /// themselves rather than a resampling: between two, density is linear in
+ /// push (development is interpolated in log time, and push is log time),
+ /// so interpolating the rows by push reproduces
+ /// [`Profile::curves_at_push`] exactly. A stock measured at one process
+ /// has one row.
pub curves: Vec<[f32; 3]>,
+ pub curve_rows: usize,
+ /// The push each row was developed to, ascending, one per row.
+ pub push_stations: Vec,
pub curve_log_min: f32,
pub curve_log_max: f32,
- /// Density to linear sRGB, `LUT_SIZE³` entries uniform over
- /// `[0, density_max]` on each axis.
+ /// Film density to what comes next, `LUT_SIZE³` entries uniform over
+ /// `[0, density_max]` on each axis: linear sRGB when the film is viewed
+ /// directly, and the paper's log₁₀ exposure through it, per layer, when it
+ /// is printed.
///
/// **The red axis varies fastest**, then green, then blue — that is,
/// `lut[(b * size + g) * size + r]`. Stated because it is not the order
@@ -108,60 +150,142 @@ pub struct Baked {
/// picture with red and blue transposed, which looks like a plausible
/// photograph of the wrong colour.
pub lut: Vec<[f32; 3]>,
+ /// The paper, when there is one. See [`Paper`].
+ pub paper: Option,
+ /// The deepest density any row develops to, so one lookup covers every
+ /// push.
pub density_max: f32,
pub lut_size: usize,
}
+/// TRACES: FR-DEV-3f
+/// The print half of a baked stock: enlarger to paper to viewing.
+///
+/// Split from the film's lookup at the paper's log exposure, for the reason
+/// the film is split from its own curve. The enlarger's exposure is a shift
+/// *in that log exposure*, the same stops on all three layers, so a print
+/// exposure is an addition between the two lookups — exact at any value and
+/// free per pixel. Baking it into one lookup instead needs a slice per
+/// setting, and interpolating between slices misses by several code values,
+/// because the paper's curve is the sharpest thing in the print.
+#[derive(Debug, Clone)]
+pub struct Paper {
+ /// The enlarger's filtration, per layer, in log₁₀ exposure: what makes a
+ /// mid-grey scene print neutral at the photograph's exposure. See
+ /// [`Recipe::exposure_ev`].
+ pub balance: [f32; 3],
+ /// The paper's characteristic curves, `CURVE_SAMPLES` samples uniform
+ /// over `[log_min, log_max]`.
+ pub curves: Vec<[f32; 3]>,
+ pub log_min: f32,
+ pub log_max: f32,
+ /// Paper density to linear sRGB, laid out as [`Baked::lut`] is, uniform
+ /// over `[0, density_max]`.
+ pub lut: Vec<[f32; 3]>,
+ pub density_max: f32,
+}
+
+/// Where `push` falls among the rows: the lower row and the fraction toward
+/// the next. Clamped at both ends, as `curves_at_push` clamps to the first and
+/// last measured process.
+fn push_row(stations: &[f32], push: f32) -> (usize, f32) {
+ if stations.len() < 2 {
+ return (0, 0.0);
+ }
+ let last = stations.len() - 1;
+ let hi = stations
+ .iter()
+ .position(|p| *p >= push)
+ .unwrap_or(last)
+ .max(1);
+ let lo = hi - 1;
+ let f = (push - stations[lo]) / (stations[hi] - stations[lo]).max(1e-6);
+ (lo, f.clamp(0.0, 1.0))
+}
+
impl Baked {
- /// Look a colour up the way the shader will, for tests and for previews.
+ /// Look a colour up the way the shader will, at the stock's own settings.
pub fn apply(&self, rgb: [f32; 3]) -> [f32; 3] {
+ self.apply_at(rgb, &Settings::default())
+ }
+
+ /// Look a colour up the way the shader will, for tests and for previews.
+ pub fn apply_at(&self, rgb: [f32; 3], settings: &Settings) -> [f32; 3] {
+ let gain = 2f32.powf(settings.exposure_ev);
let mut log_exposure = [0.0f32; 3];
for (l, slot) in log_exposure.iter_mut().enumerate() {
let m = self.exposure_matrix[l];
- let e = m[0] * rgb[0] + m[1] * rgb[1] + m[2] * rgb[2];
+ let e = gain * (m[0] * rgb[0] + m[1] * rgb[1] + m[2] * rgb[2]);
*slot = (e.max(0.0) + 1e-10).log10();
}
- self.sample_lut(self.sample_curves(log_exposure))
+ let density = self.sample_curves(log_exposure, settings.push_stops);
+ let through = sample_cube(&self.lut, self.lut_size, density, self.density_max);
+ let Some(paper) = &self.paper else {
+ return through;
+ };
+ let shift = settings.print_exposure_ev * 2f32.log10();
+ let paper_log = [0, 1, 2].map(|l| through[l] + paper.balance[l] + shift);
+ let paper_density = sample_curve(&paper.curves, paper.log_min, paper.log_max, paper_log);
+ sample_cube(&paper.lut, self.lut_size, paper_density, paper.density_max)
}
- fn sample_curves(&self, log_exposure: [f32; 3]) -> [f32; 3] {
- let last = self.curves.len() - 1;
- let span = self.curve_log_max - self.curve_log_min;
- let mut out = [0.0f32; 3];
- for (c, slot) in out.iter_mut().enumerate() {
- let t = ((log_exposure[c] - self.curve_log_min) / span).clamp(0.0, 1.0) * last as f32;
- let i = (t.floor() as usize).min(last - 1);
- let f = t - i as f32;
- *slot = self.curves[i][c] * (1.0 - f) + self.curves[i + 1][c] * f;
+ fn sample_curves(&self, log_exposure: [f32; 3], push_stops: f32) -> [f32; 3] {
+ let (row, g) = push_row(&self.push_stations, push_stops);
+ let lo = self.sample_curve_row(log_exposure, row);
+ if self.curve_rows < 2 {
+ return lo;
}
- out
+ let hi = self.sample_curve_row(log_exposure, row + 1);
+ [0, 1, 2].map(|c| lo[c] * (1.0 - g) + hi[c] * g)
}
- fn sample_lut(&self, density: [f32; 3]) -> [f32; 3] {
- let n = self.lut_size;
- let mut base = [0usize; 3];
- let mut frac = [0f32; 3];
- for c in 0..3 {
- let t = (density[c] / self.density_max).clamp(0.0, 1.0) * (n - 1) as f32;
- base[c] = (t.floor() as usize).min(n - 2);
- frac[c] = t - base[c] as f32;
- }
- let mut out = [0.0f32; 3];
- for dx in 0..2 {
- for dy in 0..2 {
- for dz in 0..2 {
- let w = if dx == 0 { 1.0 - frac[0] } else { frac[0] }
- * if dy == 0 { 1.0 - frac[1] } else { frac[1] }
- * if dz == 0 { 1.0 - frac[2] } else { frac[2] };
- let e = self.lut[((base[2] + dz) * n + base[1] + dy) * n + base[0] + dx];
- for c in 0..3 {
- out[c] += w * e[c];
- }
+ fn sample_curve_row(&self, log_exposure: [f32; 3], row: usize) -> [f32; 3] {
+ let samples = self.curves.len() / self.curve_rows;
+ let curve = &self.curves[row * samples..(row + 1) * samples];
+ sample_curve(curve, self.curve_log_min, self.curve_log_max, log_exposure)
+ }
+}
+
+/// Three curves sampled uniformly over `[log_min, log_max]`, read at a log
+/// exposure per layer. Clamped at both ends, as the shader's is.
+fn sample_curve(curve: &[[f32; 3]], log_min: f32, log_max: f32, at: [f32; 3]) -> [f32; 3] {
+ let last = curve.len() - 1;
+ let span = log_max - log_min;
+ let mut out = [0.0f32; 3];
+ for (c, slot) in out.iter_mut().enumerate() {
+ let t = ((at[c] - log_min) / span).clamp(0.0, 1.0) * last as f32;
+ let i = (t.floor() as usize).min(last - 1);
+ let f = t - i as f32;
+ *slot = curve[i][c] * (1.0 - f) + curve[i + 1][c] * f;
+ }
+ out
+}
+
+/// A cube of `n³` triples over `[0, max]` per axis, red fastest, read
+/// trilinearly.
+fn sample_cube(lut: &[[f32; 3]], n: usize, density: [f32; 3], max: f32) -> [f32; 3] {
+ let mut base = [0usize; 3];
+ let mut frac = [0f32; 3];
+ for c in 0..3 {
+ let t = (density[c] / max).clamp(0.0, 1.0) * (n - 1) as f32;
+ base[c] = (t.floor() as usize).min(n - 2);
+ frac[c] = t - base[c] as f32;
+ }
+ let mut out = [0.0f32; 3];
+ for dx in 0..2 {
+ for dy in 0..2 {
+ for dz in 0..2 {
+ let w = if dx == 0 { 1.0 - frac[0] } else { frac[0] }
+ * if dy == 0 { 1.0 - frac[1] } else { frac[1] }
+ * if dz == 0 { 1.0 - frac[2] } else { frac[2] };
+ let e = lut[((base[2] + dz) * n + base[1] + dy) * n + base[0] + dx];
+ for c in 0..3 {
+ out[c] += w * e[c];
}
}
}
- out
}
+ out
}
/// Linear sRGB to the three layers' exposure, mid-grey normalised.
@@ -204,12 +328,7 @@ pub fn exposure_matrix(film: &Profile) -> [[f32; 3]; 3] {
/// goes: the mask is a fixed density, so balancing mid-grey to neutral cancels
/// it — which is why a printed negative looks like a photograph while a scanned
/// one looks orange.
-fn print_balance(
- film: &Profile,
- paper: &Profile,
- exposure_ev: f32,
- print_exposure_ev: f32,
-) -> [f32; 3] {
+pub fn print_balance(film: &Profile, paper: &Profile, exposure_ev: f32) -> [f32; 3] {
let matrix = exposure_matrix(film);
let scene = MID_GREY * 2f32.powf(exposure_ev);
let mut log_exposure = [0.0f32; 3];
@@ -227,7 +346,7 @@ fn print_balance(
let mut offsets = [0.0f32; 3];
for (l, slot) in offsets.iter_mut().enumerate() {
- *slot = target - (mid_raw[l] + 1e-10).log10() + print_exposure_ev * 2f32.log10();
+ *slot = target - (mid_raw[l] + 1e-10).log10();
}
offsets
}
@@ -257,77 +376,117 @@ fn paper_exposure(film: &Profile, paper: &Profile, density: [f32; 3]) -> [f32; 3
/// Bake a recipe into the tables a shader runs.
pub fn bake(recipe: &Recipe) -> Baked {
let film = recipe.film;
- let mut matrix = exposure_matrix(film);
- // Camera exposure rides in the matrix rather than in the shader: it is a
- // scalar on a linear quantity, and folding it in here costs nothing and
- // keeps the per-pixel work identical whether or not it has been moved.
- let gain = 2f32.powf(recipe.exposure_ev);
- for row in &mut matrix {
- for v in row.iter_mut() {
- *v *= gain;
- }
- }
+ // At unit gain. Camera exposure is a scalar on a linear quantity, so the
+ // shader applies it for the price of one multiply — and has to, since a
+ // layer may hold its own.
+ let matrix = exposure_matrix(film);
// TRACES: FR-DEV-3f
- // Developed to the requested push before anything else reads the curves:
- // the density ceiling, the print balance and the grain all depend on how
- // far this film was taken, and a push that only reached one of them would
- // be a contrast change wearing a push's name.
- let curves = film.curves_at_push(recipe.push_stops);
- let density_max = curves
- .iter()
- .flat_map(|row| row.iter())
- .fold(0.0f32, |a, &b| a.max(b))
- .max(1e-3);
-
- let viewing = match recipe.print {
- Some(paper) => Viewing::new(&paper.viewing_illuminant),
- None => Viewing::new(&film.viewing_illuminant),
+ // Every measured process, not the one the slider is at: the shader
+ // interpolates between rows per pixel, so a layer can push a region.
+ // Resampled to one length because the rows share a texture.
+ let measured = film.development_curves.len() >= 2
+ && film.development_times.len() == film.development_curves.len();
+ let (curves, push_stations): (Vec<[f32; 3]>, Vec) = if measured {
+ let rows = film.development_curves.len().min(MAX_CURVE_ROWS);
+ (
+ film.development_curves[..rows]
+ .iter()
+ .flat_map(|c| resample(c))
+ .collect(),
+ film.development_times[..rows]
+ .iter()
+ .map(|t| 2.0 * (t / film.development_normal).log2())
+ .collect(),
+ )
+ } else {
+ (resample(&film.density_curves), vec![0.0])
};
- let balance = recipe
- .print
- .map(|paper| print_balance(film, paper, recipe.exposure_ev, recipe.print_exposure_ev));
+ let curve_rows = push_stations.len();
+ // The ceiling of the deepest row, so one lookup covers every push.
+ let density_max = ceiling(&curves);
let n = LUT_SIZE;
- let mut lut = Vec::with_capacity(n * n * n);
// Blue outermost and red innermost, so the red axis varies fastest. See
// `Baked::lut`: this is the layout a 3D texture upload wants, and getting
// it backwards transposes red and blue in the finished picture.
- for b in 0..n {
- for g in 0..n {
- for r in 0..n {
- let density = [
- density_max * r as f32 / (n - 1) as f32,
- density_max * g as f32 / (n - 1) as f32,
- density_max * b as f32 / (n - 1) as f32,
- ];
- lut.push(match recipe.print.zip(balance) {
- Some((paper, offsets)) => {
- let raw = paper_exposure(film, paper, density);
- let mut log_exposure = [0.0f32; 3];
- for (l, slot) in log_exposure.iter_mut().enumerate() {
- *slot = (raw[l] + 1e-10).log10() + offsets[l];
- }
- let paper_density = paper.density_at(log_exposure);
- viewing.to_srgb(&paper.transmittance(paper_density))
- }
- None => viewing.to_srgb(&film.transmittance(density)),
- });
+ let cube = |max: f32, f: &dyn Fn([f32; 3]) -> [f32; 3]| {
+ let mut out = Vec::with_capacity(n * n * n);
+ for b in 0..n {
+ for g in 0..n {
+ for r in 0..n {
+ let step = max / (n - 1) as f32;
+ out.push(f([r as f32 * step, g as f32 * step, b as f32 * step]));
+ }
}
}
- }
+ out
+ };
+
+ let (lut, paper) = match recipe.print {
+ None => {
+ let viewing = Viewing::new(&film.viewing_illuminant);
+ (
+ cube(density_max, &|d| viewing.to_srgb(&film.transmittance(d))),
+ None,
+ )
+ }
+ Some(paper) => {
+ let viewing = Viewing::new(&paper.viewing_illuminant);
+ let curves = resample(&paper.density_curves);
+ let paper_max = ceiling(&curves);
+ let lut = cube(density_max, &|d| {
+ paper_exposure(film, paper, d).map(|raw| (raw + 1e-10).log10())
+ });
+ let paper = Paper {
+ balance: print_balance(film, paper, recipe.exposure_ev),
+ log_min: paper.log_exposure_min,
+ log_max: paper.log_exposure_max,
+ lut: cube(paper_max, &|d| viewing.to_srgb(&paper.transmittance(d))),
+ density_max: paper_max,
+ curves,
+ };
+ (lut, Some(paper))
+ }
+ };
Baked {
exposure_matrix: matrix,
curves,
+ curve_rows,
+ push_stations,
curve_log_min: film.log_exposure_min,
curve_log_max: film.log_exposure_max,
lut,
+ paper,
density_max,
lut_size: n,
}
}
+/// A curve at `CURVE_SAMPLES`, uniform over the same domain it came in on.
+fn resample(curve: &[[f32; 3]]) -> Vec<[f32; 3]> {
+ if curve.len() == CURVE_SAMPLES {
+ return curve.to_vec();
+ }
+ (0..CURVE_SAMPLES)
+ .map(|i| {
+ let at = i as f32 / (CURVE_SAMPLES - 1) as f32;
+ sample_curve(curve, 0.0, 1.0, [at; 3])
+ })
+ .collect()
+}
+
+/// The deepest density in a set of curves, floored so a lookup over it has
+/// a width.
+fn ceiling(curves: &[[f32; 3]]) -> f32 {
+ curves
+ .iter()
+ .flat_map(|row| row.iter())
+ .fold(0.0f32, |a, &b| a.max(b))
+ .max(1e-3)
+}
+
fn mean(s: &Spectrum) -> f32 {
s.iter().sum::() / SPECTRUM as f32
}
@@ -467,6 +626,10 @@ mod tests {
#[test]
fn exposure_moves_the_print_the_way_it_moves_a_photograph() {
+ // The photograph's exposure: the enlarger balanced at it, and the
+ // scene brighter by it. Mid-grey stays where the balance puts it —
+ // that is what the balance is for — so what a stop more does to a
+ // print is lift everything either side of it along the paper's curve.
let film = portra();
let paper = endura();
let brighter = bake(&Recipe {
@@ -474,7 +637,155 @@ mod tests {
..Recipe::new(&film, Some(&paper))
});
let base = bake(&Recipe::new(&film, Some(&paper)));
- assert!(brighter.apply([MID_GREY; 3])[1] > base.apply([MID_GREY; 3])[1]);
+ let one_stop = Settings {
+ exposure_ev: 1.0,
+ ..Settings::default()
+ };
+ for v in [0.02f32, 0.6] {
+ assert!(
+ brighter.apply_at([v; 3], &one_stop)[1] > base.apply([v; 3])[1],
+ "{v} did not print brighter a stop up"
+ );
+ }
+ let (a, b) = (
+ brighter.apply_at([MID_GREY; 3], &one_stop)[1],
+ base.apply([MID_GREY; 3])[1],
+ );
+ assert!(
+ (a - b).abs() < 1.0 / 255.0,
+ "the balance let mid-grey move: {a} vs {b}"
+ );
+ }
+
+ #[test]
+ fn a_region_exposed_brighter_prints_brighter_than_the_enlarger_expects() {
+ // TRACES: FR-DEV-3f
+ // A layer's exposure is the scene's, not the enlarger's: the balance
+ // stays where the photograph put it, so the region prints lighter by
+ // more than the whole photograph would, which is what dodging at the
+ // camera is.
+ let film = portra();
+ let paper = endura();
+ let base = bake(&Recipe::new(&film, Some(&paper)));
+ let rebalanced = bake(&Recipe {
+ exposure_ev: 1.0,
+ ..Recipe::new(&film, Some(&paper))
+ });
+ let one_stop = Settings {
+ exposure_ev: 1.0,
+ ..Settings::default()
+ };
+ let local = base.apply_at([MID_GREY; 3], &one_stop)[1];
+ let global = rebalanced.apply_at([MID_GREY; 3], &one_stop)[1];
+ assert!(local > base.apply([MID_GREY; 3])[1], "not brighter at all");
+ assert!(
+ local > global,
+ "a region was rebalanced as though it were the whole print: {local} vs {global}"
+ );
+ }
+
+ #[test]
+ fn more_light_through_the_enlarger_darkens_the_print() {
+ // TRACES: FR-DEV-3f
+ // Paper is negative-working. Opening the enlarger a stop is burning
+ // in, and a slider that brightened would be the wrong way round for
+ // anyone who has printed.
+ let film = portra();
+ let paper = endura();
+ let baked = bake(&Recipe::new(&film, Some(&paper)));
+ let at = |stops: f32| {
+ baked.apply_at(
+ [MID_GREY; 3],
+ &Settings {
+ print_exposure_ev: stops,
+ ..Settings::default()
+ },
+ )[1]
+ };
+ assert!(at(1.0) < at(0.0) && at(0.0) < at(-1.0));
+ }
+
+ #[test]
+ fn a_push_on_a_row_is_the_measured_curve() {
+ // TRACES: FR-DEV-3f
+ // The rows are the measured processes, so at a row the table must be
+ // that curve exactly, and between rows — density being linear in push
+ // there — it must be `curves_at_push` to rounding.
+ let film = profile(include_str!("../profiles/kodak_doublex.yaml"));
+ let baked = bake(&Recipe::new(&film, None));
+ assert_eq!(
+ baked.curve_rows, 5,
+ "Double-X measures five development times"
+ );
+
+ let span = film.log_exposure_max - film.log_exposure_min;
+ let mut worst = 0.0f32;
+ let stations = baked.push_stations.clone();
+ let mut pushes: Vec<(f32, bool)> = stations.iter().map(|p| (*p, true)).collect();
+ for k in 0..=16 {
+ pushes.push((-1.0 + 4.0 * k as f32 / 16.0, false));
+ }
+ for (push, on_row) in pushes {
+ let exact = film.curves_at_push(push);
+ for i in (0..exact.len()).step_by(7) {
+ let log = film.log_exposure_min + span * i as f32 / (exact.len() - 1) as f32;
+ let got = baked.sample_curves([log; 3], push);
+ for c in 0..3 {
+ let err = (got[c] - exact[i][c]).abs();
+ if on_row {
+ assert!(err < 1e-4, "push {push} is a row but misses it by {err}");
+ }
+ worst = worst.max(err);
+ }
+ }
+ }
+ assert!(worst < 1e-3, "between rows the density is off by {worst}");
+ }
+
+ #[test]
+ fn a_print_exposure_is_exact_at_any_setting() {
+ // TRACES: FR-DEV-3f
+ // The enlarger's exposure is added between the two lookups rather than
+ // baked into either, so no setting is nearer the tables than another.
+ // Compared against the chain evaluated spectrally, end to end, at
+ // settings chosen off every half and whole stop.
+ let film = portra();
+ let paper = endura();
+ let baked = bake(&Recipe::new(&film, Some(&paper)));
+ let offsets = print_balance(&film, &paper, 0.0);
+ let viewing = Viewing::new(&paper.viewing_illuminant);
+
+ let mut worst = 0.0f32;
+ for stops in [-2.3f32, -0.6, 0.0, 0.35, 1.7] {
+ for i in 0..14 {
+ let v = 0.004 * 2f32.powf(i as f32 * 0.6);
+ let rgb = [v, v * 0.8, v * 1.1];
+ let mut log_exposure = [0.0f32; 3];
+ for (l, slot) in log_exposure.iter_mut().enumerate() {
+ let m = baked.exposure_matrix[l];
+ *slot =
+ ((m[0] * rgb[0] + m[1] * rgb[1] + m[2] * rgb[2]).max(0.0) + 1e-10).log10();
+ }
+ let raw = paper_exposure(&film, &paper, film.density_at(log_exposure));
+ let paper_log =
+ [0, 1, 2].map(|l| (raw[l] + 1e-10).log10() + offsets[l] + stops * 2f32.log10());
+ let exact = viewing.to_srgb(&paper.transmittance(paper.density_at(paper_log)));
+ let approx = baked.apply_at(
+ rgb,
+ &Settings {
+ print_exposure_ev: stops,
+ ..Settings::default()
+ },
+ );
+ for c in 0..3 {
+ worst = worst.max((exact[c] - approx[c]).abs());
+ }
+ }
+ }
+ assert!(
+ worst < 1.0 / 255.0,
+ "the print misses the spectral chain by {worst}"
+ );
}
#[test]
@@ -550,5 +861,25 @@ mod tests {
let baked = bake(&Recipe::new(&film, None));
assert_eq!(baked.lut.len(), LUT_SIZE * LUT_SIZE * LUT_SIZE);
assert_eq!(baked.curves.len(), CURVE_SAMPLES);
+ assert_eq!(baked.curve_rows, 1);
+ assert!(baked.paper.is_none());
+
+ // A print has a second lookup and a curve of its own; a development
+ // series a row per push. Neither is inferred from the other.
+ let negative = portra();
+ let paper = endura();
+ let printed = bake(&Recipe::new(&negative, Some(&paper)));
+ let print = printed
+ .paper
+ .as_ref()
+ .expect("a printed negative has a paper");
+ assert_eq!(print.lut.len(), LUT_SIZE.pow(3));
+ assert_eq!(print.curves.len(), CURVE_SAMPLES);
+
+ let pushable = profile(include_str!("../profiles/kodak_doublex.yaml"));
+ let rows = bake(&Recipe::new(&pushable, None));
+ assert_eq!(rows.curve_rows, pushable.development_times.len());
+ assert_eq!(rows.push_stations.len(), rows.curve_rows);
+ assert_eq!(rows.curves.len(), rows.curve_rows * CURVE_SAMPLES);
}
}
diff --git a/core/dr-gpu/examples/frame_budget.rs b/core/dr-gpu/examples/frame_budget.rs
index 24f0667..ccb959e 100644
--- a/core/dr-gpu/examples/frame_budget.rs
+++ b/core/dr-gpu/examples/frame_budget.rs
@@ -680,15 +680,18 @@ fn film_tables() -> FilmTables {
FilmTables {
exposure_matrix: baked.exposure_matrix,
curves: baked.curves.clone(),
+ push_stations: baked.push_stations.clone(),
curve_log_min: baked.curve_log_min,
curve_log_max: baked.curve_log_max,
lut: baked.lut.clone(),
density_max: baked.density_max,
lut_size: baked.lut_size,
+ // Viewed directly: `STOCK` is baked without a paper above.
+ paper: None,
// Grain off. It is a per-pixel hash and would be measured; it is also
// not part of every edit, and the chain being measured here is "every
// operation active", not "every option of every operation".
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; dr_pipeline::ops::film_sim::FORMAT_COUNT],
grain_density_max: [baked.density_max; 3],
grain_uniformity: 0.97,
}
diff --git a/core/dr-gpu/src/adjust.rs b/core/dr-gpu/src/adjust.rs
index 41bf934..ebe5941 100644
--- a/core/dr-gpu/src/adjust.rs
+++ b/core/dr-gpu/src/adjust.rs
@@ -382,9 +382,23 @@ fn film_key(t: &dr_pipeline::ops::FilmTables) -> u64 {
t.curve_log_max,
t.density_max,
t.lut_size as f32,
+ t.curves.len() as f32,
+ t.lut.len() as f32,
] {
mix(v.to_bits());
}
+ for v in &t.push_stations {
+ mix(v.to_bits());
+ }
+ // The paper's balance is left out on purpose: it reaches the shader as
+ // uniforms, not texels, and it is what moves when the photograph's
+ // exposure does — keying on it would re-upload a megabyte per tick of a
+ // slider that changes three floats.
+ if let Some(p) = &t.paper {
+ for v in [p.log_min, p.log_max, p.density_max] {
+ mix(v.to_bits());
+ }
+ }
for e in t.lut.iter().step_by(8).chain(t.curves.iter().step_by(8)) {
mix(e[0].to_bits() ^ e[1].to_bits().rotate_left(11) ^ e[2].to_bits().rotate_left(22));
}
@@ -431,12 +445,16 @@ impl AdjustPass {
return;
}
+ // One row per curve — the film's at each measured push, then the
+ // paper's — and one cube per stage stacked in depth. The shader reads
+ // the layout from `FilmTables`' uniforms, not from these sizes.
+ let samples = dr_pipeline::ops::film_sim::CURVE_SAMPLES as u32;
let curves = self.upload_film(
"adjust-film-curves",
wgpu::TextureDimension::D2,
wgpu::Extent3d {
- width: t.curves.len() as u32,
- height: 1,
+ width: samples,
+ height: t.curves.len() as u32 / samples,
depth_or_array_layers: 1,
},
&to_rgba(&t.curves),
@@ -448,7 +466,7 @@ impl AdjustPass {
wgpu::Extent3d {
width: n,
height: n,
- depth_or_array_layers: n,
+ depth_or_array_layers: t.lut.len() as u32 / (n * n),
},
&to_rgba(&t.lut),
);
@@ -2303,7 +2321,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; N * N * N],
density_max: 3.0,
lut_size: N,
- grain_particles: [0.0; 3],
+ push_stations: vec![0.0],
+ paper: None,
+ grain_particles: [[0.0; 3]; dr_pipeline::ops::film_sim::FORMAT_COUNT],
grain_density_max: [3.0; 3],
grain_uniformity: 0.97,
}
diff --git a/core/dr-gpu/tests/film_sim.rs b/core/dr-gpu/tests/film_sim.rs
index 2c2349f..4b2688c 100644
--- a/core/dr-gpu/tests/film_sim.rs
+++ b/core/dr-gpu/tests/film_sim.rs
@@ -16,9 +16,12 @@
//! the CPU model.
use dr_decode::{BaseCurve, CfaPattern, CropRect, RawImage};
-use dr_film::bake::{bake, Recipe};
-use dr_gpu::{AdjustPass, Demosaicer, GpuContext};
-use dr_pipeline::ops::FilmTables;
+use dr_film::bake::{bake, Recipe, Settings};
+use dr_gpu::{AdjustPass, Demosaicer, GpuContext, LabelField, MaskPass};
+use dr_pipeline::mask::{MaskLayer, MaskSource};
+use dr_pipeline::ops::film_sim;
+use dr_pipeline::ops::film_sim::FORMAT_COUNT;
+use dr_pipeline::ops::{FilmTables, PaperTables};
use dr_pipeline::EditGraph;
const SIZE: u32 = 16;
@@ -77,15 +80,31 @@ fn tables(baked: &dr_film::Baked) -> FilmTables {
/// split a grain that never left the CPU would look exactly like a passing
/// test suite.
fn tables_with_grain(baked: &dr_film::Baked, particles: [f32; 3]) -> FilmTables {
+ // The paper, when there is one, rides behind the film: its curve as one
+ // more row, its cube stacked after the film's.
+ let mut curves = baked.curves.clone();
+ let mut lut = baked.lut.clone();
+ let paper = baked.paper.as_ref().map(|p| {
+ curves.extend_from_slice(&p.curves);
+ lut.extend_from_slice(&p.lut);
+ PaperTables {
+ balance: p.balance,
+ log_min: p.log_min,
+ log_max: p.log_max,
+ density_max: p.density_max,
+ }
+ });
FilmTables {
exposure_matrix: baked.exposure_matrix,
- curves: baked.curves.clone(),
+ curves,
+ push_stations: baked.push_stations.clone(),
curve_log_min: baked.curve_log_min,
curve_log_max: baked.curve_log_max,
- lut: baked.lut.clone(),
+ lut,
density_max: baked.density_max,
lut_size: baked.lut_size,
- grain_particles: particles,
+ paper,
+ grain_particles: [particles; FORMAT_COUNT],
grain_density_max: [baked.density_max; 3],
grain_uniformity: 0.97,
}
@@ -241,3 +260,241 @@ fn grain_reaches_the_shader_and_scales_with_the_pixel() {
"grain never reached the shader: the coarsest setting moved the pixel by {coarse_err}"
);
}
+
+/// The same render, with the film's sliders set and mask layers laid over it.
+///
+/// Every layer is a `Regions` mask over a field splitting the frame down the
+/// middle: region 0, the left half, at full weight, and the right half
+/// untouched. Returns the left and right centre pixels, linear.
+fn rendered_split(
+ ctx: &GpuContext,
+ level: u16,
+ tables: FilmTables,
+ global: Settings,
+ layers: Vec,
+) -> ([f32; 3], [f32; 3]) {
+ let source = Demosaicer::new(ctx)
+ .expect("demosaicer")
+ .run(&flat_raw(level))
+ .expect("demosaic");
+
+ let mut graph = EditGraph::default_chain();
+ graph.set_film(Some(dr_pipeline::graph::Film {
+ stock: "under_test".to_string(),
+ print: None,
+ tables: tables.clone(),
+ }));
+ graph.set_param(film_sim::ID, film_sim::EXPOSURE, global.exposure_ev);
+ graph.set_param(film_sim::ID, film_sim::PUSH, global.push_stops);
+ graph.set_param(
+ film_sim::ID,
+ film_sim::PRINT_EXPOSURE,
+ global.print_exposure_ev,
+ );
+ for layer in layers {
+ graph.masks_mut().push(layer);
+ }
+ let shader = graph.compose();
+
+ let labels: Vec = (0..SIZE * SIZE)
+ .map(|i| u32::from(i % SIZE >= SIZE / 2))
+ .collect();
+ let field = LabelField::upload(ctx, &labels, SIZE, SIZE, 2).expect("label upload");
+ let mut masks = MaskPass::new(ctx).expect("mask pass");
+ let array = masks
+ .render(graph.masks(), Some(&field), None, None, SIZE, SIZE)
+ .expect("rasterise");
+
+ let mut adjust = AdjustPass::new(ctx);
+ adjust.set_film(Some(&tables));
+ adjust
+ .render_masked(&source, &shader, SIZE, SIZE, Some(array))
+ .expect("render");
+ let (pixels, _, _) = adjust.export_pixels().expect("readback");
+ let at = |x: u32| {
+ let c = (((SIZE / 2) * SIZE + x) * 4) as usize;
+ [0, 1, 2].map(|i| srgb_to_linear(f32::from(pixels[c + i]) / 255.0))
+ };
+ (at(SIZE / 4), at(3 * SIZE / 4))
+}
+
+/// A layer over the left half holding these film offsets.
+fn left_half(id: &str, offsets: &[(dr_pipeline::descriptor::ParamId, f32)]) -> MaskLayer {
+ let mut layer = MaskLayer::new(
+ id,
+ MaskSource::Regions {
+ signature: 1,
+ level: 2,
+ ids: vec![0],
+ },
+ );
+ for (param, v) in offsets {
+ layer.set_param(film_sim::ID.0, *param, *v);
+ }
+ layer
+}
+
+fn assert_close(got: [f32; 3], want: [f32; 3], what: &str) {
+ for c in 0..3 {
+ assert!(
+ (got[c] - want[c]).abs() < 0.02,
+ "{what}, channel {c}: GPU gave {got:?}, the model says {want:?}"
+ );
+ }
+}
+
+#[test]
+fn a_print_renders_on_the_gpu_the_way_it_does_on_the_cpu_at_any_setting() {
+ // TRACES: FR-DEV-3f
+ // The print path — the film's lookup into the paper's log exposure, the
+ // enlarger added between, the paper's curve and its own lookup — is read
+ // from the same two textures as the film, at offsets. Every one of those
+ // offsets is a way to render a plausible print of the wrong thing.
+ let Some(ctx) = ctx() else {
+ eprintln!("no GPU adapter; skipping");
+ return;
+ };
+ let film = dr_film::find("kodak_portra_400").expect("stock");
+ let paper = dr_film::default_print(film).expect("paper");
+ let baked = bake(&Recipe::new(film, Some(paper)));
+
+ for settings in [
+ Settings::default(),
+ Settings {
+ print_exposure_ev: -1.3,
+ ..Settings::default()
+ },
+ Settings {
+ exposure_ev: 0.4,
+ print_exposure_ev: 0.8,
+ ..Settings::default()
+ },
+ ] {
+ for level in [6_000u16, 20_000] {
+ let input = f32::from(level) / f32::from(u16::MAX);
+ let (got, _) = rendered_split(&ctx, level, tables(&baked), settings, Vec::new());
+ assert_close(
+ got,
+ baked.apply_at([input; 3], &settings),
+ &format!("{settings:?} at {level}"),
+ );
+ }
+ }
+}
+
+#[test]
+fn a_push_between_two_measured_processes_renders_as_the_model_does() {
+ // TRACES: FR-DEV-3f
+ // Double-X measures five processes; a push between two is a mix of two
+ // rows of the curve texture, found by searching the stations uniform.
+ let Some(ctx) = ctx() else {
+ eprintln!("no GPU adapter; skipping");
+ return;
+ };
+ let film = dr_film::find("kodak_doublex").expect("stock");
+ let baked = bake(&Recipe::new(film, None));
+ assert!(baked.curve_rows > 2, "Double-X has a development series");
+
+ for push in [-0.8f32, 0.4, 1.3, 2.9] {
+ let settings = Settings {
+ push_stops: push,
+ ..Settings::default()
+ };
+ let level = 12_000u16;
+ let input = f32::from(level) / f32::from(u16::MAX);
+ let (got, _) = rendered_split(&ctx, level, tables(&baked), settings, Vec::new());
+ assert_close(
+ got,
+ baked.apply_at([input; 3], &settings),
+ &format!("push {push}"),
+ );
+ }
+}
+
+#[test]
+fn a_layer_develops_its_region_on_its_own_settings() {
+ // TRACES: FR-DEV-3f
+ // Offsets to the photograph's: print exposure +1 on a photograph at +0.5
+ // is +1.5 under the layer, and the rest of the print is untouched. Before
+ // film was blended as settings the layer's sliders moved and nothing
+ // happened, because the layer's copy of the node had no stock.
+ let Some(ctx) = ctx() else {
+ eprintln!("no GPU adapter; skipping");
+ return;
+ };
+ let film = dr_film::find("kodak_portra_400").expect("stock");
+ let paper = dr_film::default_print(film).expect("paper");
+ let baked = bake(&Recipe::new(film, Some(paper)));
+ let level = 12_000u16;
+ let input = f32::from(level) / f32::from(u16::MAX);
+
+ let global = Settings {
+ print_exposure_ev: 0.5,
+ ..Settings::default()
+ };
+ let layer = left_half(
+ "burn",
+ &[(film_sim::PRINT_EXPOSURE, 1.0), (film_sim::EXPOSURE, -0.5)],
+ );
+ let (left, right) = rendered_split(&ctx, level, tables(&baked), global, vec![layer]);
+
+ let under = Settings {
+ exposure_ev: -0.5,
+ print_exposure_ev: 1.5,
+ ..Settings::default()
+ };
+ assert_close(left, baked.apply_at([input; 3], &under), "under the layer");
+ assert_close(right, baked.apply_at([input; 3], &global), "outside it");
+ assert!(
+ left[1] < right[1] - 0.01,
+ "the burn did not darken: {left:?} vs {right:?}"
+ );
+}
+
+#[test]
+fn overlapping_layers_take_the_average_of_their_settings() {
+ // TRACES: FR-DEV-3f
+ // Three layers over the same pixels at full weight: the plain mean of what
+ // each asks for, and the global setting has no weight left. Summed, the
+ // offsets would be -2 stops of print exposure and +1 of exposure; the mean
+ // is (-1, -1, 0) / 3 and (0, 0, +1) / 3.
+ let Some(ctx) = ctx() else {
+ eprintln!("no GPU adapter; skipping");
+ return;
+ };
+ let film = dr_film::find("kodak_portra_400").expect("stock");
+ let paper = dr_film::default_print(film).expect("paper");
+ let baked = bake(&Recipe::new(film, Some(paper)));
+ let level = 12_000u16;
+ let input = f32::from(level) / f32::from(u16::MAX);
+
+ let layers = vec![
+ left_half("a", &[(film_sim::PRINT_EXPOSURE, -1.0)]),
+ left_half("b", &[(film_sim::PRINT_EXPOSURE, -1.0)]),
+ left_half("c", &[(film_sim::EXPOSURE, 1.0)]),
+ ];
+ let (left, right) = rendered_split(&ctx, level, tables(&baked), Settings::default(), layers);
+
+ let mean = Settings {
+ exposure_ev: 1.0 / 3.0,
+ print_exposure_ev: -2.0 / 3.0,
+ ..Settings::default()
+ };
+ let summed = Settings {
+ exposure_ev: 1.0,
+ print_exposure_ev: -2.0,
+ ..Settings::default()
+ };
+ let (m, s) = (
+ baked.apply_at([input; 3], &mean)[1],
+ baked.apply_at([input; 3], &summed)[1],
+ );
+ // Three times the tolerance the GPU is held to below, or a sum could pass
+ // for a mean.
+ assert!(
+ (m - s).abs() > 0.06,
+ "the mean and the sum render alike ({m} vs {s}), so this proves nothing"
+ );
+ assert_close(left, baked.apply_at([input; 3], &mean), "under all three");
+ assert_close(right, baked.apply([input; 3]), "outside them");
+}
diff --git a/core/dr-pipeline/src/history.rs b/core/dr-pipeline/src/history.rs
index 226c3fe..d2668f8 100644
--- a/core/dr-pipeline/src/history.rs
+++ b/core/dr-pipeline/src/history.rs
@@ -581,7 +581,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; 8],
density_max: 2.0,
lut_size: 2,
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; crate::ops::film_sim::FORMAT_COUNT],
+ push_stations: vec![0.0],
+ paper: None,
grain_density_max: [2.0; 3],
grain_uniformity: 1.0,
},
diff --git a/core/dr-pipeline/src/lib.rs b/core/dr-pipeline/src/lib.rs
index f78f758..215f628 100644
--- a/core/dr-pipeline/src/lib.rs
+++ b/core/dr-pipeline/src/lib.rs
@@ -132,7 +132,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; 32 * 32 * 32],
density_max: 3.0,
lut_size: 32,
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; crate::ops::film_sim::FORMAT_COUNT],
+ push_stations: vec![0.0],
+ paper: None,
grain_density_max: [3.0; 3],
grain_uniformity: 0.97,
},
diff --git a/core/dr-pipeline/src/mask.rs b/core/dr-pipeline/src/mask.rs
index f14c034..41efdc7 100644
--- a/core/dr-pipeline/src/mask.rs
+++ b/core/dr-pipeline/src/mask.rs
@@ -1656,7 +1656,7 @@ impl MaskLayer {
self.ops
.iter()
.map(|o| o.as_ref())
- .filter(|o| o.is_active() && o.detail().is_none())
+ .filter(|o| moves(*o) && o.detail().is_none())
}
/// Whether any part of this mask belongs to a different segmentation.
@@ -2087,9 +2087,30 @@ pub(crate) struct LocalOp {
/// Empty when the offsets cancel the global setting back to neutral. That
/// is still an entry, because it still means something: inside the mask
/// this operation does nothing at all.
+ ///
+ /// Empty, too, for an operation blended as settings: that entry stands
+ /// for this layer's uniforms, `mask{slot}_{op}_*`, which the composer
+ /// averages into the one fragment it runs.
pub fragment: String,
}
+/// Whether a layer's copy of `op` holds an adjustment.
+///
+/// An operation's own answer, except for one blended as settings
+/// ([`Operation::blends_settings`]): that one is active by what it *holds* —
+/// a film is active when a stock is loaded — and a layer never holds a stock,
+/// only offsets to the photograph's. So a layer's film counts as moved when
+/// its sliders are, which is the question being asked.
+fn moves(op: &dyn Operation) -> bool {
+ op.is_active()
+ || (op.blends_settings()
+ && op
+ .descriptor()
+ .params
+ .iter()
+ .any(|p| op.param(p.id) != p.default))
+}
+
/// A layer's settings for one operation, applied as offsets to the global
/// operation's.
///
@@ -2215,7 +2236,7 @@ pub(crate) fn compose_layers_revealing(
let mut combined = layer_chain();
for (dst, local) in combined.iter_mut().zip(&layer.ops) {
let local = local.as_ref();
- if !local.is_active() || local.detail().is_some() {
+ if !moves(local) || local.detail().is_some() {
continue;
}
let id = local.descriptor().id.0;
@@ -2223,9 +2244,22 @@ pub(crate) fn compose_layers_revealing(
.iter()
.map(|o| o.as_ref())
.find(|o| o.descriptor().id.0 == id);
+ // The photograph's stock, lent to the layer's copy: a layer holds
+ // offsets to a film, never one of its own.
+ if let Some(g) = g {
+ dst.set_film_tables(g.film_tables());
+ }
offset_onto(dst.as_mut(), local, g);
- if !dst.is_active() {
+ if dst.blends_settings() {
+ // Its uniforms, and no fragment: the composer blends this
+ // layer's settings with the others' and runs the global
+ // fragment once. With no stock loaded there is nothing to
+ // blend, and the global side skips the operation too.
+ if !dst.is_active() {
+ continue;
+ }
+ } else if !dst.is_active() {
out.ops.push(LocalOp {
op: id,
slot,
@@ -2250,13 +2284,16 @@ pub(crate) fn compose_layers_revealing(
}
}
- let mut fragment = dst.wgsl_body();
- for u in &op_uniforms {
- fragment = crate::operation::rewrite_uniform(
- &fragment,
- u.name,
- &format!("u.{op_prefix}_{}", u.name),
- );
+ let mut fragment = String::new();
+ if !dst.blends_settings() {
+ fragment = dst.wgsl_body();
+ for u in &op_uniforms {
+ fragment = crate::operation::rewrite_uniform(
+ &fragment,
+ u.name,
+ &format!("u.{op_prefix}_{}", u.name),
+ );
+ }
}
out.ops.push(LocalOp {
op: id,
@@ -2387,6 +2424,78 @@ mod tests {
layer
}
+ /// A stock the shader can index, with values that are not a real one's.
+ fn film_fixture() -> crate::graph::Film {
+ use crate::ops::film_sim::{CURVE_SAMPLES, FORMAT_COUNT};
+ crate::graph::Film {
+ stock: "fixture".into(),
+ print: None,
+ tables: crate::ops::FilmTables {
+ exposure_matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
+ curves: vec![[0.5; 3]; CURVE_SAMPLES],
+ push_stations: vec![0.0],
+ curve_log_min: -3.0,
+ curve_log_max: 1.0,
+ lut: vec![[0.5; 3]; 8],
+ density_max: 2.0,
+ lut_size: 2,
+ paper: None,
+ grain_particles: [[0.0; 3]; FORMAT_COUNT],
+ grain_density_max: [2.0; 3],
+ grain_uniformity: 1.0,
+ },
+ }
+ }
+
+ #[test]
+ fn a_layers_film_is_blended_as_settings_and_developed_once() {
+ // TRACES: FR-DEV-3f
+ // The film is a rendering: a layer's version of it run beside the
+ // global one and cross-faded would be the photograph developed twice.
+ // So the layer's uniforms are averaged into the global ones by weight
+ // and the fragment appears once.
+ use crate::ops::film_sim;
+ let mut graph = crate::EditGraph::default_chain();
+ graph.set_film(Some(film_fixture()));
+ let mut layer = MaskLayer::new("m1", regions(&[1]));
+ layer.set_param(film_sim::ID.0, film_sim::PRINT_EXPOSURE, 1.0);
+ assert!(layer.is_active(), "a film offset is an adjustment");
+ graph.masks_mut().push(layer);
+
+ let source = graph.compose().source;
+ assert!(source.contains("let set_w = mask_w0;"), "{source}");
+ assert!(source.contains("let set_g = max(1.0 - set_w, 0.0);"));
+ assert!(
+ source.contains("u.mask0_film_sim_pev"),
+ "the layer's setting is not read"
+ );
+ assert_eq!(
+ source.matches("let density = film_curve_pushed(").count(),
+ 1,
+ "the film was developed more than once"
+ );
+ assert!(
+ !source.contains("let local_in"),
+ "the film was blended as a result"
+ );
+ }
+
+ #[test]
+ fn a_layers_film_without_a_stock_composes_to_nothing() {
+ // TRACES: FR-DEV-3f
+ // The offsets are kept — a stock chosen later brings them back — but
+ // with no film on the photograph there is nothing for them to offset.
+ use crate::ops::film_sim;
+ let mut graph = crate::EditGraph::default_chain();
+ let mut layer = MaskLayer::new("m1", regions(&[1]));
+ layer.set_param(film_sim::ID.0, film_sim::PUSH, 1.0);
+ graph.masks_mut().push(layer);
+
+ let source = graph.compose().source;
+ assert!(!source.contains("---- film_sim ----"), "{source}");
+ assert!(!source.contains("mask0_film_sim"));
+ }
+
#[test]
fn a_layer_with_no_adjustment_is_not_in_the_shader() {
let layer = MaskLayer::new("m1", regions(&[1]));
diff --git a/core/dr-pipeline/src/operation.rs b/core/dr-pipeline/src/operation.rs
index 3eed7be..d9a224e 100644
--- a/core/dr-pipeline/src/operation.rs
+++ b/core/dr-pipeline/src/operation.rs
@@ -307,6 +307,32 @@ pub trait Operation: Send + Sync {
/// shape it should take.
fn set_film_tables(&mut self, _tables: Option<&crate::ops::film_sim::FilmTables>) {}
+ /// TRACES: FR-DEV-3f
+ /// The stock's tables this operation holds, for a mask layer's copy of it.
+ ///
+ /// The other half of [`Self::set_film_tables`]. A layer holds offsets,
+ /// never a stock of its own — the photograph is made on one film — so the
+ /// composer hands the global operation's tables to the layer's combined
+ /// copy through this pair.
+ fn film_tables(&self) -> Option<&crate::ops::film_sim::FilmTables> {
+ None
+ }
+
+ /// TRACES: FR-DEV-3 | FR-DEV-3f
+ /// Whether a mask layer's version of this operation is blended as
+ /// *settings* rather than as a result.
+ ///
+ /// Default `false`: each layer's version runs on the same input as the
+ /// global one and the results are blended by weight, which is right for an
+ /// adjustment. An operation that answers `true` has every uniform linear
+ /// in what it controls, so the composer can blend the uniforms instead —
+ /// the weighted average of every overlapping layer's settings, the global
+ /// setting taking whatever weight the layers leave — and run the fragment
+ /// once. See `local_settings_block`.
+ fn blends_settings(&self) -> bool {
+ false
+ }
+
/// TRACES: FR-DEV-3e | FR-DEV-3f
/// Whether this operation *is* the rendering, rather than an adjustment to
/// one.
@@ -884,10 +910,11 @@ fn compose_inner(
let prefix = sanitise(id);
let mut fragment = String::new();
+ let mut op_uniforms = Vec::new();
if op.is_active() {
// Each op's uniforms are prefixed, so two operations may both
// declare a field called `amount` without colliding.
- let op_uniforms = op.uniforms();
+ op_uniforms = op.uniforms();
if !op_uniforms.is_empty() {
let _ = writeln!(uniform_fields, " // {id}");
}
@@ -901,16 +928,25 @@ fn compose_inner(
helpers.push(*h);
}
}
-
- // Rewrite bare uniform names to their prefixed struct fields, so a
- // fragment is written without knowing about any other operation.
fragment = op.wgsl_body();
- for u in &op_uniforms {
- fragment = rewrite_uniform(&fragment, u.name, &format!("u.{prefix}_{}", u.name));
- }
}
let _ = writeln!(body, "\n // ---- {id} ----");
+ if op.blends_settings() && !local.is_empty() {
+ // TRACES: FR-DEV-3f
+ body.push_str(&local_settings_block(
+ &fragment,
+ &prefix,
+ &op_uniforms,
+ &local,
+ ));
+ continue;
+ }
+ // Rewrite bare uniform names to their prefixed struct fields, so a
+ // fragment is written without knowing about any other operation.
+ for u in &op_uniforms {
+ fragment = rewrite_uniform(&fragment, u.name, &format!("u.{prefix}_{}", u.name));
+ }
body.push_str(&local_block(&fragment, &local));
}
@@ -1283,6 +1319,65 @@ fn main(@builtin(global_invocation_id) gid: vec3) {{
}
}
+/// TRACES: FR-DEV-3f
+/// One operation's block when its layers are blended as *settings*: every
+/// uniform the weighted average of the global value and each overlapping
+/// layer's, and then the fragment once.
+///
+/// The weights: each layer's mask weight `w_i`, and the global setting
+/// whatever the layers leave, `w_g = max(0, 1 − Σ w_i)`. So
+///
+/// ```text
+/// p = (w_g·p_g + Σ w_i·p_i) / (w_g + Σ w_i)
+/// ```
+///
+/// — one layer at weight `w` is `(1 − w)·p_g + w·p_1`, exactly the blend a
+/// layer has always had, and three layers overlapping at full weight are the
+/// plain mean of their three settings rather than one piled on another. A
+/// layer's `p_i` is its combined setting (the global one plus its offset), so
+/// the average is of what each layer *asks for*.
+///
+/// Only layers that move this operation take part. One that left it alone is
+/// not voting for the global setting; it is not voting.
+///
+/// `fragment` is the operation's body with bare uniform names, as
+/// `wgsl_body` wrote it: they are rewritten here to the blended values.
+fn local_settings_block(
+ fragment: &str,
+ prefix: &str,
+ uniforms: &[Uniform],
+ local: &[&crate::mask::LocalOp],
+) -> String {
+ let mut out = String::new();
+ let _ = writeln!(out, " {{");
+ let weights: Vec = local.iter().map(|l| format!("mask_w{}", l.slot)).collect();
+ let _ = writeln!(out, " let set_w = {};", weights.join(" + "));
+ let _ = writeln!(out, " let set_g = max(1.0 - set_w, 0.0);");
+ // Never zero: the global weight is one wherever no layer reaches.
+ let _ = writeln!(out, " let set_n = max(set_g + set_w, 1e-6);");
+ let mut body = fragment.to_string();
+ for u in uniforms {
+ let mut sum = format!("set_g * u.{prefix}_{}", u.name);
+ for l in local {
+ let _ = write!(
+ sum,
+ " + mask_w{slot} * u.mask{slot}_{prefix}_{name}",
+ slot = l.slot,
+ name = u.name
+ );
+ }
+ let _ = writeln!(out, " let set_{} = ({sum}) / set_n;", u.name);
+ body = rewrite_uniform(&body, u.name, &format!("set_{}", u.name));
+ }
+ let _ = writeln!(out, " {{");
+ for line in body.lines() {
+ let _ = writeln!(out, " {line}");
+ }
+ let _ = writeln!(out, " }}");
+ let _ = writeln!(out, " }}");
+ out
+}
+
/// One operation's block: its global fragment, and each layer's version of it
/// blended in by that layer's weight.
///
@@ -2158,7 +2253,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; 32 * 32 * 32],
density_max: 3.0,
lut_size: 32,
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; crate::ops::film_sim::FORMAT_COUNT],
+ push_stations: vec![0.0],
+ paper: None,
grain_density_max: [3.0; 3],
grain_uniformity: 0.97,
}));
diff --git a/core/dr-pipeline/src/ops/film_sim.rs b/core/dr-pipeline/src/ops/film_sim.rs
index 32fb251..5166d3a 100644
--- a/core/dr-pipeline/src/ops/film_sim.rs
+++ b/core/dr-pipeline/src/ops/film_sim.rs
@@ -22,9 +22,11 @@
//!
//! For the same reason [`crate::ops::vignetting`]'s coefficients are not: they
//! are measurements of a physical thing, not something a slider moves. The
-//! sliders here are exposure and print exposure, which are what a photographer
-//! and a printer actually control. `dr-film` turns a stock plus those two
-//! numbers into [`FilmTables`]; this node knows only the layout.
+//! sliders here are exposure, push, print exposure and format, which are what
+//! a photographer and a printer actually control. `dr-film` turns a stock into
+//! [`FilmTables`] that hold none of them; the shader applies all four per
+//! pixel, which is what lets a mask layer hold its own (see
+//! [`Operation::blends_settings`]). This node knows only the layout.
//!
//! Declared as a plain struct here rather than imported, so that dr-pipeline
//! keeps its no-dependency property (ARCH §6.5a) exactly as `vignetting` does
@@ -63,6 +65,15 @@ static FORMATS: [LocalizedKey; 6] = [
/// take; [`FilmTables::is_well_formed`] is what stops the two drifting.
pub const CURVE_SAMPLES: usize = 256;
+/// The most development times a stock may measure — a curve row and a push
+/// station each. Must agree with `dr_film::bake::MAX_CURVE_ROWS`, for the
+/// reason [`CURVE_SAMPLES`] must; the uniform block holds this many stations.
+pub const MAX_CURVE_ROWS: usize = 8;
+
+/// How many frames [`FORMATS`] offers, and so how many grain counts a stock
+/// carries.
+pub const FORMAT_COUNT: usize = 6;
+
/// The uniform field names the fragment reads the exposure matrix from.
///
/// A table rather than a formatted string, because a `Uniform`'s name is
@@ -74,6 +85,24 @@ static MATRIX_FIELDS: [[&str; 3]; 3] = [
["m20", "m21", "m22"],
];
+/// Grains per pixel, per format and layer: `gn{format}{layer}`.
+static GRAIN_FIELDS: [[&str; 3]; FORMAT_COUNT] = [
+ ["gn00", "gn01", "gn02"],
+ ["gn10", "gn11", "gn12"],
+ ["gn20", "gn21", "gn22"],
+ ["gn30", "gn31", "gn32"],
+ ["gn40", "gn41", "gn42"],
+ ["gn50", "gn51", "gn52"],
+];
+
+/// The push each curve row was developed to, padded with the last.
+static PUSH_FIELDS: [&str; MAX_CURVE_ROWS] =
+ ["ps0", "ps1", "ps2", "ps3", "ps4", "ps5", "ps6", "ps7"];
+
+/// Which format this is, one-hot. See [`FilmSim::uniforms`] for why a choice
+/// reaches the shader as six weights rather than an index.
+static FORMAT_FIELDS: [&str; FORMAT_COUNT] = ["fmt0", "fmt1", "fmt2", "fmt3", "fmt4", "fmt5"];
+
static DESCRIPTOR: LazyLock> = LazyLock::new(|| {
Arc::new(OpDescriptor {
// Tone and colour both, and not `Effect`: a stock is not something applied
@@ -115,48 +144,86 @@ static DESCRIPTOR: LazyLock> = LazyLock::new(|| {
/// Layout is the contract between the two crates, so it is written down here
/// and checked rather than assumed:
///
-/// - `exposure_matrix[l][c]` — layer `l`'s response to linear sRGB channel `c`.
-/// - `curves` — `CURVE_SAMPLES` density triples, uniform over
-/// `[curve_log_min, curve_log_max]`.
-/// - `lut` — `lut_size³` linear sRGB triples, uniform over `[0, density_max]`
-/// on each axis, with the **red axis varying fastest**: index
-/// `(b * size + g) * size + r`. That is the order a 3D texture upload
-/// expects, so the consumer hands the slice straight to the driver. Filling
-/// it the other way round transposes red and blue in the finished picture —
-/// which is a plausible photograph of the wrong colour, and which the unit
-/// tests on both sides of this seam happily pass, because each side is
-/// internally consistent. `dr-film` pins it; `dr-gpu`'s `film_sim` test
-/// catches it end to end.
+/// - `exposure_matrix[l][c]` — layer `l`'s response to linear sRGB channel
+/// `c`, at unit gain: camera exposure is a per-pixel setting.
+/// - `curves` — one row of `CURVE_SAMPLES` density triples per
+/// `push_stations` entry, uniform over `[curve_log_min, curve_log_max]`,
+/// and then, when printed, one more row: the paper's, uniform over
+/// `[paper.log_min, paper.log_max]`.
+/// - `lut` — `lut_size³` triples uniform over `[0, density_max]` on each
+/// axis, with the **red axis varying fastest**: index
+/// `(b * size + g) * size + r`. Linear sRGB when the film is viewed
+/// directly; the paper's log₁₀ exposure through the negative when it is
+/// printed, followed by a second cube, paper density over
+/// `[0, paper.density_max]` to linear sRGB. That is the order a 3D texture
+/// upload expects with the cubes stacked in depth, so the consumer hands the
+/// slice straight to the driver. Filling it the other way round transposes
+/// red and blue in the finished picture — which is a plausible photograph
+/// of the wrong colour, and which the unit tests on both sides of this seam
+/// happily pass, because each side is internally consistent. `dr-film` pins
+/// it; `dr-gpu`'s `film_sim` test catches it end to end.
+///
+/// Everything the sliders move — exposure, push, print exposure, format — is
+/// absent. They are per-pixel settings the shader applies against these
+/// tables, which is what lets a mask layer hold its own.
#[derive(Debug, Clone, PartialEq)]
pub struct FilmTables {
pub exposure_matrix: [[f32; 3]; 3],
pub curves: Vec<[f32; 3]>,
+ /// The push each film row was developed to, ascending: one entry for a
+ /// stock measured at a single process.
+ pub push_stations: Vec,
pub curve_log_min: f32,
pub curve_log_max: f32,
pub lut: Vec<[f32; 3]>,
pub density_max: f32,
pub lut_size: usize,
+ /// The print, for a negative printed on paper.
+ pub paper: Option,
/// TRACES: FR-DEV-3f
- /// Grains in one pixel's patch of film, per layer, with the density
- /// ceiling and uniformity the variance is taken against. Zero particles
- /// means no grain, which is how the control is turned off.
- pub grain_particles: [f32; 3],
+ /// Grains in one pixel's patch of film, per format and then per layer,
+ /// with the density ceiling and uniformity the variance is taken against.
+ /// Zero particles means no grain, which is how the control is turned off.
+ pub grain_particles: [[f32; 3]; FORMAT_COUNT],
pub grain_density_max: [f32; 3],
pub grain_uniformity: f32,
}
+/// The print half of [`FilmTables`]: where the paper's row and cube are read.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct PaperTables {
+ /// The enlarger's filtration, per layer, in log₁₀ exposure.
+ pub balance: [f32; 3],
+ pub log_min: f32,
+ pub log_max: f32,
+ pub density_max: f32,
+}
+
impl FilmTables {
+ /// Film rows, not counting the paper's.
+ pub fn curve_rows(&self) -> usize {
+ self.push_stations.len()
+ }
+
/// Whether these tables are the shape the shader will index them at.
///
/// Checked on the way in, because the failure otherwise is a shader
/// sampling past the end of a texture: undefined, silent, and different on
/// every driver.
pub fn is_well_formed(&self) -> bool {
- self.curves.len() == CURVE_SAMPLES
+ let rows = self.curve_rows();
+ let printed = usize::from(self.paper.is_some());
+ let paper_ok = self
+ .paper
+ .is_none_or(|p| p.density_max > 0.0 && p.log_max > p.log_min);
+ (1..=MAX_CURVE_ROWS).contains(&rows)
+ && self.push_stations.windows(2).all(|w| w[0] < w[1])
+ && self.curves.len() == CURVE_SAMPLES * (rows + printed)
&& self.lut_size >= 2
- && self.lut.len() == self.lut_size.pow(3)
+ && self.lut.len() == self.lut_size.pow(3) * (1 + printed)
&& self.density_max > 0.0
&& self.curve_log_max > self.curve_log_min
+ && paper_ok
}
}
@@ -246,60 +313,80 @@ impl Operation for FilmSim {
self.set_tables(tables.cloned());
}
+ fn film_tables(&self) -> Option<&FilmTables> {
+ self.tables.as_ref()
+ }
+
+ /// TRACES: FR-DEV-3f
+ /// A layer's film is its settings, not its own picture blended over the
+ /// global one.
+ ///
+ /// Blending outputs would be a photograph developed twice and cross-faded;
+ /// a region on a pushed film is not that. Every uniform below is linear
+ /// in what it controls, so the composer can take each layer's weighted
+ /// average of them and develop the pixel once.
+ fn blends_settings(&self) -> bool {
+ true
+ }
+
+ /// Every value here is linear in what the shader does with it, which is
+ /// what [`Self::blends_settings`] rests on. The format is the one that
+ /// needs arranging: an index averaged between layers is a format nobody
+ /// chose, so it goes out one-hot and the shader mixes the six grain
+ /// counts by it — two layers on 35 mm and 6x7 meet at the average grain.
fn uniforms(&self) -> Vec {
let Some(t) = &self.tables else {
return Vec::new();
};
- let m = t.exposure_matrix;
- // Exposure rides in the matrix on the CPU when the stock is baked, so
- // what is left here is the *shader's* copy of the same nine numbers.
- // Spelled out one at a time because a uniform is a named `f32` in this
- // pipeline and a matrix would be a second kind of thing for one caller.
- let mut out = Vec::with_capacity(MATRIX_FIELDS.len() + 5);
- for (l, row) in m.iter().enumerate() {
+ let mut out = Vec::with_capacity(64);
+ let mut push = |name: &'static str, value: f32| out.push(Uniform { name, value });
+ for (l, row) in t.exposure_matrix.iter().enumerate() {
for (c, v) in row.iter().enumerate() {
- out.push(Uniform {
- name: MATRIX_FIELDS[l][c],
- value: *v,
- });
+ push(MATRIX_FIELDS[l][c], *v);
}
}
- for (l, name) in ["gn0", "gn1", "gn2"].into_iter().enumerate() {
- out.push(Uniform {
- name,
- value: t.grain_particles[l],
- });
+ for (f, per_layer) in t.grain_particles.iter().enumerate() {
+ for (l, v) in per_layer.iter().enumerate() {
+ push(GRAIN_FIELDS[f][l], *v);
+ }
}
for (l, name) in ["gd0", "gd1", "gd2"].into_iter().enumerate() {
- out.push(Uniform {
- name,
- value: t.grain_density_max[l],
- });
+ push(name, t.grain_density_max[l]);
}
- out.push(Uniform {
- name: "grain_u",
- value: t.grain_uniformity,
- });
- out.push(Uniform {
- name: "log_min",
- value: t.curve_log_min,
- });
- out.push(Uniform {
- name: "log_max",
- value: t.curve_log_max,
- });
- out.push(Uniform {
- name: "density_max",
- value: t.density_max,
- });
- out.push(Uniform {
- name: "lut_size",
- value: t.lut_size as f32,
- });
- out.push(Uniform {
- name: "print_exposure",
- value: self.print_exposure,
+ push("grain_u", t.grain_uniformity);
+ push("log_min", t.curve_log_min);
+ push("log_max", t.curve_log_max);
+ push("density_max", t.density_max);
+ push("lut_size", t.lut_size as f32);
+
+ let last = *t.push_stations.last().unwrap_or(&0.0);
+ for (i, name) in PUSH_FIELDS.into_iter().enumerate() {
+ push(name, t.push_stations.get(i).copied().unwrap_or(last));
+ }
+ push("rows", t.curve_rows() as f32);
+
+ let paper = t.paper.unwrap_or(PaperTables {
+ balance: [0.0; 3],
+ log_min: 0.0,
+ log_max: 1.0,
+ density_max: 1.0,
});
+ push("printed", if t.paper.is_some() { 1.0 } else { 0.0 });
+ for (l, name) in ["pb0", "pb1", "pb2"].into_iter().enumerate() {
+ push(name, paper.balance[l]);
+ }
+ push("plog_min", paper.log_min);
+ push("plog_max", paper.log_max);
+ push("pdmax", paper.density_max);
+
+ // The sliders.
+ push("ev", self.exposure);
+ push("push", self.push);
+ push("pev", self.print_exposure);
+ let chosen = (self.format.max(0.0).round() as usize).min(FORMAT_COUNT - 1);
+ for (f, name) in FORMAT_FIELDS.into_iter().enumerate() {
+ push(name, if f == chosen { 1.0 } else { 0.0 });
+ }
out
}
@@ -321,8 +408,9 @@ let scene = vec3(
// What each emulsion layer was exposed to. A matrix, exactly: the scene
// spectrum reconstructed from an sRGB triple is linear in that triple, so the
// integral over wavelength collapsed into these nine numbers when the stock
-// was baked.
-let exposure = vec3(
+// was baked. The camera's exposure is a gain on it, applied here rather than
+// baked in so that a layer can hold its own.
+let exposure = exp2(ev) * vec3(
dot(vec3(m00, m01, m02), scene),
dot(vec3(m10, m11, m12), scene),
dot(vec3(m20, m21, m22), scene),
@@ -331,12 +419,16 @@ let exposure = vec3(
// the curve, and the toe is where it belongs.
let log_exposure = log10(max(exposure, vec3(0.0)) + 1e-10);
-// The characteristic curve: what density each layer develops to. Clamped, not
-// extrapolated — past the shoulder a real emulsion stops responding, and
-// extrapolating would turn a blown highlight into a colour cast that grows the
-// more it is overexposed.
-let density = film_curve(clamp((log_exposure - log_min) / (log_max - log_min),
- vec3(0.0), vec3(1.0)));
+// The characteristic curve: what density each layer develops to, at this
+// pixel's push. Clamped, not extrapolated — past the shoulder a real emulsion
+// stops responding, and extrapolating would turn a blown highlight into a
+// colour cast that grows the more it is overexposed.
+let density = film_curve_pushed(
+ clamp((log_exposure - log_min) / (log_max - log_min), vec3(0.0), vec3(1.0)),
+ push,
+ array(ps0, ps1, ps2, ps3, ps4, ps5, ps6, ps7),
+ u32(rows),
+);
// TRACES: FR-DEV-3f
// Grain, on the density and before the dye.
@@ -346,16 +438,40 @@ let density = film_curve(clamp((log_exposure - log_min) / (log_max - log_min),
// through whatever density resulted. Adding noise to the finished colour --
// which is what an effect does -- tints the highlights wrong, because that
// noise never passes through the dye at all.
-let grained = film_grain(density, source_px,
- vec3(gn0, gn1, gn2),
+//
+// The format's grain count, mixed by the one-hot weights: exactly one format's
+// on the whole photograph, and the weighted average under overlapping layers.
+let particles = fmt0 * vec3(gn00, gn01, gn02)
+ + fmt1 * vec3(gn10, gn11, gn12)
+ + fmt2 * vec3(gn20, gn21, gn22)
+ + fmt3 * vec3(gn30, gn31, gn32)
+ + fmt4 * vec3(gn40, gn41, gn42)
+ + fmt5 * vec3(gn50, gn51, gn52);
+let grained = film_grain(density, source_px, particles,
vec3(gd0, gd1, gd2),
grain_u);
-// Dye absorption, the print through the negative, the paper, the viewing
-// illuminant and the chromatic adaptation — all of which take exactly three
-// numbers in, which is why they fit in one lookup.
-c = film_lut(clamp(grained / density_max, vec3(0.0), vec3(1.0)), lut_size);"
- .into()
+// Dye absorption through to what comes next — all of it takes exactly three
+// numbers in, which is why it fits in one lookup. Viewed directly, that is
+// the picture; printed, it is the light the paper receives through the
+// negative, in log exposure.
+let through = film_lut(clamp(grained / density_max, vec3(0.0), vec3(1.0)),
+ lut_size, 0);
+if (printed > 0.5) {
+ // The enlarger: its filtration, and then its exposure, the same stops on
+ // every layer — which is why print exposure is an addition here and not
+ // a table, and so exact at any setting.
+ let paper_log = through + vec3(pb0, pb1, pb2) + pev * 0.30103;
+ let paper_density = film_curve(
+ clamp((paper_log - plog_min) / (plog_max - plog_min), vec3(0.0), vec3(1.0)),
+ u32(rows),
+ );
+ c = film_lut(clamp(paper_density / pdmax, vec3(0.0), vec3(1.0)),
+ lut_size, i32(lut_size));
+} else {
+ c = through;
+}"
+ .into()
}
fn helpers(&self) -> &'static [crate::operation::Helper] {
@@ -363,7 +479,7 @@ c = film_lut(clamp(grained / density_max, vec3(0.0), vec3(1.0)), lut_s
}
}
-static HELPERS: [crate::operation::Helper; 5] = [
+static HELPERS: [crate::operation::Helper; 6] = [
crate::operation::Helper {
name: "film_hash",
source: "\
@@ -453,9 +569,9 @@ fn log10(v: vec3) -> vec3 {
crate::operation::Helper {
name: "film_curve",
source: "\
-// Three characteristic curves, sampled from a 256-wide texture and
-// interpolated by hand. `t` is already normalised to the curve's domain.
-fn film_curve(t: vec3) -> vec3 {
+// Three characteristic curves, one row of a 256-wide texture, interpolated by
+// hand. `t` is already normalised to the curve's domain.
+fn film_curve(t: vec3, row: u32) -> vec3 {
let samples = u32(textureDimensions(film_curves).x);
let last = f32(samples - 1u);
var out = vec3(0.0);
@@ -463,20 +579,45 @@ fn film_curve(t: vec3) -> vec3 {
let x = t[ch] * last;
let i = min(u32(floor(x)), samples - 2u);
let f = x - f32(i);
- let a = textureLoad(film_curves, vec2(i32(i), 0), 0);
- let b = textureLoad(film_curves, vec2(i32(i) + 1, 0), 0);
+ let a = textureLoad(film_curves, vec2(i32(i), i32(row)), 0);
+ let b = textureLoad(film_curves, vec2(i32(i) + 1, i32(row)), 0);
out[ch] = mix(a[ch], b[ch], f);
}
return out;
+}",
+ },
+ crate::operation::Helper {
+ name: "film_curve_pushed",
+ source: "\
+// The curves at a push between two measured processes. Development is
+// interpolated in log time and push *is* log time, so a straight line between
+// the neighbouring rows is the stock's own interpolation, not an estimate of
+// it. Clamped to the first and last process, as the stock is.
+fn film_curve_pushed(t: vec3, push: f32, stations: array, rows: u32) -> vec3 {
+ if (rows < 2u) {
+ return film_curve(t, 0u);
+ }
+ var at = stations;
+ var hi = rows - 1u;
+ for (var i = 1u; i < rows; i = i + 1u) {
+ if (at[i] >= push) {
+ hi = i;
+ break;
+ }
+ }
+ let lo = hi - 1u;
+ let f = clamp((push - at[lo]) / max(at[hi] - at[lo], 1e-6), 0.0, 1.0);
+ return mix(film_curve(t, lo), film_curve(t, hi), f);
}",
},
crate::operation::Helper {
name: "film_lut",
source: "\
-// Trilinear interpolation of the density lookup, by hand for the same reason
-// the curve above is: there is no sampler bound, and the eight loads are
-// cache-neighbours.
-fn film_lut(t: vec3, size: f32) -> vec3 {
+// Trilinear interpolation of one cube of the lookup, by hand for the same
+// reason the curve above is: there is no sampler bound, and the eight loads
+// are cache-neighbours. `z0` is where the cube starts in depth: the film's at
+// zero, the paper's stacked after it.
+fn film_lut(t: vec3, size: f32, z0: i32) -> vec3 {
let n = i32(size);
let x = t * (size - 1.0);
let base = min(vec3(floor(x)), vec3(n - 2));
@@ -489,7 +630,7 @@ fn film_lut(t: vec3, size: f32) -> vec3 {
let wy = select(1.0 - f.y, f.y, dy == 1);
for (var dz = 0; dz < 2; dz = dz + 1) {
let wz = select(1.0 - f.z, f.z, dz == 1);
- let p = base + vec3(dx, dy, dz);
+ let p = base + vec3(dx, dy, dz + z0);
out = out + wx * wy * wz
* textureLoad(film_lut_texture, p, 0).rgb;
}
@@ -513,7 +654,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; 32 * 32 * 32],
density_max: 3.0,
lut_size: 32,
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; FORMAT_COUNT],
+ push_stations: vec![0.0],
+ paper: None,
grain_density_max: [3.0; 3],
grain_uniformity: 0.97,
}
diff --git a/core/dr-pipeline/src/ops/mod.rs b/core/dr-pipeline/src/ops/mod.rs
index 0e5f63a..edd0a04 100644
--- a/core/dr-pipeline/src/ops/mod.rs
+++ b/core/dr-pipeline/src/ops/mod.rs
@@ -82,7 +82,7 @@ pub use colour_mixer::ColourMixer;
pub use curve::ToneCurve;
pub use dehaze::Dehaze;
pub use distortion::Distortion;
-pub use film_sim::{FilmSim, FilmTables};
+pub use film_sim::{FilmSim, FilmTables, PaperTables};
// Clarity and texture are one implementation at two scales; see the module's
// documentation for why that is two nodes and not one.
pub use local_contrast::{Clarity, Texture};
diff --git a/core/dr-pipeline/src/preset.rs b/core/dr-pipeline/src/preset.rs
index 2399b54..883c809 100644
--- a/core/dr-pipeline/src/preset.rs
+++ b/core/dr-pipeline/src/preset.rs
@@ -1280,7 +1280,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; 8],
density_max: 2.0,
lut_size: 2,
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; crate::ops::film_sim::FORMAT_COUNT],
+ push_stations: vec![0.0],
+ paper: None,
grain_density_max: [2.0; 3],
grain_uniformity: 1.0,
},
diff --git a/core/dr-pipeline/src/sidecar.rs b/core/dr-pipeline/src/sidecar.rs
index 0f4a340..87d9150 100644
--- a/core/dr-pipeline/src/sidecar.rs
+++ b/core/dr-pipeline/src/sidecar.rs
@@ -2023,7 +2023,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; 8],
density_max: 2.0,
lut_size: 2,
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; crate::ops::film_sim::FORMAT_COUNT],
+ push_stations: vec![0.0],
+ paper: None,
grain_density_max: [2.0; 3],
grain_uniformity: 1.0,
},
diff --git a/core/dr-pipeline/src/state.rs b/core/dr-pipeline/src/state.rs
index 928c690..10c532e 100644
--- a/core/dr-pipeline/src/state.rs
+++ b/core/dr-pipeline/src/state.rs
@@ -235,7 +235,9 @@ mod tests {
lut: vec![[0.5, 0.5, 0.5]; 8],
density_max: 2.0,
lut_size: 2,
- grain_particles: [0.0; 3],
+ grain_particles: [[0.0; 3]; crate::ops::film_sim::FORMAT_COUNT],
+ push_stations: vec![0.0],
+ paper: None,
grain_density_max: [2.0; 3],
grain_uniformity: 1.0,
},
diff --git a/docs/dev/requirements.md b/docs/dev/requirements.md
index a8b4ad1..bc1ae59 100644
--- a/docs/dev/requirements.md
+++ b/docs/dev/requirements.md
@@ -434,6 +434,15 @@ reference implementation.
parameter — `core/dr-pipeline/src/sidecar.rs` records why an index was rejected (installing a
profile would silently change which film every existing photograph was developed on).
+*Resolved 2026-09-26:* the film's settings — exposure, push, print exposure, format — are
+**per-pixel**, evaluated by the shader against tables that hold none of them, so a mask layer can
+hold its own. A layer's settings are offsets to the photograph's, and where layers overlap a pixel
+takes the weighted average of what each asks for, the photograph's setting taking whatever weight
+the layers leave (`operation::local_settings_block`). The stock and its paper stay
+photograph-wide: a layer has no picker. The print is split at the paper's log exposure, so print
+exposure is an addition between two lookups and exact at any setting; push interpolates the
+stock's measured processes. Before this a layer offered the film's sliders and they moved nothing.
+
**FR-DEV-3g — AI denoise.** Learned denoising operating in the raw domain, ideally jointly with
demosaic.
diff --git a/docs/dev/traceability.md b/docs/dev/traceability.md
index 2489c2f..aa774c4 100644
--- a/docs/dev/traceability.md
+++ b/docs/dev/traceability.md
@@ -10,7 +10,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n
| Metric | Value |
|---|---|
| Source files scanned | 465 |
-| TRACES tags found | 1872 |
+| TRACES tags found | 1893 |
| Requirements defined | 192 |
| Requirements deferred (post-v1) | 24 |
| Requirements covered | 161 |
@@ -45,10 +45,10 @@ _None._
| FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../../core/dr-catalog/src/schema.rs#L1) |
| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:65`](../../core/dr-catalog/src/walk.rs#L65), [`core/dr-sync/src/scan.rs:105`](../../core/dr-sync/src/scan.rs#L105), [`core/dr-thumbs/src/codec.rs:1`](../../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:474`](../../ui/dr-ui/src/import.rs#L474), [`ui/dr-ui/src/import.rs:504`](../../ui/dr-ui/src/import.rs#L504), [`ui/dr-ui/src/library/sweep.rs:562`](../../ui/dr-ui/src/library/sweep.rs#L562), [`ui/dr-ui/src/library_ui/controller.rs:187`](../../ui/dr-ui/src/library_ui/controller.rs#L187), [`ui/dr-ui/src/library_ui/grid.rs:544`](../../ui/dr-ui/src/library_ui/grid.rs#L544), [`ui/dr-ui/src/library_ui/sync.rs:392`](../../ui/dr-ui/src/library_ui/sync.rs#L392), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/repairs.rs:695`](../../ui/dr-ui/src/repairs.rs#L695), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:781`](../../ui/dr-ui/ui/library.slint#L781), [`ui/dr-ui/ui/settings.slint:375`](../../ui/dr-ui/ui/settings.slint#L375), [`ui/dr-ui/ui/settings.slint:74`](../../ui/dr-ui/ui/settings.slint#L74) |
| FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/schema.rs:594`](../../core/dr-catalog/src/schema.rs#L594), [`ui/dr-ui/src/library/filters.rs:39`](../../ui/dr-ui/src/library/filters.rs#L39), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.rs#L1), [`ui/dr-ui/src/library_ui/mod.rs:1`](../../ui/dr-ui/src/library_ui/mod.rs#L1), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488) |
-| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/rating.rs:396`](../../core/dr-catalog/src/rating.rs#L396), [`core/dr-catalog/src/rating.rs:411`](../../core/dr-catalog/src/rating.rs#L411), [`core/dr-catalog/src/rating.rs:422`](../../core/dr-catalog/src/rating.rs#L422), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:52`](../../core/dr-catalog/src/rating.rs#L52), [`core/dr-catalog/src/rating.rs:827`](../../core/dr-catalog/src/rating.rs#L827), [`core/dr-catalog/src/rating.rs:851`](../../core/dr-catalog/src/rating.rs#L851), [`core/dr-catalog/src/rating.rs:870`](../../core/dr-catalog/src/rating.rs#L870), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-catalog/src/schema.rs:1742`](../../core/dr-catalog/src/schema.rs#L1742), [`core/dr-decode/src/lib.rs:307`](../../core/dr-decode/src/lib.rs#L307), [`core/dr-decode/src/lib.rs:313`](../../core/dr-decode/src/lib.rs#L313), [`core/dr-decode/src/lib.rs:323`](../../core/dr-decode/src/lib.rs#L323), [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-export/src/dng.rs:308`](../../core/dr-export/src/dng.rs#L308), [`core/dr-pipeline/src/sidecar.rs:1093`](../../core/dr-pipeline/src/sidecar.rs#L1093), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-pipeline/src/sidecar.rs:193`](../../core/dr-pipeline/src/sidecar.rs#L193), [`core/dr-pipeline/src/sidecar.rs:2874`](../../core/dr-pipeline/src/sidecar.rs#L2874), [`core/dr-pipeline/src/sidecar.rs:2892`](../../core/dr-pipeline/src/sidecar.rs#L2892), [`core/dr-pipeline/src/sidecar.rs:590`](../../core/dr-pipeline/src/sidecar.rs#L590), [`ui/dr-ui/src/collections_ui/controller.rs:16`](../../ui/dr-ui/src/collections_ui/controller.rs#L16), [`ui/dr-ui/src/collections_ui/controller.rs:209`](../../ui/dr-ui/src/collections_ui/controller.rs#L209), [`ui/dr-ui/src/collections_ui/controller.rs:418`](../../ui/dr-ui/src/collections_ui/controller.rs#L418), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:150`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L150), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:323`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L323), [`ui/dr-ui/src/decoder_seam.rs:184`](../../ui/dr-ui/src/decoder_seam.rs#L184), [`ui/dr-ui/src/library/cells.rs:198`](../../ui/dr-ui/src/library/cells.rs#L198), [`ui/dr-ui/src/library/scan.rs:526`](../../ui/dr-ui/src/library/scan.rs#L526), [`ui/dr-ui/src/library/scan.rs:916`](../../ui/dr-ui/src/library/scan.rs#L916), [`ui/dr-ui/src/library_ui/controller.rs:836`](../../ui/dr-ui/src/library_ui/controller.rs#L836), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1185`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1185), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:118`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L118), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1391`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1391), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1402`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1402), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1415`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1415), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1430`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1430), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1439`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1439), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:97`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L97), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:177`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L177), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:215`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L215), [`ui/dr-ui/ui/app.slint:1665`](../../ui/dr-ui/ui/app.slint#L1665), [`ui/dr-ui/ui/app.slint:2809`](../../ui/dr-ui/ui/app.slint#L2809), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:48`](../../ui/dr-ui/ui/develop.slint#L48), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1614`](../../ui/dr-ui/ui/library.slint#L1614), [`ui/dr-ui/ui/library.slint:1617`](../../ui/dr-ui/ui/library.slint#L1617), [`ui/dr-ui/ui/library.slint:1768`](../../ui/dr-ui/ui/library.slint#L1768), [`ui/dr-ui/ui/library.slint:1792`](../../ui/dr-ui/ui/library.slint#L1792), [`ui/dr-ui/ui/library.slint:1849`](../../ui/dr-ui/ui/library.slint#L1849), [`ui/dr-ui/ui/library.slint:21`](../../ui/dr-ui/ui/library.slint#L21), [`ui/dr-ui/ui/library.slint:3202`](../../ui/dr-ui/ui/library.slint#L3202), [`ui/dr-ui/ui/library.slint:3209`](../../ui/dr-ui/ui/library.slint#L3209), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:3318`](../../ui/dr-ui/ui/library.slint#L3318), [`ui/dr-ui/ui/library.slint:3934`](../../ui/dr-ui/ui/library.slint#L3934), [`ui/dr-ui/ui/library.slint:467`](../../ui/dr-ui/ui/library.slint#L467), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5060`](../../ui/dr-ui/ui/library.slint#L5060), [`ui/dr-ui/ui/library.slint:5076`](../../ui/dr-ui/ui/library.slint#L5076), [`ui/dr-ui/ui/library.slint:515`](../../ui/dr-ui/ui/library.slint#L515), [`ui/dr-ui/ui/library.slint:621`](../../ui/dr-ui/ui/library.slint#L621), [`ui/dr-ui/ui/library.slint:665`](../../ui/dr-ui/ui/library.slint#L665), [`ui/dr-ui/ui/library.slint:671`](../../ui/dr-ui/ui/library.slint#L671), [`ui/dr-ui/ui/library.slint:675`](../../ui/dr-ui/ui/library.slint#L675) |
+| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/rating.rs:396`](../../core/dr-catalog/src/rating.rs#L396), [`core/dr-catalog/src/rating.rs:411`](../../core/dr-catalog/src/rating.rs#L411), [`core/dr-catalog/src/rating.rs:422`](../../core/dr-catalog/src/rating.rs#L422), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:52`](../../core/dr-catalog/src/rating.rs#L52), [`core/dr-catalog/src/rating.rs:827`](../../core/dr-catalog/src/rating.rs#L827), [`core/dr-catalog/src/rating.rs:851`](../../core/dr-catalog/src/rating.rs#L851), [`core/dr-catalog/src/rating.rs:870`](../../core/dr-catalog/src/rating.rs#L870), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-catalog/src/schema.rs:1742`](../../core/dr-catalog/src/schema.rs#L1742), [`core/dr-decode/src/lib.rs:307`](../../core/dr-decode/src/lib.rs#L307), [`core/dr-decode/src/lib.rs:313`](../../core/dr-decode/src/lib.rs#L313), [`core/dr-decode/src/lib.rs:323`](../../core/dr-decode/src/lib.rs#L323), [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-export/src/dng.rs:308`](../../core/dr-export/src/dng.rs#L308), [`core/dr-pipeline/src/sidecar.rs:1093`](../../core/dr-pipeline/src/sidecar.rs#L1093), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-pipeline/src/sidecar.rs:193`](../../core/dr-pipeline/src/sidecar.rs#L193), [`core/dr-pipeline/src/sidecar.rs:2876`](../../core/dr-pipeline/src/sidecar.rs#L2876), [`core/dr-pipeline/src/sidecar.rs:2894`](../../core/dr-pipeline/src/sidecar.rs#L2894), [`core/dr-pipeline/src/sidecar.rs:590`](../../core/dr-pipeline/src/sidecar.rs#L590), [`ui/dr-ui/src/collections_ui/controller.rs:16`](../../ui/dr-ui/src/collections_ui/controller.rs#L16), [`ui/dr-ui/src/collections_ui/controller.rs:209`](../../ui/dr-ui/src/collections_ui/controller.rs#L209), [`ui/dr-ui/src/collections_ui/controller.rs:418`](../../ui/dr-ui/src/collections_ui/controller.rs#L418), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:150`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L150), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:323`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L323), [`ui/dr-ui/src/decoder_seam.rs:184`](../../ui/dr-ui/src/decoder_seam.rs#L184), [`ui/dr-ui/src/library/cells.rs:198`](../../ui/dr-ui/src/library/cells.rs#L198), [`ui/dr-ui/src/library/scan.rs:526`](../../ui/dr-ui/src/library/scan.rs#L526), [`ui/dr-ui/src/library/scan.rs:916`](../../ui/dr-ui/src/library/scan.rs#L916), [`ui/dr-ui/src/library_ui/controller.rs:836`](../../ui/dr-ui/src/library_ui/controller.rs#L836), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1185`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1185), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:118`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L118), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1391`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1391), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1402`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1402), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1415`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1415), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1430`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1430), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1439`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1439), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:97`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L97), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:177`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L177), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:215`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L215), [`ui/dr-ui/ui/app.slint:1665`](../../ui/dr-ui/ui/app.slint#L1665), [`ui/dr-ui/ui/app.slint:2809`](../../ui/dr-ui/ui/app.slint#L2809), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:48`](../../ui/dr-ui/ui/develop.slint#L48), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1614`](../../ui/dr-ui/ui/library.slint#L1614), [`ui/dr-ui/ui/library.slint:1617`](../../ui/dr-ui/ui/library.slint#L1617), [`ui/dr-ui/ui/library.slint:1768`](../../ui/dr-ui/ui/library.slint#L1768), [`ui/dr-ui/ui/library.slint:1792`](../../ui/dr-ui/ui/library.slint#L1792), [`ui/dr-ui/ui/library.slint:1849`](../../ui/dr-ui/ui/library.slint#L1849), [`ui/dr-ui/ui/library.slint:21`](../../ui/dr-ui/ui/library.slint#L21), [`ui/dr-ui/ui/library.slint:3202`](../../ui/dr-ui/ui/library.slint#L3202), [`ui/dr-ui/ui/library.slint:3209`](../../ui/dr-ui/ui/library.slint#L3209), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:3318`](../../ui/dr-ui/ui/library.slint#L3318), [`ui/dr-ui/ui/library.slint:3934`](../../ui/dr-ui/ui/library.slint#L3934), [`ui/dr-ui/ui/library.slint:467`](../../ui/dr-ui/ui/library.slint#L467), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5060`](../../ui/dr-ui/ui/library.slint#L5060), [`ui/dr-ui/ui/library.slint:5076`](../../ui/dr-ui/ui/library.slint#L5076), [`ui/dr-ui/ui/library.slint:515`](../../ui/dr-ui/ui/library.slint#L515), [`ui/dr-ui/ui/library.slint:621`](../../ui/dr-ui/ui/library.slint#L621), [`ui/dr-ui/ui/library.slint:665`](../../ui/dr-ui/ui/library.slint#L665), [`ui/dr-ui/ui/library.slint:671`](../../ui/dr-ui/ui/library.slint#L671), [`ui/dr-ui/ui/library.slint:675`](../../ui/dr-ui/ui/library.slint#L675) |
| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:880`](../../core/dr-catalog/src/rating.rs#L880), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-types/src/place.rs:138`](../../core/dr-types/src/place.rs#L138), [`core/dr-types/src/place.rs:1`](../../core/dr-types/src/place.rs#L1), [`core/dr-types/src/selector.rs:1`](../../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library/filters.rs:156`](../../ui/dr-ui/src/library/filters.rs#L156), [`ui/dr-ui/src/library/filters.rs:357`](../../ui/dr-ui/src/library/filters.rs#L357), [`ui/dr-ui/src/library/filters.rs:442`](../../ui/dr-ui/src/library/filters.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/spans.rs:262`](../../ui/dr-ui/src/library/spans.rs#L262), [`ui/dr-ui/src/library_ui/controller.rs:412`](../../ui/dr-ui/src/library_ui/controller.rs#L412), [`ui/dr-ui/src/library_ui/controller.rs:524`](../../ui/dr-ui/src/library_ui/controller.rs#L524), [`ui/dr-ui/src/library_ui/filter_bar.rs:256`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L256), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/filter_bar.rs:406`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L406), [`ui/dr-ui/src/library_ui/filter_bar.rs:840`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L840), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:85`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L85), [`ui/dr-ui/ui/app.slint:655`](../../ui/dr-ui/ui/app.slint#L655), [`ui/dr-ui/ui/library.slint:113`](../../ui/dr-ui/ui/library.slint#L113), [`ui/dr-ui/ui/library.slint:1936`](../../ui/dr-ui/ui/library.slint#L1936), [`ui/dr-ui/ui/library.slint:1973`](../../ui/dr-ui/ui/library.slint#L1973), [`ui/dr-ui/ui/library.slint:2703`](../../ui/dr-ui/ui/library.slint#L2703), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5060`](../../ui/dr-ui/ui/library.slint#L5060), [`ui/dr-ui/ui/library.slint:621`](../../ui/dr-ui/ui/library.slint#L621), [`ui/dr-ui/ui/library.slint:679`](../../ui/dr-ui/ui/library.slint#L679), [`ui/dr-ui/ui/library.slint:701`](../../ui/dr-ui/ui/library.slint#L701), [`ui/dr-ui/ui/settings.slint:202`](../../ui/dr-ui/ui/settings.slint#L202), [`ui/dr-ui/ui/widgets.slint:271`](../../ui/dr-ui/ui/widgets.slint#L271) |
| FR-CAT-7 | [`core/dr-catalog/src/albums.rs:1`](../../core/dr-catalog/src/albums.rs#L1), [`core/dr-catalog/src/collections.rs:1`](../../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/dedup_people.rs:1`](../../core/dr-catalog/src/dedup_people.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:263`](../../ui/dr-ui/src/collections_ui/controller.rs#L263), [`ui/dr-ui/src/collections_ui/controller.rs:273`](../../ui/dr-ui/src/collections_ui/controller.rs#L273), [`ui/dr-ui/src/collections_ui/controller.rs:296`](../../ui/dr-ui/src/collections_ui/controller.rs#L296), [`ui/dr-ui/src/collections_ui/controller.rs:317`](../../ui/dr-ui/src/collections_ui/controller.rs#L317), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/drag.rs:274`](../../ui/dr-ui/src/collections_ui/drag.rs#L274), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/rename_menu.rs:196`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L196), [`ui/dr-ui/src/collections_ui/rename_menu.rs:21`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L21), [`ui/dr-ui/src/collections_ui/tree_sync.rs:251`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L251), [`ui/dr-ui/src/collections_ui/tree_sync.rs:322`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L322), [`ui/dr-ui/src/collections_ui/tree_sync.rs:344`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:171`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L171), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:522`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L522), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:586`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L586), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:394`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L394), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:517`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L517), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:581`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L581), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library/spans.rs:131`](../../ui/dr-ui/src/library/spans.rs#L131), [`ui/dr-ui/src/library/spans.rs:50`](../../ui/dr-ui/src/library/spans.rs#L50), [`ui/dr-ui/src/library/spans.rs:76`](../../ui/dr-ui/src/library/spans.rs#L76), [`ui/dr-ui/src/library_ui/sync.rs:235`](../../ui/dr-ui/src/library_ui/sync.rs#L235), [`ui/dr-ui/src/library_ui/timeline.rs:513`](../../ui/dr-ui/src/library_ui/timeline.rs#L513), [`ui/dr-ui/src/library_ui/window.rs:359`](../../ui/dr-ui/src/library_ui/window.rs#L359), [`ui/dr-ui/src/library_ui/window.rs:395`](../../ui/dr-ui/src/library_ui/window.rs#L395), [`ui/dr-ui/ui/app.slint:3508`](../../ui/dr-ui/ui/app.slint#L3508), [`ui/dr-ui/ui/collections.slint:1283`](../../ui/dr-ui/ui/collections.slint#L1283), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:616`](../../ui/dr-ui/ui/collections.slint#L616), [`ui/dr-ui/ui/library.slint:1367`](../../ui/dr-ui/ui/library.slint#L1367), [`ui/dr-ui/ui/library.slint:1734`](../../ui/dr-ui/ui/library.slint#L1734), [`ui/dr-ui/ui/library.slint:3685`](../../ui/dr-ui/ui/library.slint#L3685), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5038`](../../ui/dr-ui/ui/library.slint#L5038), [`ui/dr-ui/ui/library.slint:5180`](../../ui/dr-ui/ui/library.slint#L5180), [`ui/dr-ui/ui/library.slint:518`](../../ui/dr-ui/ui/library.slint#L518), [`ui/dr-ui/ui/library.slint:567`](../../ui/dr-ui/ui/library.slint#L567), [`ui/dr-ui/ui/library.slint:577`](../../ui/dr-ui/ui/library.slint#L577) |
-| FR-CAT-8 | [`core/dr-catalog/src/schema.rs:770`](../../core/dr-catalog/src/schema.rs#L770), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`core/dr-pipeline/src/ops/curve.rs:137`](../../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:2273`](../../core/dr-pipeline/src/sidecar.rs#L2273), [`core/dr-pipeline/src/sidecar.rs:99`](../../core/dr-pipeline/src/sidecar.rs#L99), [`core/dr-pipeline/src/state.rs:1`](../../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../../core/dr-pipeline/tests/tone_curve.rs#L34), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:600`](../../ui/dr-ui/src/develop/session.rs#L600), [`ui/dr-ui/src/export.rs:922`](../../ui/dr-ui/src/export.rs#L922), [`ui/dr-ui/src/lib.rs:1197`](../../ui/dr-ui/src/lib.rs#L1197), [`ui/dr-ui/src/lib.rs:1257`](../../ui/dr-ui/src/lib.rs#L1257), [`ui/dr-ui/src/lib.rs:2655`](../../ui/dr-ui/src/lib.rs#L2655), [`ui/dr-ui/src/lib.rs:2848`](../../ui/dr-ui/src/lib.rs#L2848), [`ui/dr-ui/src/lib.rs:808`](../../ui/dr-ui/src/lib.rs#L808), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:102`](../../ui/dr-ui/src/library/sidecar.rs#L102), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:380`](../../ui/dr-ui/src/library/sidecar.rs#L380), [`ui/dr-ui/src/library/sidecar.rs:65`](../../ui/dr-ui/src/library/sidecar.rs#L65), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui/grid.rs:778`](../../ui/dr-ui/src/library_ui/grid.rs#L778), [`ui/dr-ui/src/library_ui/open.rs:455`](../../ui/dr-ui/src/library_ui/open.rs#L455), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1) |
+| FR-CAT-8 | [`core/dr-catalog/src/schema.rs:770`](../../core/dr-catalog/src/schema.rs#L770), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`core/dr-pipeline/src/ops/curve.rs:137`](../../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:2275`](../../core/dr-pipeline/src/sidecar.rs#L2275), [`core/dr-pipeline/src/sidecar.rs:99`](../../core/dr-pipeline/src/sidecar.rs#L99), [`core/dr-pipeline/src/state.rs:1`](../../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../../core/dr-pipeline/tests/tone_curve.rs#L34), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:600`](../../ui/dr-ui/src/develop/session.rs#L600), [`ui/dr-ui/src/export.rs:922`](../../ui/dr-ui/src/export.rs#L922), [`ui/dr-ui/src/lib.rs:1197`](../../ui/dr-ui/src/lib.rs#L1197), [`ui/dr-ui/src/lib.rs:1257`](../../ui/dr-ui/src/lib.rs#L1257), [`ui/dr-ui/src/lib.rs:2655`](../../ui/dr-ui/src/lib.rs#L2655), [`ui/dr-ui/src/lib.rs:2848`](../../ui/dr-ui/src/lib.rs#L2848), [`ui/dr-ui/src/lib.rs:808`](../../ui/dr-ui/src/lib.rs#L808), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:102`](../../ui/dr-ui/src/library/sidecar.rs#L102), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:380`](../../ui/dr-ui/src/library/sidecar.rs#L380), [`ui/dr-ui/src/library/sidecar.rs:65`](../../ui/dr-ui/src/library/sidecar.rs#L65), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui/grid.rs:778`](../../ui/dr-ui/src/library_ui/grid.rs#L778), [`ui/dr-ui/src/library_ui/open.rs:455`](../../ui/dr-ui/src/library_ui/open.rs#L455), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1) |
| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:1212`](../../core/dr-catalog/src/schema.rs#L1212), [`core/dr-catalog/src/walk.rs:161`](../../core/dr-catalog/src/walk.rs#L161), [`core/dr-catalog/src/walk.rs:1`](../../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:432`](../../core/dr-catalog/src/walk.rs#L432), [`core/dr-catalog/src/walk.rs:695`](../../core/dr-catalog/src/walk.rs#L695), [`core/dr-catalog/src/walk.rs:989`](../../core/dr-catalog/src/walk.rs#L989), [`core/dr-sync-nextcloud/src/desktop_client.rs:35`](../../core/dr-sync-nextcloud/src/desktop_client.rs#L35), [`core/dr-sync/src/error.rs:113`](../../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:211`](../../core/dr-sync/src/scan.rs#L211), [`core/dr-sync/src/scan.rs:519`](../../core/dr-sync/src/scan.rs#L519), [`core/dr-types/src/lib.rs:122`](../../core/dr-types/src/lib.rs#L122), [`ui/dr-ui/src/develop/render.rs:626`](../../ui/dr-ui/src/develop/render.rs#L626), [`ui/dr-ui/src/develop/render.rs:669`](../../ui/dr-ui/src/develop/render.rs#L669), [`ui/dr-ui/src/library/filters.rs:89`](../../ui/dr-ui/src/library/filters.rs#L89), [`ui/dr-ui/src/library/scan.rs:126`](../../ui/dr-ui/src/library/scan.rs#L126), [`ui/dr-ui/src/library/scan.rs:179`](../../ui/dr-ui/src/library/scan.rs#L179), [`ui/dr-ui/src/library/scan.rs:210`](../../ui/dr-ui/src/library/scan.rs#L210), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:635`](../../ui/dr-ui/src/library/scan.rs#L635), [`ui/dr-ui/src/library/sidecar.rs:102`](../../ui/dr-ui/src/library/sidecar.rs#L102), [`ui/dr-ui/src/library/sidecar.rs:364`](../../ui/dr-ui/src/library/sidecar.rs#L364), [`ui/dr-ui/src/library/sidecar.rs:380`](../../ui/dr-ui/src/library/sidecar.rs#L380), [`ui/dr-ui/src/library/sidecar.rs:434`](../../ui/dr-ui/src/library/sidecar.rs#L434), [`ui/dr-ui/src/library/spans.rs:378`](../../ui/dr-ui/src/library/spans.rs#L378), [`ui/dr-ui/src/library/thumbnails_fetch.rs:393`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L393), [`ui/dr-ui/src/library/thumbnails_fetch.rs:449`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L449), [`ui/dr-ui/src/library/thumbnails_gen.rs:74`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L74), [`ui/dr-ui/src/library_ui/controller.rs:300`](../../ui/dr-ui/src/library_ui/controller.rs#L300), [`ui/dr-ui/src/library_ui/controller.rs:336`](../../ui/dr-ui/src/library_ui/controller.rs#L336), [`ui/dr-ui/src/library_ui/controller.rs:344`](../../ui/dr-ui/src/library_ui/controller.rs#L344), [`ui/dr-ui/src/library_ui/controller.rs:592`](../../ui/dr-ui/src/library_ui/controller.rs#L592), [`ui/dr-ui/src/library_ui/controller.rs:709`](../../ui/dr-ui/src/library_ui/controller.rs#L709), [`ui/dr-ui/src/library_ui/filter_bar.rs:461`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L461), [`ui/dr-ui/src/library_ui/filter_bar.rs:547`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L547), [`ui/dr-ui/src/library_ui/offline.rs:561`](../../ui/dr-ui/src/library_ui/offline.rs#L561), [`ui/dr-ui/src/library_ui/offline.rs:607`](../../ui/dr-ui/src/library_ui/offline.rs#L607), [`ui/dr-ui/src/library_ui/offline.rs:623`](../../ui/dr-ui/src/library_ui/offline.rs#L623), [`ui/dr-ui/src/library_ui/open.rs:535`](../../ui/dr-ui/src/library_ui/open.rs#L535), [`ui/dr-ui/src/library_ui/open.rs:615`](../../ui/dr-ui/src/library_ui/open.rs#L615), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:75`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L75), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:883`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L883), [`ui/dr-ui/src/library_ui/sync.rs:287`](../../ui/dr-ui/src/library_ui/sync.rs#L287), [`ui/dr-ui/src/library_ui/sync.rs:421`](../../ui/dr-ui/src/library_ui/sync.rs#L421), [`ui/dr-ui/src/library_ui/sync.rs:83`](../../ui/dr-ui/src/library_ui/sync.rs#L83), [`ui/dr-ui/src/library_ui/window.rs:678`](../../ui/dr-ui/src/library_ui/window.rs#L678), [`ui/dr-ui/src/library_ui/window.rs:958`](../../ui/dr-ui/src/library_ui/window.rs#L958), [`ui/dr-ui/src/presets.rs:486`](../../ui/dr-ui/src/presets.rs#L486), [`ui/dr-ui/src/presets.rs:498`](../../ui/dr-ui/src/presets.rs#L498), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) |
| FR-CULL-1 | [`core/dr-decode/src/preview.rs:121`](../../core/dr-decode/src/preview.rs#L121) |
| FR-CULL-10 | [`core/dr-catalog/src/dedup_people.rs:1`](../../core/dr-catalog/src/dedup_people.rs#L1), [`core/dr-catalog/src/faces.rs:1662`](../../core/dr-catalog/src/faces.rs#L1662), [`core/dr-catalog/src/faces.rs:1680`](../../core/dr-catalog/src/faces.rs#L1680), [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1041`](../../core/dr-catalog/src/schema.rs#L1041), [`core/dr-catalog/src/schema.rs:633`](../../core/dr-catalog/src/schema.rs#L633), [`core/dr-catalog/src/schema.rs:811`](../../core/dr-catalog/src/schema.rs#L811), [`core/dr-face/src/assign.rs:1`](../../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../../core/dr-face/src/neighbours.rs#L1), [`core/dr-face/src/references.rs:1`](../../core/dr-face/src/references.rs#L1), [`core/dr-types/src/settings.rs:129`](../../core/dr-types/src/settings.rs#L129), [`ui/dr-ui/src/develop/segmentation.rs:104`](../../ui/dr-ui/src/develop/segmentation.rs#L104), [`ui/dr-ui/src/develop/segmentation.rs:170`](../../ui/dr-ui/src/develop/segmentation.rs#L170), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:95`](../../ui/dr-ui/src/develop/segmentation.rs#L95), [`ui/dr-ui/src/develop/session.rs:23`](../../ui/dr-ui/src/develop/session.rs#L23), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity.rs:1`](../../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1472`](../../ui/dr-ui/src/identity_ui.rs#L1472), [`ui/dr-ui/src/identity_ui.rs:1`](../../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2820`](../../ui/dr-ui/src/lib.rs#L2820), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/ui/identity.slint:1`](../../ui/dr-ui/ui/identity.slint#L1), [`ui/dr-ui/ui/settings.slint:469`](../../ui/dr-ui/ui/settings.slint#L469), [`ui/dr-ui/ui/settings.slint:95`](../../ui/dr-ui/ui/settings.slint#L95) |
@@ -56,64 +56,64 @@ _None._
| FR-CULL-12 | [`core/dr-catalog/src/dedup_people.rs:1`](../../core/dr-catalog/src/dedup_people.rs#L1), [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1041`](../../core/dr-catalog/src/schema.rs#L1041), [`core/dr-catalog/src/schema.rs:633`](../../core/dr-catalog/src/schema.rs#L633), [`ui/dr-ui/src/identity.rs:1`](../../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/ui/identity.slint:1`](../../ui/dr-ui/ui/identity.slint#L1) |
| FR-CULL-13 | [`core/dr-catalog/src/schema.rs:947`](../../core/dr-catalog/src/schema.rs#L947), [`core/dr-types/src/place.rs:133`](../../core/dr-types/src/place.rs#L133), [`ui/dr-ui/src/identity.rs:104`](../../ui/dr-ui/src/identity.rs#L104), [`ui/dr-ui/src/library/filters.rs:136`](../../ui/dr-ui/src/library/filters.rs#L136), [`ui/dr-ui/src/library/filters.rs:763`](../../ui/dr-ui/src/library/filters.rs#L763), [`ui/dr-ui/src/library/filters.rs:806`](../../ui/dr-ui/src/library/filters.rs#L806), [`ui/dr-ui/src/library/filters.rs:844`](../../ui/dr-ui/src/library/filters.rs#L844), [`ui/dr-ui/ui/identity.slint:64`](../../ui/dr-ui/ui/identity.slint#L64), [`ui/dr-ui/ui/library.slint:1928`](../../ui/dr-ui/ui/library.slint#L1928), [`ui/dr-ui/ui/library.slint:735`](../../ui/dr-ui/ui/library.slint#L735) |
| FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../../core/dr-decode/src/preview.rs#L148), [`ui/dr-ui/src/decoder_seam.rs:215`](../../ui/dr-ui/src/decoder_seam.rs#L215), [`ui/dr-ui/src/import.rs:474`](../../ui/dr-ui/src/import.rs#L474) |
-| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1364`](../../ui/dr-ui/src/develop/render.rs#L1364), [`ui/dr-ui/src/develop/render.rs:1428`](../../ui/dr-ui/src/develop/render.rs#L1428), [`ui/dr-ui/src/develop/render.rs:1451`](../../ui/dr-ui/src/develop/render.rs#L1451), [`ui/dr-ui/src/develop/render.rs:317`](../../ui/dr-ui/src/develop/render.rs#L317), [`ui/dr-ui/src/develop/render.rs:330`](../../ui/dr-ui/src/develop/render.rs#L330), [`ui/dr-ui/src/develop/render.rs:375`](../../ui/dr-ui/src/develop/render.rs#L375), [`ui/dr-ui/src/develop/render.rs:386`](../../ui/dr-ui/src/develop/render.rs#L386), [`ui/dr-ui/src/develop/render.rs:392`](../../ui/dr-ui/src/develop/render.rs#L392), [`ui/dr-ui/src/develop/render.rs:409`](../../ui/dr-ui/src/develop/render.rs#L409), [`ui/dr-ui/src/develop/session.rs:122`](../../ui/dr-ui/src/develop/session.rs#L122), [`ui/dr-ui/src/develop/session.rs:142`](../../ui/dr-ui/src/develop/session.rs#L142), [`ui/dr-ui/src/develop/session.rs:148`](../../ui/dr-ui/src/develop/session.rs#L148), [`ui/dr-ui/src/develop_ui.rs:1420`](../../ui/dr-ui/src/develop_ui.rs#L1420), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1286`](../../ui/dr-ui/src/lib.rs#L1286), [`ui/dr-ui/src/lib.rs:2169`](../../ui/dr-ui/src/lib.rs#L2169), [`ui/dr-ui/src/lib.rs:2299`](../../ui/dr-ui/src/lib.rs#L2299), [`ui/dr-ui/src/lib.rs:2342`](../../ui/dr-ui/src/lib.rs#L2342), [`ui/dr-ui/src/lib.rs:479`](../../ui/dr-ui/src/lib.rs#L479), [`ui/dr-ui/src/peaking.rs:1`](../../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:182`](../../ui/dr-ui/ui/app.slint#L182), [`ui/dr-ui/ui/app.slint:1904`](../../ui/dr-ui/ui/app.slint#L1904), [`ui/dr-ui/ui/app.slint:3281`](../../ui/dr-ui/ui/app.slint#L3281), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../../ui/dr-ui/ui/peaking.slint#L56), [`ui/dr-ui/ui/peaking.slint:91`](../../ui/dr-ui/ui/peaking.slint#L91) |
+| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1492`](../../ui/dr-ui/src/develop/render.rs#L1492), [`ui/dr-ui/src/develop/render.rs:1556`](../../ui/dr-ui/src/develop/render.rs#L1556), [`ui/dr-ui/src/develop/render.rs:1579`](../../ui/dr-ui/src/develop/render.rs#L1579), [`ui/dr-ui/src/develop/render.rs:317`](../../ui/dr-ui/src/develop/render.rs#L317), [`ui/dr-ui/src/develop/render.rs:330`](../../ui/dr-ui/src/develop/render.rs#L330), [`ui/dr-ui/src/develop/render.rs:375`](../../ui/dr-ui/src/develop/render.rs#L375), [`ui/dr-ui/src/develop/render.rs:386`](../../ui/dr-ui/src/develop/render.rs#L386), [`ui/dr-ui/src/develop/render.rs:392`](../../ui/dr-ui/src/develop/render.rs#L392), [`ui/dr-ui/src/develop/render.rs:409`](../../ui/dr-ui/src/develop/render.rs#L409), [`ui/dr-ui/src/develop/session.rs:122`](../../ui/dr-ui/src/develop/session.rs#L122), [`ui/dr-ui/src/develop/session.rs:142`](../../ui/dr-ui/src/develop/session.rs#L142), [`ui/dr-ui/src/develop/session.rs:148`](../../ui/dr-ui/src/develop/session.rs#L148), [`ui/dr-ui/src/develop_ui.rs:1419`](../../ui/dr-ui/src/develop_ui.rs#L1419), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1286`](../../ui/dr-ui/src/lib.rs#L1286), [`ui/dr-ui/src/lib.rs:2169`](../../ui/dr-ui/src/lib.rs#L2169), [`ui/dr-ui/src/lib.rs:2299`](../../ui/dr-ui/src/lib.rs#L2299), [`ui/dr-ui/src/lib.rs:2342`](../../ui/dr-ui/src/lib.rs#L2342), [`ui/dr-ui/src/lib.rs:479`](../../ui/dr-ui/src/lib.rs#L479), [`ui/dr-ui/src/peaking.rs:1`](../../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:182`](../../ui/dr-ui/ui/app.slint#L182), [`ui/dr-ui/ui/app.slint:1904`](../../ui/dr-ui/ui/app.slint#L1904), [`ui/dr-ui/ui/app.slint:3281`](../../ui/dr-ui/ui/app.slint#L3281), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../../ui/dr-ui/ui/peaking.slint#L56), [`ui/dr-ui/ui/peaking.slint:91`](../../ui/dr-ui/ui/peaking.slint#L91) |
| FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-xmp/src/lib.rs:442`](../../core/dr-xmp/src/lib.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/ui/app.slint:1673`](../../ui/dr-ui/ui/app.slint#L1673), [`ui/dr-ui/ui/app.slint:2778`](../../ui/dr-ui/ui/app.slint#L2778), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/library.slint:1044`](../../ui/dr-ui/ui/library.slint#L1044), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5093`](../../ui/dr-ui/ui/library.slint#L5093), [`ui/dr-ui/ui/library.slint:647`](../../ui/dr-ui/ui/library.slint#L647) |
| FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:676`](../../core/dr-catalog/src/schema.rs#L676), [`ui/dr-ui/src/bursts.rs:1`](../../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library/cells.rs:21`](../../ui/dr-ui/src/library/cells.rs#L21), [`ui/dr-ui/src/library/cells.rs:776`](../../ui/dr-ui/src/library/cells.rs#L776), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488) |
| FR-CULL-8 | [`core/dr-catalog/src/face_shard.rs:1`](../../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1000`](../../core/dr-catalog/src/schema.rs#L1000), [`core/dr-catalog/src/schema.rs:1041`](../../core/dr-catalog/src/schema.rs#L1041), [`core/dr-catalog/src/schema.rs:744`](../../core/dr-catalog/src/schema.rs#L744), [`core/dr-face/src/align.rs:296`](../../core/dr-face/src/align.rs#L296), [`core/dr-types/src/settings.rs:176`](../../core/dr-types/src/settings.rs#L176), [`ui/dr-ui/examples/face_detectors.rs:1`](../../ui/dr-ui/examples/face_detectors.rs#L1), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/derived_sync.rs:127`](../../ui/dr-ui/src/derived_sync.rs#L127), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:446`](../../ui/dr-ui/src/faces.rs#L446), [`ui/dr-ui/src/faces.rs:462`](../../ui/dr-ui/src/faces.rs#L462), [`ui/dr-ui/src/faces.rs:584`](../../ui/dr-ui/src/faces.rs#L584), [`ui/dr-ui/src/identity_ui.rs:1472`](../../ui/dr-ui/src/identity_ui.rs#L1472), [`ui/dr-ui/src/identity_ui.rs:617`](../../ui/dr-ui/src/identity_ui.rs#L617), [`ui/dr-ui/src/lib.rs:1780`](../../ui/dr-ui/src/lib.rs#L1780), [`ui/dr-ui/src/lib.rs:3108`](../../ui/dr-ui/src/lib.rs#L3108), [`ui/dr-ui/src/library/sweep.rs:242`](../../ui/dr-ui/src/library/sweep.rs#L242), [`ui/dr-ui/src/library/sweep.rs:510`](../../ui/dr-ui/src/library/sweep.rs#L510), [`ui/dr-ui/src/library_ui/controller.rs:422`](../../ui/dr-ui/src/library_ui/controller.rs#L422), [`ui/dr-ui/src/library_ui/controller.rs:512`](../../ui/dr-ui/src/library_ui/controller.rs#L512), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/settings_ui.rs:174`](../../ui/dr-ui/src/settings_ui.rs#L174), [`ui/dr-ui/src/settings_ui.rs:537`](../../ui/dr-ui/src/settings_ui.rs#L537), [`ui/dr-ui/ui/app.slint:672`](../../ui/dr-ui/ui/app.slint#L672), [`ui/dr-ui/ui/settings.slint:101`](../../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:407`](../../ui/dr-ui/ui/settings.slint#L407), [`ui/dr-ui/ui/settings.slint:469`](../../ui/dr-ui/ui/settings.slint#L469), [`ui/dr-ui/ui/settings.slint:83`](../../ui/dr-ui/ui/settings.slint#L83), [`ui/dr-ui/ui/settings.slint:95`](../../ui/dr-ui/ui/settings.slint#L95) |
| FR-CULL-8a | [`core/dr-catalog/src/faces.rs:124`](../../core/dr-catalog/src/faces.rs#L124), [`core/dr-catalog/src/schema.rs:947`](../../core/dr-catalog/src/schema.rs#L947), [`core/dr-face/src/classify.rs:1`](../../core/dr-face/src/classify.rs#L1), [`core/dr-face/src/eyes.rs:1`](../../core/dr-face/src/eyes.rs#L1), [`core/dr-face/src/landmarks.rs:1`](../../core/dr-face/src/landmarks.rs#L1), [`core/dr-types/src/place.rs:133`](../../core/dr-types/src/place.rs#L133), [`ui/dr-ui/src/faces.rs:166`](../../ui/dr-ui/src/faces.rs#L166), [`ui/dr-ui/src/faces.rs:1821`](../../ui/dr-ui/src/faces.rs#L1821), [`ui/dr-ui/src/faces.rs:308`](../../ui/dr-ui/src/faces.rs#L308), [`ui/dr-ui/src/identity.rs:104`](../../ui/dr-ui/src/identity.rs#L104), [`ui/dr-ui/src/library/filters.rs:136`](../../ui/dr-ui/src/library/filters.rs#L136), [`ui/dr-ui/src/library/filters.rs:763`](../../ui/dr-ui/src/library/filters.rs#L763), [`ui/dr-ui/src/library/filters.rs:806`](../../ui/dr-ui/src/library/filters.rs#L806), [`ui/dr-ui/src/library/filters.rs:844`](../../ui/dr-ui/src/library/filters.rs#L844), [`ui/dr-ui/src/repairs.rs:579`](../../ui/dr-ui/src/repairs.rs#L579), [`ui/dr-ui/ui/identity.slint:276`](../../ui/dr-ui/ui/identity.slint#L276), [`ui/dr-ui/ui/identity.slint:64`](../../ui/dr-ui/ui/identity.slint#L64), [`ui/dr-ui/ui/library.slint:1928`](../../ui/dr-ui/ui/library.slint#L1928), [`ui/dr-ui/ui/library.slint:735`](../../ui/dr-ui/ui/library.slint#L735) |
| FR-CULL-9 | [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1041`](../../core/dr-catalog/src/schema.rs#L1041), [`core/dr-catalog/src/schema.rs:811`](../../core/dr-catalog/src/schema.rs#L811), [`core/dr-face/src/assign.rs:1`](../../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:129`](../../core/dr-types/src/settings.rs#L129), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:584`](../../ui/dr-ui/src/faces.rs#L584), [`ui/dr-ui/src/identity_ui.rs:1`](../../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/repairs.rs:525`](../../ui/dr-ui/src/repairs.rs#L525), [`ui/dr-ui/ui/identity.slint:355`](../../ui/dr-ui/ui/identity.slint#L355) |
| FR-DEV-1 | [`core/dr-pipeline/src/graph.rs:1`](../../core/dr-pipeline/src/graph.rs#L1), [`core/dr-pipeline/src/sidecar.rs:1`](../../core/dr-pipeline/src/sidecar.rs#L1) |
-| FR-DEV-10 | [`core/dr-gpu/src/mask.rs:1039`](../../core/dr-gpu/src/mask.rs#L1039), [`core/dr-gpu/src/mask.rs:1263`](../../core/dr-gpu/src/mask.rs#L1263), [`core/dr-gpu/src/mask.rs:1522`](../../core/dr-gpu/src/mask.rs#L1522), [`core/dr-gpu/src/mask.rs:422`](../../core/dr-gpu/src/mask.rs#L422), [`core/dr-gpu/src/mask.rs:464`](../../core/dr-gpu/src/mask.rs#L464), [`core/dr-gpu/src/mask.rs:58`](../../core/dr-gpu/src/mask.rs#L58), [`core/dr-gpu/src/mask.rs:60`](../../core/dr-gpu/src/mask.rs#L60), [`core/dr-gpu/src/mask.rs:846`](../../core/dr-gpu/src/mask.rs#L846), [`core/dr-gpu/src/mask.rs:85`](../../core/dr-gpu/src/mask.rs#L85), [`core/dr-gpu/src/mask.rs:925`](../../core/dr-gpu/src/mask.rs#L925), [`core/dr-gpu/src/shaders/mask.wgsl:117`](../../core/dr-gpu/src/shaders/mask.wgsl#L117), [`core/dr-gpu/src/shaders/mask.wgsl:59`](../../core/dr-gpu/src/shaders/mask.wgsl#L59), [`core/dr-gpu/tests/range_masks.rs:119`](../../core/dr-gpu/tests/range_masks.rs#L119), [`core/dr-gpu/tests/range_masks.rs:147`](../../core/dr-gpu/tests/range_masks.rs#L147), [`core/dr-gpu/tests/range_masks.rs:175`](../../core/dr-gpu/tests/range_masks.rs#L175), [`core/dr-gpu/tests/range_masks.rs:1`](../../core/dr-gpu/tests/range_masks.rs#L1), [`core/dr-gpu/tests/range_masks.rs:94`](../../core/dr-gpu/tests/range_masks.rs#L94), [`core/dr-pipeline/src/mask.rs:125`](../../core/dr-pipeline/src/mask.rs#L125), [`core/dr-pipeline/src/mask.rs:3099`](../../core/dr-pipeline/src/mask.rs#L3099), [`core/dr-pipeline/src/mask.rs:3112`](../../core/dr-pipeline/src/mask.rs#L3112), [`core/dr-pipeline/src/mask.rs:3131`](../../core/dr-pipeline/src/mask.rs#L3131), [`core/dr-pipeline/src/mask.rs:3145`](../../core/dr-pipeline/src/mask.rs#L3145), [`core/dr-pipeline/src/mask.rs:3155`](../../core/dr-pipeline/src/mask.rs#L3155), [`core/dr-pipeline/src/mask.rs:710`](../../core/dr-pipeline/src/mask.rs#L710), [`core/dr-pipeline/src/mask.rs:749`](../../core/dr-pipeline/src/mask.rs#L749), [`core/dr-pipeline/src/mask.rs:809`](../../core/dr-pipeline/src/mask.rs#L809), [`core/dr-pipeline/src/mask.rs:828`](../../core/dr-pipeline/src/mask.rs#L828), [`core/dr-pipeline/src/mask.rs:852`](../../core/dr-pipeline/src/mask.rs#L852), [`core/dr-pipeline/src/mask.rs:864`](../../core/dr-pipeline/src/mask.rs#L864), [`core/dr-pipeline/src/mask.rs:876`](../../core/dr-pipeline/src/mask.rs#L876), [`core/dr-pipeline/src/sidecar.rs:1369`](../../core/dr-pipeline/src/sidecar.rs#L1369), [`core/dr-pipeline/src/sidecar.rs:1548`](../../core/dr-pipeline/src/sidecar.rs#L1548), [`core/dr-pipeline/src/sidecar.rs:1637`](../../core/dr-pipeline/src/sidecar.rs#L1637), [`core/dr-pipeline/src/sidecar.rs:1741`](../../core/dr-pipeline/src/sidecar.rs#L1741), [`core/dr-pipeline/src/sidecar.rs:1949`](../../core/dr-pipeline/src/sidecar.rs#L1949), [`core/dr-pipeline/tests/mask_sidecar.rs:1157`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1157), [`core/dr-pipeline/tests/mask_sidecar.rs:1186`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1186), [`core/dr-pipeline/tests/mask_sidecar.rs:1204`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1204), [`core/dr-pipeline/tests/mask_sidecar.rs:1234`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1234), [`ui/dr-ui/src/develop/mask_ops.rs:341`](../../ui/dr-ui/src/develop/mask_ops.rs#L341), [`ui/dr-ui/src/develop/mask_ops.rs:638`](../../ui/dr-ui/src/develop/mask_ops.rs#L638), [`ui/dr-ui/src/develop/mask_ops.rs:665`](../../ui/dr-ui/src/develop/mask_ops.rs#L665), [`ui/dr-ui/src/develop/mask_ops.rs:697`](../../ui/dr-ui/src/develop/mask_ops.rs#L697), [`ui/dr-ui/src/develop/mask_ops.rs:721`](../../ui/dr-ui/src/develop/mask_ops.rs#L721), [`ui/dr-ui/src/develop/mask_ops.rs:727`](../../ui/dr-ui/src/develop/mask_ops.rs#L727), [`ui/dr-ui/src/develop/mask_ops.rs:734`](../../ui/dr-ui/src/develop/mask_ops.rs#L734), [`ui/dr-ui/src/develop/mask_ops.rs:754`](../../ui/dr-ui/src/develop/mask_ops.rs#L754), [`ui/dr-ui/src/labels.rs:63`](../../ui/dr-ui/src/labels.rs#L63), [`ui/dr-ui/src/masks_ui.rs:1257`](../../ui/dr-ui/src/masks_ui.rs#L1257), [`ui/dr-ui/src/masks_ui.rs:797`](../../ui/dr-ui/src/masks_ui.rs#L797) |
+| FR-DEV-10 | [`core/dr-gpu/src/mask.rs:1039`](../../core/dr-gpu/src/mask.rs#L1039), [`core/dr-gpu/src/mask.rs:1263`](../../core/dr-gpu/src/mask.rs#L1263), [`core/dr-gpu/src/mask.rs:1522`](../../core/dr-gpu/src/mask.rs#L1522), [`core/dr-gpu/src/mask.rs:422`](../../core/dr-gpu/src/mask.rs#L422), [`core/dr-gpu/src/mask.rs:464`](../../core/dr-gpu/src/mask.rs#L464), [`core/dr-gpu/src/mask.rs:58`](../../core/dr-gpu/src/mask.rs#L58), [`core/dr-gpu/src/mask.rs:60`](../../core/dr-gpu/src/mask.rs#L60), [`core/dr-gpu/src/mask.rs:846`](../../core/dr-gpu/src/mask.rs#L846), [`core/dr-gpu/src/mask.rs:85`](../../core/dr-gpu/src/mask.rs#L85), [`core/dr-gpu/src/mask.rs:925`](../../core/dr-gpu/src/mask.rs#L925), [`core/dr-gpu/src/shaders/mask.wgsl:117`](../../core/dr-gpu/src/shaders/mask.wgsl#L117), [`core/dr-gpu/src/shaders/mask.wgsl:59`](../../core/dr-gpu/src/shaders/mask.wgsl#L59), [`core/dr-gpu/tests/range_masks.rs:119`](../../core/dr-gpu/tests/range_masks.rs#L119), [`core/dr-gpu/tests/range_masks.rs:147`](../../core/dr-gpu/tests/range_masks.rs#L147), [`core/dr-gpu/tests/range_masks.rs:175`](../../core/dr-gpu/tests/range_masks.rs#L175), [`core/dr-gpu/tests/range_masks.rs:1`](../../core/dr-gpu/tests/range_masks.rs#L1), [`core/dr-gpu/tests/range_masks.rs:94`](../../core/dr-gpu/tests/range_masks.rs#L94), [`core/dr-pipeline/src/mask.rs:125`](../../core/dr-pipeline/src/mask.rs#L125), [`core/dr-pipeline/src/mask.rs:3208`](../../core/dr-pipeline/src/mask.rs#L3208), [`core/dr-pipeline/src/mask.rs:3221`](../../core/dr-pipeline/src/mask.rs#L3221), [`core/dr-pipeline/src/mask.rs:3240`](../../core/dr-pipeline/src/mask.rs#L3240), [`core/dr-pipeline/src/mask.rs:3254`](../../core/dr-pipeline/src/mask.rs#L3254), [`core/dr-pipeline/src/mask.rs:3264`](../../core/dr-pipeline/src/mask.rs#L3264), [`core/dr-pipeline/src/mask.rs:710`](../../core/dr-pipeline/src/mask.rs#L710), [`core/dr-pipeline/src/mask.rs:749`](../../core/dr-pipeline/src/mask.rs#L749), [`core/dr-pipeline/src/mask.rs:809`](../../core/dr-pipeline/src/mask.rs#L809), [`core/dr-pipeline/src/mask.rs:828`](../../core/dr-pipeline/src/mask.rs#L828), [`core/dr-pipeline/src/mask.rs:852`](../../core/dr-pipeline/src/mask.rs#L852), [`core/dr-pipeline/src/mask.rs:864`](../../core/dr-pipeline/src/mask.rs#L864), [`core/dr-pipeline/src/mask.rs:876`](../../core/dr-pipeline/src/mask.rs#L876), [`core/dr-pipeline/src/sidecar.rs:1369`](../../core/dr-pipeline/src/sidecar.rs#L1369), [`core/dr-pipeline/src/sidecar.rs:1548`](../../core/dr-pipeline/src/sidecar.rs#L1548), [`core/dr-pipeline/src/sidecar.rs:1637`](../../core/dr-pipeline/src/sidecar.rs#L1637), [`core/dr-pipeline/src/sidecar.rs:1741`](../../core/dr-pipeline/src/sidecar.rs#L1741), [`core/dr-pipeline/src/sidecar.rs:1949`](../../core/dr-pipeline/src/sidecar.rs#L1949), [`core/dr-pipeline/tests/mask_sidecar.rs:1157`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1157), [`core/dr-pipeline/tests/mask_sidecar.rs:1186`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1186), [`core/dr-pipeline/tests/mask_sidecar.rs:1204`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1204), [`core/dr-pipeline/tests/mask_sidecar.rs:1234`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1234), [`ui/dr-ui/src/develop/mask_ops.rs:341`](../../ui/dr-ui/src/develop/mask_ops.rs#L341), [`ui/dr-ui/src/develop/mask_ops.rs:638`](../../ui/dr-ui/src/develop/mask_ops.rs#L638), [`ui/dr-ui/src/develop/mask_ops.rs:665`](../../ui/dr-ui/src/develop/mask_ops.rs#L665), [`ui/dr-ui/src/develop/mask_ops.rs:697`](../../ui/dr-ui/src/develop/mask_ops.rs#L697), [`ui/dr-ui/src/develop/mask_ops.rs:721`](../../ui/dr-ui/src/develop/mask_ops.rs#L721), [`ui/dr-ui/src/develop/mask_ops.rs:727`](../../ui/dr-ui/src/develop/mask_ops.rs#L727), [`ui/dr-ui/src/develop/mask_ops.rs:734`](../../ui/dr-ui/src/develop/mask_ops.rs#L734), [`ui/dr-ui/src/develop/mask_ops.rs:754`](../../ui/dr-ui/src/develop/mask_ops.rs#L754), [`ui/dr-ui/src/labels.rs:63`](../../ui/dr-ui/src/labels.rs#L63), [`ui/dr-ui/src/masks_ui.rs:1257`](../../ui/dr-ui/src/masks_ui.rs#L1257), [`ui/dr-ui/src/masks_ui.rs:797`](../../ui/dr-ui/src/masks_ui.rs#L797) |
| FR-DEV-12 | [`core/dr-pipeline/ops/colour_grading.yaml:1`](../../core/dr-pipeline/ops/colour_grading.yaml#L1) |
-| FR-DEV-16 | [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/develop_ui.rs:476`](../../ui/dr-ui/src/develop_ui.rs#L476), [`ui/dr-ui/src/lib.rs:460`](../../ui/dr-ui/src/lib.rs#L460), [`ui/dr-ui/src/masks_ui.rs:1140`](../../ui/dr-ui/src/masks_ui.rs#L1140), [`ui/dr-ui/src/masks_ui.rs:713`](../../ui/dr-ui/src/masks_ui.rs#L713), [`ui/dr-ui/ui/adjust.slint:1024`](../../ui/dr-ui/ui/adjust.slint#L1024), [`ui/dr-ui/ui/adjust.slint:1029`](../../ui/dr-ui/ui/adjust.slint#L1029), [`ui/dr-ui/ui/adjust.slint:651`](../../ui/dr-ui/ui/adjust.slint#L651), [`ui/dr-ui/ui/app.slint:2550`](../../ui/dr-ui/ui/app.slint#L2550), [`ui/dr-ui/ui/app.slint:2580`](../../ui/dr-ui/ui/app.slint#L2580), [`ui/dr-ui/ui/app.slint:2600`](../../ui/dr-ui/ui/app.slint#L2600), [`ui/dr-ui/ui/app.slint:2618`](../../ui/dr-ui/ui/app.slint#L2618), [`ui/dr-ui/ui/app.slint:2626`](../../ui/dr-ui/ui/app.slint#L2626), [`ui/dr-ui/ui/app.slint:2650`](../../ui/dr-ui/ui/app.slint#L2650), [`ui/dr-ui/ui/app.slint:2733`](../../ui/dr-ui/ui/app.slint#L2733), [`ui/dr-ui/ui/app.slint:2749`](../../ui/dr-ui/ui/app.slint#L2749), [`ui/dr-ui/ui/app.slint:2778`](../../ui/dr-ui/ui/app.slint#L2778), [`ui/dr-ui/ui/app.slint:722`](../../ui/dr-ui/ui/app.slint#L722), [`ui/dr-ui/ui/app.slint:745`](../../ui/dr-ui/ui/app.slint#L745), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/masks.slint:858`](../../ui/dr-ui/ui/masks.slint#L858), [`ui/dr-ui/ui/masks.slint:884`](../../ui/dr-ui/ui/masks.slint#L884) |
-| FR-DEV-17 | [`core/dr-pipeline/src/orphan.rs:142`](../../core/dr-pipeline/src/orphan.rs#L142), [`core/dr-pipeline/src/orphan.rs:1`](../../core/dr-pipeline/src/orphan.rs#L1), [`ui/dr-ui/src/develop/framing.rs:1201`](../../ui/dr-ui/src/develop/framing.rs#L1201), [`ui/dr-ui/src/develop/framing.rs:133`](../../ui/dr-ui/src/develop/framing.rs#L133), [`ui/dr-ui/src/develop/framing.rs:169`](../../ui/dr-ui/src/develop/framing.rs#L169), [`ui/dr-ui/src/develop/framing.rs:235`](../../ui/dr-ui/src/develop/framing.rs#L235), [`ui/dr-ui/src/develop/framing.rs:243`](../../ui/dr-ui/src/develop/framing.rs#L243), [`ui/dr-ui/src/develop/session.rs:110`](../../ui/dr-ui/src/develop/session.rs#L110), [`ui/dr-ui/src/develop_ui.rs:1073`](../../ui/dr-ui/src/develop_ui.rs#L1073), [`ui/dr-ui/src/develop_ui.rs:1099`](../../ui/dr-ui/src/develop_ui.rs#L1099), [`ui/dr-ui/src/develop_ui.rs:1162`](../../ui/dr-ui/src/develop_ui.rs#L1162), [`ui/dr-ui/src/lib.rs:2134`](../../ui/dr-ui/src/lib.rs#L2134), [`ui/dr-ui/src/lib.rs:635`](../../ui/dr-ui/src/lib.rs#L635), [`ui/dr-ui/ui/adjust.slint:704`](../../ui/dr-ui/ui/adjust.slint#L704), [`ui/dr-ui/ui/adjust.slint:708`](../../ui/dr-ui/ui/adjust.slint#L708), [`ui/dr-ui/ui/app.slint:2600`](../../ui/dr-ui/ui/app.slint#L2600), [`ui/dr-ui/ui/app.slint:3013`](../../ui/dr-ui/ui/app.slint#L3013), [`ui/dr-ui/ui/crop.slint:51`](../../ui/dr-ui/ui/crop.slint#L51) |
+| FR-DEV-16 | [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/develop_ui.rs:475`](../../ui/dr-ui/src/develop_ui.rs#L475), [`ui/dr-ui/src/lib.rs:460`](../../ui/dr-ui/src/lib.rs#L460), [`ui/dr-ui/src/masks_ui.rs:1140`](../../ui/dr-ui/src/masks_ui.rs#L1140), [`ui/dr-ui/src/masks_ui.rs:713`](../../ui/dr-ui/src/masks_ui.rs#L713), [`ui/dr-ui/ui/adjust.slint:1024`](../../ui/dr-ui/ui/adjust.slint#L1024), [`ui/dr-ui/ui/adjust.slint:1029`](../../ui/dr-ui/ui/adjust.slint#L1029), [`ui/dr-ui/ui/adjust.slint:651`](../../ui/dr-ui/ui/adjust.slint#L651), [`ui/dr-ui/ui/app.slint:2550`](../../ui/dr-ui/ui/app.slint#L2550), [`ui/dr-ui/ui/app.slint:2580`](../../ui/dr-ui/ui/app.slint#L2580), [`ui/dr-ui/ui/app.slint:2600`](../../ui/dr-ui/ui/app.slint#L2600), [`ui/dr-ui/ui/app.slint:2618`](../../ui/dr-ui/ui/app.slint#L2618), [`ui/dr-ui/ui/app.slint:2626`](../../ui/dr-ui/ui/app.slint#L2626), [`ui/dr-ui/ui/app.slint:2650`](../../ui/dr-ui/ui/app.slint#L2650), [`ui/dr-ui/ui/app.slint:2733`](../../ui/dr-ui/ui/app.slint#L2733), [`ui/dr-ui/ui/app.slint:2749`](../../ui/dr-ui/ui/app.slint#L2749), [`ui/dr-ui/ui/app.slint:2778`](../../ui/dr-ui/ui/app.slint#L2778), [`ui/dr-ui/ui/app.slint:722`](../../ui/dr-ui/ui/app.slint#L722), [`ui/dr-ui/ui/app.slint:745`](../../ui/dr-ui/ui/app.slint#L745), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/masks.slint:858`](../../ui/dr-ui/ui/masks.slint#L858), [`ui/dr-ui/ui/masks.slint:884`](../../ui/dr-ui/ui/masks.slint#L884) |
+| FR-DEV-17 | [`core/dr-pipeline/src/orphan.rs:142`](../../core/dr-pipeline/src/orphan.rs#L142), [`core/dr-pipeline/src/orphan.rs:1`](../../core/dr-pipeline/src/orphan.rs#L1), [`ui/dr-ui/src/develop/framing.rs:1201`](../../ui/dr-ui/src/develop/framing.rs#L1201), [`ui/dr-ui/src/develop/framing.rs:133`](../../ui/dr-ui/src/develop/framing.rs#L133), [`ui/dr-ui/src/develop/framing.rs:169`](../../ui/dr-ui/src/develop/framing.rs#L169), [`ui/dr-ui/src/develop/framing.rs:235`](../../ui/dr-ui/src/develop/framing.rs#L235), [`ui/dr-ui/src/develop/framing.rs:243`](../../ui/dr-ui/src/develop/framing.rs#L243), [`ui/dr-ui/src/develop/session.rs:110`](../../ui/dr-ui/src/develop/session.rs#L110), [`ui/dr-ui/src/develop_ui.rs:1072`](../../ui/dr-ui/src/develop_ui.rs#L1072), [`ui/dr-ui/src/develop_ui.rs:1098`](../../ui/dr-ui/src/develop_ui.rs#L1098), [`ui/dr-ui/src/develop_ui.rs:1161`](../../ui/dr-ui/src/develop_ui.rs#L1161), [`ui/dr-ui/src/lib.rs:2134`](../../ui/dr-ui/src/lib.rs#L2134), [`ui/dr-ui/src/lib.rs:635`](../../ui/dr-ui/src/lib.rs#L635), [`ui/dr-ui/ui/adjust.slint:704`](../../ui/dr-ui/ui/adjust.slint#L704), [`ui/dr-ui/ui/adjust.slint:708`](../../ui/dr-ui/ui/adjust.slint#L708), [`ui/dr-ui/ui/app.slint:2600`](../../ui/dr-ui/ui/app.slint#L2600), [`ui/dr-ui/ui/app.slint:3013`](../../ui/dr-ui/ui/app.slint#L3013), [`ui/dr-ui/ui/crop.slint:51`](../../ui/dr-ui/ui/crop.slint#L51) |
| FR-DEV-18 | [`core/dr-pipeline/src/ops/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:243`](../../core/dr-pipeline/src/ops/dehaze.rs#L243) |
| FR-DEV-19 | [`core/dr-pipeline/src/mask.rs:1341`](../../core/dr-pipeline/src/mask.rs#L1341) |
-| FR-DEV-19a | [`core/dr-gpu/src/mask.rs:655`](../../core/dr-gpu/src/mask.rs#L655), [`core/dr-gpu/src/mask.rs:773`](../../core/dr-gpu/src/mask.rs#L773), [`core/dr-gpu/tests/local_adjustments.rs:793`](../../core/dr-gpu/tests/local_adjustments.rs#L793), [`core/dr-gpu/tests/local_adjustments.rs:841`](../../core/dr-gpu/tests/local_adjustments.rs#L841), [`core/dr-pipeline/src/mask.rs:1022`](../../core/dr-pipeline/src/mask.rs#L1022), [`core/dr-pipeline/src/mask.rs:1635`](../../core/dr-pipeline/src/mask.rs#L1635), [`core/dr-pipeline/src/mask.rs:3173`](../../core/dr-pipeline/src/mask.rs#L3173), [`core/dr-pipeline/src/mask.rs:3294`](../../core/dr-pipeline/src/mask.rs#L3294), [`core/dr-pipeline/src/mask.rs:3329`](../../core/dr-pipeline/src/mask.rs#L3329), [`core/dr-pipeline/src/mask.rs:929`](../../core/dr-pipeline/src/mask.rs#L929), [`core/dr-pipeline/src/mask.rs:964`](../../core/dr-pipeline/src/mask.rs#L964), [`core/dr-pipeline/src/sidecar.rs:1259`](../../core/dr-pipeline/src/sidecar.rs#L1259), [`core/dr-pipeline/tests/mask_sidecar.rs:1347`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1347), [`core/dr-pipeline/tests/mask_sidecar.rs:1369`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1369), [`core/dr-pipeline/tests/mask_sidecar.rs:1395`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1395), [`ui/dr-ui/src/develop/masks.rs:432`](../../ui/dr-ui/src/develop/masks.rs#L432), [`ui/dr-ui/src/develop/masks.rs:493`](../../ui/dr-ui/src/develop/masks.rs#L493), [`ui/dr-ui/src/develop/masks.rs:512`](../../ui/dr-ui/src/develop/masks.rs#L512), [`ui/dr-ui/src/develop/masks.rs:547`](../../ui/dr-ui/src/develop/masks.rs#L547), [`ui/dr-ui/src/develop/session.rs:192`](../../ui/dr-ui/src/develop/session.rs#L192), [`ui/dr-ui/src/labels.rs:73`](../../ui/dr-ui/src/labels.rs#L73), [`ui/dr-ui/src/masks_ui.rs:1083`](../../ui/dr-ui/src/masks_ui.rs#L1083), [`ui/dr-ui/src/masks_ui.rs:1140`](../../ui/dr-ui/src/masks_ui.rs#L1140), [`ui/dr-ui/src/masks_ui.rs:1196`](../../ui/dr-ui/src/masks_ui.rs#L1196), [`ui/dr-ui/src/masks_ui.rs:1555`](../../ui/dr-ui/src/masks_ui.rs#L1555), [`ui/dr-ui/src/masks_ui.rs:1578`](../../ui/dr-ui/src/masks_ui.rs#L1578), [`ui/dr-ui/src/masks_ui.rs:270`](../../ui/dr-ui/src/masks_ui.rs#L270), [`ui/dr-ui/ui/app.slint:2626`](../../ui/dr-ui/ui/app.slint#L2626), [`ui/dr-ui/ui/masks.slint:151`](../../ui/dr-ui/ui/masks.slint#L151), [`ui/dr-ui/ui/masks.slint:176`](../../ui/dr-ui/ui/masks.slint#L176), [`ui/dr-ui/ui/masks.slint:339`](../../ui/dr-ui/ui/masks.slint#L339), [`ui/dr-ui/ui/masks.slint:379`](../../ui/dr-ui/ui/masks.slint#L379), [`ui/dr-ui/ui/masks.slint:422`](../../ui/dr-ui/ui/masks.slint#L422), [`ui/dr-ui/ui/masks.slint:468`](../../ui/dr-ui/ui/masks.slint#L468), [`ui/dr-ui/ui/masks.slint:796`](../../ui/dr-ui/ui/masks.slint#L796), [`ui/dr-ui/ui/masks.slint:881`](../../ui/dr-ui/ui/masks.slint#L881) |
+| FR-DEV-19a | [`core/dr-gpu/src/mask.rs:655`](../../core/dr-gpu/src/mask.rs#L655), [`core/dr-gpu/src/mask.rs:773`](../../core/dr-gpu/src/mask.rs#L773), [`core/dr-gpu/tests/local_adjustments.rs:793`](../../core/dr-gpu/tests/local_adjustments.rs#L793), [`core/dr-gpu/tests/local_adjustments.rs:841`](../../core/dr-gpu/tests/local_adjustments.rs#L841), [`core/dr-pipeline/src/mask.rs:1022`](../../core/dr-pipeline/src/mask.rs#L1022), [`core/dr-pipeline/src/mask.rs:1635`](../../core/dr-pipeline/src/mask.rs#L1635), [`core/dr-pipeline/src/mask.rs:3282`](../../core/dr-pipeline/src/mask.rs#L3282), [`core/dr-pipeline/src/mask.rs:3403`](../../core/dr-pipeline/src/mask.rs#L3403), [`core/dr-pipeline/src/mask.rs:3438`](../../core/dr-pipeline/src/mask.rs#L3438), [`core/dr-pipeline/src/mask.rs:929`](../../core/dr-pipeline/src/mask.rs#L929), [`core/dr-pipeline/src/mask.rs:964`](../../core/dr-pipeline/src/mask.rs#L964), [`core/dr-pipeline/src/sidecar.rs:1259`](../../core/dr-pipeline/src/sidecar.rs#L1259), [`core/dr-pipeline/tests/mask_sidecar.rs:1347`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1347), [`core/dr-pipeline/tests/mask_sidecar.rs:1369`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1369), [`core/dr-pipeline/tests/mask_sidecar.rs:1395`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1395), [`ui/dr-ui/src/develop/masks.rs:432`](../../ui/dr-ui/src/develop/masks.rs#L432), [`ui/dr-ui/src/develop/masks.rs:493`](../../ui/dr-ui/src/develop/masks.rs#L493), [`ui/dr-ui/src/develop/masks.rs:512`](../../ui/dr-ui/src/develop/masks.rs#L512), [`ui/dr-ui/src/develop/masks.rs:547`](../../ui/dr-ui/src/develop/masks.rs#L547), [`ui/dr-ui/src/develop/session.rs:192`](../../ui/dr-ui/src/develop/session.rs#L192), [`ui/dr-ui/src/labels.rs:73`](../../ui/dr-ui/src/labels.rs#L73), [`ui/dr-ui/src/masks_ui.rs:1083`](../../ui/dr-ui/src/masks_ui.rs#L1083), [`ui/dr-ui/src/masks_ui.rs:1140`](../../ui/dr-ui/src/masks_ui.rs#L1140), [`ui/dr-ui/src/masks_ui.rs:1196`](../../ui/dr-ui/src/masks_ui.rs#L1196), [`ui/dr-ui/src/masks_ui.rs:1555`](../../ui/dr-ui/src/masks_ui.rs#L1555), [`ui/dr-ui/src/masks_ui.rs:1578`](../../ui/dr-ui/src/masks_ui.rs#L1578), [`ui/dr-ui/src/masks_ui.rs:270`](../../ui/dr-ui/src/masks_ui.rs#L270), [`ui/dr-ui/ui/app.slint:2626`](../../ui/dr-ui/ui/app.slint#L2626), [`ui/dr-ui/ui/masks.slint:151`](../../ui/dr-ui/ui/masks.slint#L151), [`ui/dr-ui/ui/masks.slint:176`](../../ui/dr-ui/ui/masks.slint#L176), [`ui/dr-ui/ui/masks.slint:339`](../../ui/dr-ui/ui/masks.slint#L339), [`ui/dr-ui/ui/masks.slint:379`](../../ui/dr-ui/ui/masks.slint#L379), [`ui/dr-ui/ui/masks.slint:422`](../../ui/dr-ui/ui/masks.slint#L422), [`ui/dr-ui/ui/masks.slint:468`](../../ui/dr-ui/ui/masks.slint#L468), [`ui/dr-ui/ui/masks.slint:796`](../../ui/dr-ui/ui/masks.slint#L796), [`ui/dr-ui/ui/masks.slint:881`](../../ui/dr-ui/ui/masks.slint#L881) |
| FR-DEV-19b | [`ui/dr-ui/src/develop/mask_ops.rs:606`](../../ui/dr-ui/src/develop/mask_ops.rs#L606), [`ui/dr-ui/src/develop/masks.rs:595`](../../ui/dr-ui/src/develop/masks.rs#L595), [`ui/dr-ui/src/develop/session.rs:201`](../../ui/dr-ui/src/develop/session.rs#L201), [`ui/dr-ui/src/develop/session.rs:208`](../../ui/dr-ui/src/develop/session.rs#L208), [`ui/dr-ui/src/labels.rs:67`](../../ui/dr-ui/src/labels.rs#L67), [`ui/dr-ui/src/masks_ui.rs:1229`](../../ui/dr-ui/src/masks_ui.rs#L1229), [`ui/dr-ui/src/masks_ui.rs:1448`](../../ui/dr-ui/src/masks_ui.rs#L1448), [`ui/dr-ui/src/masks_ui.rs:1476`](../../ui/dr-ui/src/masks_ui.rs#L1476), [`ui/dr-ui/src/masks_ui.rs:1849`](../../ui/dr-ui/src/masks_ui.rs#L1849), [`ui/dr-ui/src/masks_ui.rs:908`](../../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/ui/app.slint:2161`](../../ui/dr-ui/ui/app.slint#L2161), [`ui/dr-ui/ui/app.slint:856`](../../ui/dr-ui/ui/app.slint#L856), [`ui/dr-ui/ui/masks.slint:1156`](../../ui/dr-ui/ui/masks.slint#L1156), [`ui/dr-ui/ui/masks.slint:479`](../../ui/dr-ui/ui/masks.slint#L479), [`ui/dr-ui/ui/masks.slint:800`](../../ui/dr-ui/ui/masks.slint#L800), [`ui/dr-ui/ui/masks.slint:874`](../../ui/dr-ui/ui/masks.slint#L874), [`ui/dr-ui/ui/masks.slint:894`](../../ui/dr-ui/ui/masks.slint#L894) |
-| FR-DEV-19c | [`core/dr-gpu/src/mask.rs:727`](../../core/dr-gpu/src/mask.rs#L727), [`core/dr-gpu/tests/local_adjustments.rs:1000`](../../core/dr-gpu/tests/local_adjustments.rs#L1000), [`core/dr-gpu/tests/local_adjustments.rs:1035`](../../core/dr-gpu/tests/local_adjustments.rs#L1035), [`core/dr-gpu/tests/local_adjustments.rs:1080`](../../core/dr-gpu/tests/local_adjustments.rs#L1080), [`core/dr-gpu/tests/local_adjustments.rs:943`](../../core/dr-gpu/tests/local_adjustments.rs#L943), [`core/dr-pipeline/src/graph.rs:899`](../../core/dr-pipeline/src/graph.rs#L899), [`core/dr-pipeline/src/mask.rs:1843`](../../core/dr-pipeline/src/mask.rs#L1843), [`core/dr-pipeline/src/mask.rs:1869`](../../core/dr-pipeline/src/mask.rs#L1869), [`core/dr-pipeline/src/mask.rs:1884`](../../core/dr-pipeline/src/mask.rs#L1884), [`core/dr-pipeline/src/mask.rs:2006`](../../core/dr-pipeline/src/mask.rs#L2006), [`core/dr-pipeline/src/mask.rs:2065`](../../core/dr-pipeline/src/mask.rs#L2065), [`core/dr-pipeline/src/mask.rs:2272`](../../core/dr-pipeline/src/mask.rs#L2272), [`core/dr-pipeline/src/mask.rs:2512`](../../core/dr-pipeline/src/mask.rs#L2512), [`core/dr-pipeline/src/operation.rs:1908`](../../core/dr-pipeline/src/operation.rs#L1908), [`core/dr-pipeline/src/operation.rs:621`](../../core/dr-pipeline/src/operation.rs#L621), [`core/dr-pipeline/src/operation.rs:932`](../../core/dr-pipeline/src/operation.rs#L932), [`ui/dr-ui/src/develop/mask_ops.rs:1199`](../../ui/dr-ui/src/develop/mask_ops.rs#L1199), [`ui/dr-ui/src/develop/mask_ops.rs:1235`](../../ui/dr-ui/src/develop/mask_ops.rs#L1235), [`ui/dr-ui/src/develop/mask_ops.rs:318`](../../ui/dr-ui/src/develop/mask_ops.rs#L318), [`ui/dr-ui/src/develop/mask_ops.rs:58`](../../ui/dr-ui/src/develop/mask_ops.rs#L58), [`ui/dr-ui/src/develop/masks.rs:12`](../../ui/dr-ui/src/develop/masks.rs#L12), [`ui/dr-ui/src/develop/masks.rs:176`](../../ui/dr-ui/src/develop/masks.rs#L176), [`ui/dr-ui/src/develop/masks.rs:21`](../../ui/dr-ui/src/develop/masks.rs#L21), [`ui/dr-ui/src/develop/masks.rs:43`](../../ui/dr-ui/src/develop/masks.rs#L43), [`ui/dr-ui/src/develop/render.rs:260`](../../ui/dr-ui/src/develop/render.rs#L260), [`ui/dr-ui/src/develop/session.rs:227`](../../ui/dr-ui/src/develop/session.rs#L227), [`ui/dr-ui/src/develop/session.rs:236`](../../ui/dr-ui/src/develop/session.rs#L236), [`ui/dr-ui/src/masks_ui.rs:1024`](../../ui/dr-ui/src/masks_ui.rs#L1024), [`ui/dr-ui/src/masks_ui.rs:1448`](../../ui/dr-ui/src/masks_ui.rs#L1448), [`ui/dr-ui/src/masks_ui.rs:152`](../../ui/dr-ui/src/masks_ui.rs#L152), [`ui/dr-ui/src/masks_ui.rs:1849`](../../ui/dr-ui/src/masks_ui.rs#L1849), [`ui/dr-ui/src/masks_ui.rs:291`](../../ui/dr-ui/src/masks_ui.rs#L291), [`ui/dr-ui/src/masks_ui.rs:317`](../../ui/dr-ui/src/masks_ui.rs#L317), [`ui/dr-ui/src/masks_ui.rs:677`](../../ui/dr-ui/src/masks_ui.rs#L677), [`ui/dr-ui/src/masks_ui.rs:996`](../../ui/dr-ui/src/masks_ui.rs#L996), [`ui/dr-ui/ui/icons.slint:340`](../../ui/dr-ui/ui/icons.slint#L340), [`ui/dr-ui/ui/masks.slint:1121`](../../ui/dr-ui/ui/masks.slint#L1121), [`ui/dr-ui/ui/masks.slint:184`](../../ui/dr-ui/ui/masks.slint#L184), [`ui/dr-ui/ui/masks.slint:294`](../../ui/dr-ui/ui/masks.slint#L294), [`ui/dr-ui/ui/masks.slint:543`](../../ui/dr-ui/ui/masks.slint#L543), [`ui/dr-ui/ui/masks.slint:70`](../../ui/dr-ui/ui/masks.slint#L70), [`ui/dr-ui/ui/masks.slint:808`](../../ui/dr-ui/ui/masks.slint#L808), [`ui/dr-ui/ui/masks.slint:876`](../../ui/dr-ui/ui/masks.slint#L876), [`ui/dr-ui/ui/masks.slint:968`](../../ui/dr-ui/ui/masks.slint#L968) |
-| FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:403`](../../core/dr-pipeline/src/operation.rs#L403) |
-| FR-DEV-20 | [`core/dr-gpu/src/adjust.rs:2177`](../../core/dr-gpu/src/adjust.rs#L2177), [`core/dr-pipeline/src/framing.rs:1362`](../../core/dr-pipeline/src/framing.rs#L1362), [`core/dr-pipeline/src/framing.rs:170`](../../core/dr-pipeline/src/framing.rs#L170), [`core/dr-pipeline/src/framing.rs:388`](../../core/dr-pipeline/src/framing.rs#L388), [`core/dr-pipeline/src/framing.rs:482`](../../core/dr-pipeline/src/framing.rs#L482), [`core/dr-pipeline/src/framing.rs:639`](../../core/dr-pipeline/src/framing.rs#L639), [`core/dr-pipeline/src/framing.rs:650`](../../core/dr-pipeline/src/framing.rs#L650), [`core/dr-pipeline/src/framing.rs:88`](../../core/dr-pipeline/src/framing.rs#L88), [`core/dr-pipeline/src/framing.rs:925`](../../core/dr-pipeline/src/framing.rs#L925), [`core/dr-pipeline/src/framing.rs:92`](../../core/dr-pipeline/src/framing.rs#L92), [`core/dr-pipeline/src/framing.rs:981`](../../core/dr-pipeline/src/framing.rs#L981), [`core/dr-pipeline/src/preset.rs:1001`](../../core/dr-pipeline/src/preset.rs#L1001), [`core/dr-pipeline/src/sidecar.rs:2195`](../../core/dr-pipeline/src/sidecar.rs#L2195), [`ui/dr-ui/src/develop/framing.rs:1035`](../../ui/dr-ui/src/develop/framing.rs#L1035), [`ui/dr-ui/src/develop/framing.rs:418`](../../ui/dr-ui/src/develop/framing.rs#L418), [`ui/dr-ui/src/develop/framing.rs:474`](../../ui/dr-ui/src/develop/framing.rs#L474), [`ui/dr-ui/src/develop/framing.rs:480`](../../ui/dr-ui/src/develop/framing.rs#L480), [`ui/dr-ui/src/develop/framing.rs:489`](../../ui/dr-ui/src/develop/framing.rs#L489), [`ui/dr-ui/src/develop_ui.rs:1301`](../../ui/dr-ui/src/develop_ui.rs#L1301), [`ui/dr-ui/src/develop_ui.rs:1334`](../../ui/dr-ui/src/develop_ui.rs#L1334), [`ui/dr-ui/src/labels.rs:277`](../../ui/dr-ui/src/labels.rs#L277), [`ui/dr-ui/ui/adjust.slint:646`](../../ui/dr-ui/ui/adjust.slint#L646), [`ui/dr-ui/ui/adjust.slint:688`](../../ui/dr-ui/ui/adjust.slint#L688), [`ui/dr-ui/ui/adjust.slint:831`](../../ui/dr-ui/ui/adjust.slint#L831), [`ui/dr-ui/ui/app.slint:2650`](../../ui/dr-ui/ui/app.slint#L2650), [`ui/dr-ui/ui/app.slint:722`](../../ui/dr-ui/ui/app.slint#L722), [`ui/dr-ui/ui/app.slint:745`](../../ui/dr-ui/ui/app.slint#L745) |
-| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/adjust.rs:2775`](../../core/dr-gpu/src/adjust.rs#L2775), [`core/dr-gpu/src/adjust.rs:84`](../../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:906`](../../core/dr-gpu/src/adjust.rs#L906), [`core/dr-gpu/tests/tone_curve.rs:1`](../../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/coverage.rs:122`](../../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/framing.rs:1098`](../../core/dr-pipeline/src/framing.rs#L1098), [`core/dr-pipeline/src/framing.rs:251`](../../core/dr-pipeline/src/framing.rs#L251), [`core/dr-pipeline/src/framing.rs:308`](../../core/dr-pipeline/src/framing.rs#L308), [`core/dr-pipeline/src/framing.rs:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`core/dr-pipeline/src/graph.rs:1354`](../../core/dr-pipeline/src/graph.rs#L1354), [`core/dr-pipeline/src/graph.rs:1385`](../../core/dr-pipeline/src/graph.rs#L1385), [`core/dr-pipeline/src/graph.rs:1408`](../../core/dr-pipeline/src/graph.rs#L1408), [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:1477`](../../core/dr-pipeline/src/graph.rs#L1477), [`core/dr-pipeline/src/graph.rs:225`](../../core/dr-pipeline/src/graph.rs#L225), [`core/dr-pipeline/src/graph.rs:306`](../../core/dr-pipeline/src/graph.rs#L306), [`core/dr-pipeline/src/graph.rs:344`](../../core/dr-pipeline/src/graph.rs#L344), [`core/dr-pipeline/src/graph.rs:350`](../../core/dr-pipeline/src/graph.rs#L350), [`core/dr-pipeline/src/graph.rs:866`](../../core/dr-pipeline/src/graph.rs#L866), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`core/dr-pipeline/src/lens.rs:93`](../../core/dr-pipeline/src/lens.rs#L93), [`core/dr-pipeline/src/mask.rs:205`](../../core/dr-pipeline/src/mask.rs#L205), [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:306`](../../core/dr-pipeline/src/neutral.rs#L306), [`core/dr-pipeline/src/neutral.rs:322`](../../core/dr-pipeline/src/neutral.rs#L322), [`core/dr-pipeline/src/neutral.rs:338`](../../core/dr-pipeline/src/neutral.rs#L338), [`core/dr-pipeline/src/neutral.rs:360`](../../core/dr-pipeline/src/neutral.rs#L360), [`core/dr-pipeline/src/neutral.rs:81`](../../core/dr-pipeline/src/neutral.rs#L81), [`core/dr-pipeline/src/operation.rs:330`](../../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:597`](../../core/dr-pipeline/src/operation.rs#L597), [`core/dr-pipeline/src/operation.rs:670`](../../core/dr-pipeline/src/operation.rs#L670), [`core/dr-pipeline/src/operation.rs:862`](../../core/dr-pipeline/src/operation.rs#L862), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/preset.rs:927`](../../core/dr-pipeline/src/preset.rs#L927), [`core/dr-pipeline/src/sidecar.rs:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:206`](../../core/dr-pipeline/src/sidecar.rs#L206), [`core/dr-pipeline/src/sidecar.rs:2273`](../../core/dr-pipeline/src/sidecar.rs#L2273), [`core/dr-pipeline/src/sidecar.rs:2353`](../../core/dr-pipeline/src/sidecar.rs#L2353), [`core/dr-pipeline/tests/tone_curve.rs:1`](../../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:937`](../../core/dr-segment/src/refine.rs#L937), [`ui/dr-ui/src/develop/curves.rs:163`](../../ui/dr-ui/src/develop/curves.rs#L163), [`ui/dr-ui/src/develop/framing.rs:10`](../../ui/dr-ui/src/develop/framing.rs#L10), [`ui/dr-ui/src/develop/framing.rs:298`](../../ui/dr-ui/src/develop/framing.rs#L298), [`ui/dr-ui/src/develop/framing.rs:328`](../../ui/dr-ui/src/develop/framing.rs#L328), [`ui/dr-ui/src/develop/framing.rs:394`](../../ui/dr-ui/src/develop/framing.rs#L394), [`ui/dr-ui/src/develop/framing.rs:408`](../../ui/dr-ui/src/develop/framing.rs#L408), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:71`](../../ui/dr-ui/src/develop/framing.rs#L71), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1340`](../../ui/dr-ui/src/develop/mask_ops.rs#L1340), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/mask_ops.rs:969`](../../ui/dr-ui/src/develop/mask_ops.rs#L969), [`ui/dr-ui/src/develop/masks.rs:263`](../../ui/dr-ui/src/develop/masks.rs#L263), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/masks.rs:773`](../../ui/dr-ui/src/develop/masks.rs#L773), [`ui/dr-ui/src/develop/render.rs:1200`](../../ui/dr-ui/src/develop/render.rs#L1200), [`ui/dr-ui/src/develop/rows.rs:106`](../../ui/dr-ui/src/develop/rows.rs#L106), [`ui/dr-ui/src/develop/rows.rs:195`](../../ui/dr-ui/src/develop/rows.rs#L195), [`ui/dr-ui/src/develop/rows.rs:231`](../../ui/dr-ui/src/develop/rows.rs#L231), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/segmentation.rs:139`](../../ui/dr-ui/src/develop/segmentation.rs#L139), [`ui/dr-ui/src/develop/segmentation.rs:302`](../../ui/dr-ui/src/develop/segmentation.rs#L302), [`ui/dr-ui/src/develop/segmentation.rs:339`](../../ui/dr-ui/src/develop/segmentation.rs#L339), [`ui/dr-ui/src/develop/segmentation.rs:572`](../../ui/dr-ui/src/develop/segmentation.rs#L572), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:604`](../../ui/dr-ui/src/develop/segmentation.rs#L604), [`ui/dr-ui/src/develop/segmentation.rs:626`](../../ui/dr-ui/src/develop/segmentation.rs#L626), [`ui/dr-ui/src/develop/segmentation.rs:77`](../../ui/dr-ui/src/develop/segmentation.rs#L77), [`ui/dr-ui/src/develop/segmentation.rs:806`](../../ui/dr-ui/src/develop/segmentation.rs#L806), [`ui/dr-ui/src/develop/session.rs:162`](../../ui/dr-ui/src/develop/session.rs#L162), [`ui/dr-ui/src/develop/session.rs:255`](../../ui/dr-ui/src/develop/session.rs#L255), [`ui/dr-ui/src/develop/session.rs:284`](../../ui/dr-ui/src/develop/session.rs#L284), [`ui/dr-ui/src/develop/session.rs:434`](../../ui/dr-ui/src/develop/session.rs#L434), [`ui/dr-ui/src/develop/session.rs:494`](../../ui/dr-ui/src/develop/session.rs#L494), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:721`](../../ui/dr-ui/src/develop/session.rs#L721), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop/white_balance.rs:193`](../../ui/dr-ui/src/develop/white_balance.rs#L193), [`ui/dr-ui/src/develop/white_balance.rs:276`](../../ui/dr-ui/src/develop/white_balance.rs#L276), [`ui/dr-ui/src/develop/white_balance.rs:338`](../../ui/dr-ui/src/develop/white_balance.rs#L338), [`ui/dr-ui/src/develop_ui.rs:1114`](../../ui/dr-ui/src/develop_ui.rs#L1114), [`ui/dr-ui/src/develop_ui.rs:117`](../../ui/dr-ui/src/develop_ui.rs#L117), [`ui/dr-ui/src/develop_ui.rs:1211`](../../ui/dr-ui/src/develop_ui.rs#L1211), [`ui/dr-ui/src/develop_ui.rs:1265`](../../ui/dr-ui/src/develop_ui.rs#L1265), [`ui/dr-ui/src/develop_ui.rs:515`](../../ui/dr-ui/src/develop_ui.rs#L515), [`ui/dr-ui/src/develop_ui.rs:735`](../../ui/dr-ui/src/develop_ui.rs#L735), [`ui/dr-ui/src/labels.rs:35`](../../ui/dr-ui/src/labels.rs#L35), [`ui/dr-ui/src/lib.rs:2150`](../../ui/dr-ui/src/lib.rs#L2150), [`ui/dr-ui/src/lib.rs:455`](../../ui/dr-ui/src/lib.rs#L455), [`ui/dr-ui/src/lib.rs:485`](../../ui/dr-ui/src/lib.rs#L485), [`ui/dr-ui/src/lib.rs:593`](../../ui/dr-ui/src/lib.rs#L593), [`ui/dr-ui/src/lib.rs:617`](../../ui/dr-ui/src/lib.rs#L617), [`ui/dr-ui/src/library/sidecar.rs:65`](../../ui/dr-ui/src/library/sidecar.rs#L65), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/masks_ui.rs:44`](../../ui/dr-ui/src/masks_ui.rs#L44), [`ui/dr-ui/src/presets.rs:414`](../../ui/dr-ui/src/presets.rs#L414), [`ui/dr-ui/src/segmentation.rs:117`](../../ui/dr-ui/src/segmentation.rs#L117), [`ui/dr-ui/src/segmentation.rs:353`](../../ui/dr-ui/src/segmentation.rs#L353), [`ui/dr-ui/src/segmentation.rs:369`](../../ui/dr-ui/src/segmentation.rs#L369), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499), [`ui/dr-ui/src/segmentation.rs:527`](../../ui/dr-ui/src/segmentation.rs#L527), [`ui/dr-ui/ui/adjust.slint:1050`](../../ui/dr-ui/ui/adjust.slint#L1050), [`ui/dr-ui/ui/adjust.slint:127`](../../ui/dr-ui/ui/adjust.slint#L127), [`ui/dr-ui/ui/adjust.slint:1658`](../../ui/dr-ui/ui/adjust.slint#L1658), [`ui/dr-ui/ui/adjust.slint:173`](../../ui/dr-ui/ui/adjust.slint#L173), [`ui/dr-ui/ui/adjust.slint:47`](../../ui/dr-ui/ui/adjust.slint#L47), [`ui/dr-ui/ui/adjust.slint:626`](../../ui/dr-ui/ui/adjust.slint#L626), [`ui/dr-ui/ui/adjust.slint:662`](../../ui/dr-ui/ui/adjust.slint#L662), [`ui/dr-ui/ui/adjust.slint:681`](../../ui/dr-ui/ui/adjust.slint#L681), [`ui/dr-ui/ui/adjust.slint:874`](../../ui/dr-ui/ui/adjust.slint#L874), [`ui/dr-ui/ui/adjust.slint:973`](../../ui/dr-ui/ui/adjust.slint#L973), [`ui/dr-ui/ui/app.slint:2226`](../../ui/dr-ui/ui/app.slint#L2226), [`ui/dr-ui/ui/app.slint:2288`](../../ui/dr-ui/ui/app.slint#L2288), [`ui/dr-ui/ui/app.slint:242`](../../ui/dr-ui/ui/app.slint#L242), [`ui/dr-ui/ui/develop.slint:379`](../../ui/dr-ui/ui/develop.slint#L379), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/masks.slint:779`](../../ui/dr-ui/ui/masks.slint#L779), [`ui/dr-ui/ui/masks.slint:838`](../../ui/dr-ui/ui/masks.slint#L838) |
-| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:212`](../../core/dr-pipeline/src/descriptor.rs#L212), [`core/dr-pipeline/src/descriptor.rs:252`](../../core/dr-pipeline/src/descriptor.rs#L252), [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/descriptor.rs:331`](../../core/dr-pipeline/src/descriptor.rs#L331), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-pipeline/src/graph.rs:25`](../../core/dr-pipeline/src/graph.rs#L25), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/graph.rs:534`](../../core/dr-pipeline/src/graph.rs#L534), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/mask.rs:1746`](../../core/dr-pipeline/src/mask.rs#L1746), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:378`](../../core/dr-pipeline/src/neutral.rs#L378), [`core/dr-pipeline/src/operation.rs:232`](../../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop/curves.rs:36`](../../ui/dr-ui/src/develop/curves.rs#L36), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/rows.rs:877`](../../ui/dr-ui/src/develop/rows.rs#L877), [`ui/dr-ui/src/lib.rs:930`](../../ui/dr-ui/src/lib.rs#L930), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../../ui/dr-ui/tests/ui_names_no_operation.rs#L1), [`ui/dr-ui/ui/app.slint:2680`](../../ui/dr-ui/ui/app.slint#L2680) |
-| FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`ui/dr-ui/ui/app.slint:2964`](../../ui/dr-ui/ui/app.slint#L2964) |
+| FR-DEV-19c | [`core/dr-gpu/src/mask.rs:727`](../../core/dr-gpu/src/mask.rs#L727), [`core/dr-gpu/tests/local_adjustments.rs:1000`](../../core/dr-gpu/tests/local_adjustments.rs#L1000), [`core/dr-gpu/tests/local_adjustments.rs:1035`](../../core/dr-gpu/tests/local_adjustments.rs#L1035), [`core/dr-gpu/tests/local_adjustments.rs:1080`](../../core/dr-gpu/tests/local_adjustments.rs#L1080), [`core/dr-gpu/tests/local_adjustments.rs:943`](../../core/dr-gpu/tests/local_adjustments.rs#L943), [`core/dr-pipeline/src/graph.rs:899`](../../core/dr-pipeline/src/graph.rs#L899), [`core/dr-pipeline/src/mask.rs:1843`](../../core/dr-pipeline/src/mask.rs#L1843), [`core/dr-pipeline/src/mask.rs:1869`](../../core/dr-pipeline/src/mask.rs#L1869), [`core/dr-pipeline/src/mask.rs:1884`](../../core/dr-pipeline/src/mask.rs#L1884), [`core/dr-pipeline/src/mask.rs:2006`](../../core/dr-pipeline/src/mask.rs#L2006), [`core/dr-pipeline/src/mask.rs:2065`](../../core/dr-pipeline/src/mask.rs#L2065), [`core/dr-pipeline/src/mask.rs:2309`](../../core/dr-pipeline/src/mask.rs#L2309), [`core/dr-pipeline/src/mask.rs:2621`](../../core/dr-pipeline/src/mask.rs#L2621), [`core/dr-pipeline/src/operation.rs:2003`](../../core/dr-pipeline/src/operation.rs#L2003), [`core/dr-pipeline/src/operation.rs:647`](../../core/dr-pipeline/src/operation.rs#L647), [`core/dr-pipeline/src/operation.rs:968`](../../core/dr-pipeline/src/operation.rs#L968), [`ui/dr-ui/src/develop/mask_ops.rs:1199`](../../ui/dr-ui/src/develop/mask_ops.rs#L1199), [`ui/dr-ui/src/develop/mask_ops.rs:1235`](../../ui/dr-ui/src/develop/mask_ops.rs#L1235), [`ui/dr-ui/src/develop/mask_ops.rs:318`](../../ui/dr-ui/src/develop/mask_ops.rs#L318), [`ui/dr-ui/src/develop/mask_ops.rs:58`](../../ui/dr-ui/src/develop/mask_ops.rs#L58), [`ui/dr-ui/src/develop/masks.rs:12`](../../ui/dr-ui/src/develop/masks.rs#L12), [`ui/dr-ui/src/develop/masks.rs:176`](../../ui/dr-ui/src/develop/masks.rs#L176), [`ui/dr-ui/src/develop/masks.rs:21`](../../ui/dr-ui/src/develop/masks.rs#L21), [`ui/dr-ui/src/develop/masks.rs:43`](../../ui/dr-ui/src/develop/masks.rs#L43), [`ui/dr-ui/src/develop/render.rs:260`](../../ui/dr-ui/src/develop/render.rs#L260), [`ui/dr-ui/src/develop/session.rs:227`](../../ui/dr-ui/src/develop/session.rs#L227), [`ui/dr-ui/src/develop/session.rs:236`](../../ui/dr-ui/src/develop/session.rs#L236), [`ui/dr-ui/src/masks_ui.rs:1024`](../../ui/dr-ui/src/masks_ui.rs#L1024), [`ui/dr-ui/src/masks_ui.rs:1448`](../../ui/dr-ui/src/masks_ui.rs#L1448), [`ui/dr-ui/src/masks_ui.rs:152`](../../ui/dr-ui/src/masks_ui.rs#L152), [`ui/dr-ui/src/masks_ui.rs:1849`](../../ui/dr-ui/src/masks_ui.rs#L1849), [`ui/dr-ui/src/masks_ui.rs:291`](../../ui/dr-ui/src/masks_ui.rs#L291), [`ui/dr-ui/src/masks_ui.rs:317`](../../ui/dr-ui/src/masks_ui.rs#L317), [`ui/dr-ui/src/masks_ui.rs:677`](../../ui/dr-ui/src/masks_ui.rs#L677), [`ui/dr-ui/src/masks_ui.rs:996`](../../ui/dr-ui/src/masks_ui.rs#L996), [`ui/dr-ui/ui/icons.slint:340`](../../ui/dr-ui/ui/icons.slint#L340), [`ui/dr-ui/ui/masks.slint:1121`](../../ui/dr-ui/ui/masks.slint#L1121), [`ui/dr-ui/ui/masks.slint:184`](../../ui/dr-ui/ui/masks.slint#L184), [`ui/dr-ui/ui/masks.slint:294`](../../ui/dr-ui/ui/masks.slint#L294), [`ui/dr-ui/ui/masks.slint:543`](../../ui/dr-ui/ui/masks.slint#L543), [`ui/dr-ui/ui/masks.slint:70`](../../ui/dr-ui/ui/masks.slint#L70), [`ui/dr-ui/ui/masks.slint:808`](../../ui/dr-ui/ui/masks.slint#L808), [`ui/dr-ui/ui/masks.slint:876`](../../ui/dr-ui/ui/masks.slint#L876), [`ui/dr-ui/ui/masks.slint:968`](../../ui/dr-ui/ui/masks.slint#L968) |
+| FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:429`](../../core/dr-pipeline/src/operation.rs#L429) |
+| FR-DEV-20 | [`core/dr-gpu/src/adjust.rs:2195`](../../core/dr-gpu/src/adjust.rs#L2195), [`core/dr-pipeline/src/framing.rs:1362`](../../core/dr-pipeline/src/framing.rs#L1362), [`core/dr-pipeline/src/framing.rs:170`](../../core/dr-pipeline/src/framing.rs#L170), [`core/dr-pipeline/src/framing.rs:388`](../../core/dr-pipeline/src/framing.rs#L388), [`core/dr-pipeline/src/framing.rs:482`](../../core/dr-pipeline/src/framing.rs#L482), [`core/dr-pipeline/src/framing.rs:639`](../../core/dr-pipeline/src/framing.rs#L639), [`core/dr-pipeline/src/framing.rs:650`](../../core/dr-pipeline/src/framing.rs#L650), [`core/dr-pipeline/src/framing.rs:88`](../../core/dr-pipeline/src/framing.rs#L88), [`core/dr-pipeline/src/framing.rs:925`](../../core/dr-pipeline/src/framing.rs#L925), [`core/dr-pipeline/src/framing.rs:92`](../../core/dr-pipeline/src/framing.rs#L92), [`core/dr-pipeline/src/framing.rs:981`](../../core/dr-pipeline/src/framing.rs#L981), [`core/dr-pipeline/src/preset.rs:1001`](../../core/dr-pipeline/src/preset.rs#L1001), [`core/dr-pipeline/src/sidecar.rs:2197`](../../core/dr-pipeline/src/sidecar.rs#L2197), [`ui/dr-ui/src/develop/framing.rs:1035`](../../ui/dr-ui/src/develop/framing.rs#L1035), [`ui/dr-ui/src/develop/framing.rs:418`](../../ui/dr-ui/src/develop/framing.rs#L418), [`ui/dr-ui/src/develop/framing.rs:474`](../../ui/dr-ui/src/develop/framing.rs#L474), [`ui/dr-ui/src/develop/framing.rs:480`](../../ui/dr-ui/src/develop/framing.rs#L480), [`ui/dr-ui/src/develop/framing.rs:489`](../../ui/dr-ui/src/develop/framing.rs#L489), [`ui/dr-ui/src/develop_ui.rs:1300`](../../ui/dr-ui/src/develop_ui.rs#L1300), [`ui/dr-ui/src/develop_ui.rs:1333`](../../ui/dr-ui/src/develop_ui.rs#L1333), [`ui/dr-ui/src/labels.rs:277`](../../ui/dr-ui/src/labels.rs#L277), [`ui/dr-ui/ui/adjust.slint:646`](../../ui/dr-ui/ui/adjust.slint#L646), [`ui/dr-ui/ui/adjust.slint:688`](../../ui/dr-ui/ui/adjust.slint#L688), [`ui/dr-ui/ui/adjust.slint:831`](../../ui/dr-ui/ui/adjust.slint#L831), [`ui/dr-ui/ui/app.slint:2650`](../../ui/dr-ui/ui/app.slint#L2650), [`ui/dr-ui/ui/app.slint:722`](../../ui/dr-ui/ui/app.slint#L722), [`ui/dr-ui/ui/app.slint:745`](../../ui/dr-ui/ui/app.slint#L745) |
+| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/adjust.rs:2795`](../../core/dr-gpu/src/adjust.rs#L2795), [`core/dr-gpu/src/adjust.rs:84`](../../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:924`](../../core/dr-gpu/src/adjust.rs#L924), [`core/dr-gpu/tests/tone_curve.rs:1`](../../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/coverage.rs:122`](../../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/framing.rs:1098`](../../core/dr-pipeline/src/framing.rs#L1098), [`core/dr-pipeline/src/framing.rs:251`](../../core/dr-pipeline/src/framing.rs#L251), [`core/dr-pipeline/src/framing.rs:308`](../../core/dr-pipeline/src/framing.rs#L308), [`core/dr-pipeline/src/framing.rs:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`core/dr-pipeline/src/graph.rs:1354`](../../core/dr-pipeline/src/graph.rs#L1354), [`core/dr-pipeline/src/graph.rs:1385`](../../core/dr-pipeline/src/graph.rs#L1385), [`core/dr-pipeline/src/graph.rs:1408`](../../core/dr-pipeline/src/graph.rs#L1408), [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:1477`](../../core/dr-pipeline/src/graph.rs#L1477), [`core/dr-pipeline/src/graph.rs:225`](../../core/dr-pipeline/src/graph.rs#L225), [`core/dr-pipeline/src/graph.rs:306`](../../core/dr-pipeline/src/graph.rs#L306), [`core/dr-pipeline/src/graph.rs:344`](../../core/dr-pipeline/src/graph.rs#L344), [`core/dr-pipeline/src/graph.rs:350`](../../core/dr-pipeline/src/graph.rs#L350), [`core/dr-pipeline/src/graph.rs:866`](../../core/dr-pipeline/src/graph.rs#L866), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`core/dr-pipeline/src/lens.rs:93`](../../core/dr-pipeline/src/lens.rs#L93), [`core/dr-pipeline/src/mask.rs:205`](../../core/dr-pipeline/src/mask.rs#L205), [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:306`](../../core/dr-pipeline/src/neutral.rs#L306), [`core/dr-pipeline/src/neutral.rs:322`](../../core/dr-pipeline/src/neutral.rs#L322), [`core/dr-pipeline/src/neutral.rs:338`](../../core/dr-pipeline/src/neutral.rs#L338), [`core/dr-pipeline/src/neutral.rs:360`](../../core/dr-pipeline/src/neutral.rs#L360), [`core/dr-pipeline/src/neutral.rs:81`](../../core/dr-pipeline/src/neutral.rs#L81), [`core/dr-pipeline/src/operation.rs:321`](../../core/dr-pipeline/src/operation.rs#L321), [`core/dr-pipeline/src/operation.rs:356`](../../core/dr-pipeline/src/operation.rs#L356), [`core/dr-pipeline/src/operation.rs:623`](../../core/dr-pipeline/src/operation.rs#L623), [`core/dr-pipeline/src/operation.rs:696`](../../core/dr-pipeline/src/operation.rs#L696), [`core/dr-pipeline/src/operation.rs:888`](../../core/dr-pipeline/src/operation.rs#L888), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/preset.rs:927`](../../core/dr-pipeline/src/preset.rs#L927), [`core/dr-pipeline/src/sidecar.rs:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:206`](../../core/dr-pipeline/src/sidecar.rs#L206), [`core/dr-pipeline/src/sidecar.rs:2275`](../../core/dr-pipeline/src/sidecar.rs#L2275), [`core/dr-pipeline/src/sidecar.rs:2355`](../../core/dr-pipeline/src/sidecar.rs#L2355), [`core/dr-pipeline/tests/tone_curve.rs:1`](../../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:937`](../../core/dr-segment/src/refine.rs#L937), [`ui/dr-ui/src/develop/curves.rs:163`](../../ui/dr-ui/src/develop/curves.rs#L163), [`ui/dr-ui/src/develop/framing.rs:10`](../../ui/dr-ui/src/develop/framing.rs#L10), [`ui/dr-ui/src/develop/framing.rs:298`](../../ui/dr-ui/src/develop/framing.rs#L298), [`ui/dr-ui/src/develop/framing.rs:328`](../../ui/dr-ui/src/develop/framing.rs#L328), [`ui/dr-ui/src/develop/framing.rs:394`](../../ui/dr-ui/src/develop/framing.rs#L394), [`ui/dr-ui/src/develop/framing.rs:408`](../../ui/dr-ui/src/develop/framing.rs#L408), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:71`](../../ui/dr-ui/src/develop/framing.rs#L71), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1340`](../../ui/dr-ui/src/develop/mask_ops.rs#L1340), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/mask_ops.rs:969`](../../ui/dr-ui/src/develop/mask_ops.rs#L969), [`ui/dr-ui/src/develop/masks.rs:263`](../../ui/dr-ui/src/develop/masks.rs#L263), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/masks.rs:773`](../../ui/dr-ui/src/develop/masks.rs#L773), [`ui/dr-ui/src/develop/render.rs:1244`](../../ui/dr-ui/src/develop/render.rs#L1244), [`ui/dr-ui/src/develop/rows.rs:106`](../../ui/dr-ui/src/develop/rows.rs#L106), [`ui/dr-ui/src/develop/rows.rs:195`](../../ui/dr-ui/src/develop/rows.rs#L195), [`ui/dr-ui/src/develop/rows.rs:231`](../../ui/dr-ui/src/develop/rows.rs#L231), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/segmentation.rs:139`](../../ui/dr-ui/src/develop/segmentation.rs#L139), [`ui/dr-ui/src/develop/segmentation.rs:302`](../../ui/dr-ui/src/develop/segmentation.rs#L302), [`ui/dr-ui/src/develop/segmentation.rs:339`](../../ui/dr-ui/src/develop/segmentation.rs#L339), [`ui/dr-ui/src/develop/segmentation.rs:572`](../../ui/dr-ui/src/develop/segmentation.rs#L572), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:604`](../../ui/dr-ui/src/develop/segmentation.rs#L604), [`ui/dr-ui/src/develop/segmentation.rs:626`](../../ui/dr-ui/src/develop/segmentation.rs#L626), [`ui/dr-ui/src/develop/segmentation.rs:77`](../../ui/dr-ui/src/develop/segmentation.rs#L77), [`ui/dr-ui/src/develop/segmentation.rs:806`](../../ui/dr-ui/src/develop/segmentation.rs#L806), [`ui/dr-ui/src/develop/session.rs:162`](../../ui/dr-ui/src/develop/session.rs#L162), [`ui/dr-ui/src/develop/session.rs:255`](../../ui/dr-ui/src/develop/session.rs#L255), [`ui/dr-ui/src/develop/session.rs:284`](../../ui/dr-ui/src/develop/session.rs#L284), [`ui/dr-ui/src/develop/session.rs:434`](../../ui/dr-ui/src/develop/session.rs#L434), [`ui/dr-ui/src/develop/session.rs:494`](../../ui/dr-ui/src/develop/session.rs#L494), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:721`](../../ui/dr-ui/src/develop/session.rs#L721), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop/white_balance.rs:193`](../../ui/dr-ui/src/develop/white_balance.rs#L193), [`ui/dr-ui/src/develop/white_balance.rs:276`](../../ui/dr-ui/src/develop/white_balance.rs#L276), [`ui/dr-ui/src/develop/white_balance.rs:338`](../../ui/dr-ui/src/develop/white_balance.rs#L338), [`ui/dr-ui/src/develop_ui.rs:1113`](../../ui/dr-ui/src/develop_ui.rs#L1113), [`ui/dr-ui/src/develop_ui.rs:117`](../../ui/dr-ui/src/develop_ui.rs#L117), [`ui/dr-ui/src/develop_ui.rs:1210`](../../ui/dr-ui/src/develop_ui.rs#L1210), [`ui/dr-ui/src/develop_ui.rs:1264`](../../ui/dr-ui/src/develop_ui.rs#L1264), [`ui/dr-ui/src/develop_ui.rs:514`](../../ui/dr-ui/src/develop_ui.rs#L514), [`ui/dr-ui/src/develop_ui.rs:734`](../../ui/dr-ui/src/develop_ui.rs#L734), [`ui/dr-ui/src/labels.rs:35`](../../ui/dr-ui/src/labels.rs#L35), [`ui/dr-ui/src/lib.rs:2150`](../../ui/dr-ui/src/lib.rs#L2150), [`ui/dr-ui/src/lib.rs:455`](../../ui/dr-ui/src/lib.rs#L455), [`ui/dr-ui/src/lib.rs:485`](../../ui/dr-ui/src/lib.rs#L485), [`ui/dr-ui/src/lib.rs:593`](../../ui/dr-ui/src/lib.rs#L593), [`ui/dr-ui/src/lib.rs:617`](../../ui/dr-ui/src/lib.rs#L617), [`ui/dr-ui/src/library/sidecar.rs:65`](../../ui/dr-ui/src/library/sidecar.rs#L65), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/masks_ui.rs:44`](../../ui/dr-ui/src/masks_ui.rs#L44), [`ui/dr-ui/src/presets.rs:414`](../../ui/dr-ui/src/presets.rs#L414), [`ui/dr-ui/src/segmentation.rs:117`](../../ui/dr-ui/src/segmentation.rs#L117), [`ui/dr-ui/src/segmentation.rs:353`](../../ui/dr-ui/src/segmentation.rs#L353), [`ui/dr-ui/src/segmentation.rs:369`](../../ui/dr-ui/src/segmentation.rs#L369), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499), [`ui/dr-ui/src/segmentation.rs:527`](../../ui/dr-ui/src/segmentation.rs#L527), [`ui/dr-ui/ui/adjust.slint:1050`](../../ui/dr-ui/ui/adjust.slint#L1050), [`ui/dr-ui/ui/adjust.slint:127`](../../ui/dr-ui/ui/adjust.slint#L127), [`ui/dr-ui/ui/adjust.slint:1658`](../../ui/dr-ui/ui/adjust.slint#L1658), [`ui/dr-ui/ui/adjust.slint:173`](../../ui/dr-ui/ui/adjust.slint#L173), [`ui/dr-ui/ui/adjust.slint:47`](../../ui/dr-ui/ui/adjust.slint#L47), [`ui/dr-ui/ui/adjust.slint:626`](../../ui/dr-ui/ui/adjust.slint#L626), [`ui/dr-ui/ui/adjust.slint:662`](../../ui/dr-ui/ui/adjust.slint#L662), [`ui/dr-ui/ui/adjust.slint:681`](../../ui/dr-ui/ui/adjust.slint#L681), [`ui/dr-ui/ui/adjust.slint:874`](../../ui/dr-ui/ui/adjust.slint#L874), [`ui/dr-ui/ui/adjust.slint:973`](../../ui/dr-ui/ui/adjust.slint#L973), [`ui/dr-ui/ui/app.slint:2226`](../../ui/dr-ui/ui/app.slint#L2226), [`ui/dr-ui/ui/app.slint:2288`](../../ui/dr-ui/ui/app.slint#L2288), [`ui/dr-ui/ui/app.slint:242`](../../ui/dr-ui/ui/app.slint#L242), [`ui/dr-ui/ui/develop.slint:379`](../../ui/dr-ui/ui/develop.slint#L379), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/masks.slint:779`](../../ui/dr-ui/ui/masks.slint#L779), [`ui/dr-ui/ui/masks.slint:838`](../../ui/dr-ui/ui/masks.slint#L838) |
+| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:212`](../../core/dr-pipeline/src/descriptor.rs#L212), [`core/dr-pipeline/src/descriptor.rs:252`](../../core/dr-pipeline/src/descriptor.rs#L252), [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/descriptor.rs:331`](../../core/dr-pipeline/src/descriptor.rs#L331), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-pipeline/src/graph.rs:25`](../../core/dr-pipeline/src/graph.rs#L25), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/graph.rs:534`](../../core/dr-pipeline/src/graph.rs#L534), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/mask.rs:1746`](../../core/dr-pipeline/src/mask.rs#L1746), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:378`](../../core/dr-pipeline/src/neutral.rs#L378), [`core/dr-pipeline/src/operation.rs:232`](../../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:391`](../../core/dr-pipeline/src/operation.rs#L391), [`core/dr-pipeline/src/ops/curve.rs:319`](../../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop/curves.rs:36`](../../ui/dr-ui/src/develop/curves.rs#L36), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/rows.rs:877`](../../ui/dr-ui/src/develop/rows.rs#L877), [`ui/dr-ui/src/lib.rs:930`](../../ui/dr-ui/src/lib.rs#L930), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../../ui/dr-ui/tests/ui_names_no_operation.rs#L1), [`ui/dr-ui/ui/app.slint:2680`](../../ui/dr-ui/ui/app.slint#L2680) |
+| FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/operation.rs:391`](../../core/dr-pipeline/src/operation.rs#L391), [`ui/dr-ui/ui/app.slint:2964`](../../ui/dr-ui/ui/app.slint#L2964) |
| FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/mask.rs:1746`](../../core/dr-pipeline/src/mask.rs#L1746), [`core/dr-pipeline/src/preset.rs:927`](../../core/dr-pipeline/src/preset.rs#L927), [`ui/dr-ui/src/develop/rows.rs:928`](../../ui/dr-ui/src/develop/rows.rs#L928) |
-| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/adjust.rs:114`](../../core/dr-gpu/src/adjust.rs#L114), [`core/dr-gpu/src/adjust.rs:1279`](../../core/dr-gpu/src/adjust.rs#L1279), [`core/dr-gpu/src/adjust.rs:1373`](../../core/dr-gpu/src/adjust.rs#L1373), [`core/dr-gpu/src/adjust.rs:84`](../../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/graph.rs:997`](../../core/dr-pipeline/src/graph.rs#L997), [`core/dr-pipeline/src/operation.rs:32`](../../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:403`](../../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:53`](../../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../../core/dr-pipeline/src/operation.rs#L71) |
-| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:127`](../../core/dr-decode/src/lib.rs#L127), [`core/dr-decode/src/lib.rs:777`](../../core/dr-decode/src/lib.rs#L777), [`core/dr-decode/src/lib.rs:817`](../../core/dr-decode/src/lib.rs#L817), [`core/dr-decode/src/profile.rs:102`](../../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:473`](../../core/dr-decode/src/profile.rs#L473), [`core/dr-decode/src/profile.rs:507`](../../core/dr-decode/src/profile.rs#L507), [`core/dr-decode/src/profile.rs:678`](../../core/dr-decode/src/profile.rs#L678), [`core/dr-gpu/src/adjust.rs:1260`](../../core/dr-gpu/src/adjust.rs#L1260), [`core/dr-gpu/src/adjust.rs:37`](../../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/demosaic.rs:142`](../../core/dr-gpu/src/demosaic.rs#L142), [`core/dr-gpu/src/demosaic.rs:86`](../../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:2145`](../../core/dr-pipeline/src/operation.rs#L2145), [`core/dr-pipeline/src/operation.rs:2226`](../../core/dr-pipeline/src/operation.rs#L2226), [`core/dr-pipeline/src/operation.rs:2251`](../../core/dr-pipeline/src/operation.rs#L2251), [`core/dr-pipeline/src/operation.rs:2266`](../../core/dr-pipeline/src/operation.rs#L2266), [`core/dr-pipeline/src/operation.rs:2290`](../../core/dr-pipeline/src/operation.rs#L2290), [`core/dr-pipeline/src/operation.rs:310`](../../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:502`](../../core/dr-pipeline/src/operation.rs#L502), [`core/dr-pipeline/src/operation.rs:512`](../../core/dr-pipeline/src/operation.rs#L512), [`core/dr-pipeline/src/operation.rs:806`](../../core/dr-pipeline/src/operation.rs#L806) |
-| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:152`](../../core/dr-gpu/src/adjust.rs#L152), [`core/dr-gpu/src/adjust.rs:408`](../../core/dr-gpu/src/adjust.rs#L408), [`core/dr-gpu/src/adjust.rs:583`](../../core/dr-gpu/src/adjust.rs#L583), [`core/dr-gpu/src/adjust.rs:713`](../../core/dr-gpu/src/adjust.rs#L713), [`core/dr-gpu/src/adjust.rs:77`](../../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:195`](../../core/dr-gpu/tests/film_sim.rs#L195), [`core/dr-gpu/tests/film_sim.rs:1`](../../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:103`](../../core/dr-pipeline/src/graph.rs#L103), [`core/dr-pipeline/src/graph.rs:168`](../../core/dr-pipeline/src/graph.rs#L168), [`core/dr-pipeline/src/graph.rs:566`](../../core/dr-pipeline/src/graph.rs#L566), [`core/dr-pipeline/src/operation.rs:1461`](../../core/dr-pipeline/src/operation.rs#L1461), [`core/dr-pipeline/src/operation.rs:1489`](../../core/dr-pipeline/src/operation.rs#L1489), [`core/dr-pipeline/src/operation.rs:1535`](../../core/dr-pipeline/src/operation.rs#L1535), [`core/dr-pipeline/src/operation.rs:2145`](../../core/dr-pipeline/src/operation.rs#L2145), [`core/dr-pipeline/src/operation.rs:296`](../../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:102`](../../core/dr-pipeline/src/ops/film_sim.rs#L102), [`core/dr-pipeline/src/ops/film_sim.rs:139`](../../core/dr-pipeline/src/ops/film_sim.rs#L139), [`core/dr-pipeline/src/ops/film_sim.rs:163`](../../core/dr-pipeline/src/ops/film_sim.rs#L163), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:341`](../../core/dr-pipeline/src/ops/film_sim.rs#L341), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:97`](../../core/dr-pipeline/src/ops/film_sim.rs#L97), [`core/dr-pipeline/src/preset.rs:1291`](../../core/dr-pipeline/src/preset.rs#L1291), [`core/dr-pipeline/src/preset.rs:1301`](../../core/dr-pipeline/src/preset.rs#L1301), [`core/dr-pipeline/src/preset.rs:1310`](../../core/dr-pipeline/src/preset.rs#L1310), [`core/dr-pipeline/src/preset.rs:1326`](../../core/dr-pipeline/src/preset.rs#L1326), [`core/dr-pipeline/src/preset.rs:1338`](../../core/dr-pipeline/src/preset.rs#L1338), [`core/dr-pipeline/src/preset.rs:1354`](../../core/dr-pipeline/src/preset.rs#L1354), [`core/dr-pipeline/src/preset.rs:1527`](../../core/dr-pipeline/src/preset.rs#L1527), [`core/dr-pipeline/src/preset.rs:1542`](../../core/dr-pipeline/src/preset.rs#L1542), [`core/dr-pipeline/src/preset.rs:248`](../../core/dr-pipeline/src/preset.rs#L248), [`core/dr-pipeline/src/preset.rs:689`](../../core/dr-pipeline/src/preset.rs#L689), [`core/dr-pipeline/src/preset.rs:778`](../../core/dr-pipeline/src/preset.rs#L778), [`core/dr-pipeline/src/sidecar.rs:1077`](../../core/dr-pipeline/src/sidecar.rs#L1077), [`core/dr-pipeline/src/sidecar.rs:133`](../../core/dr-pipeline/src/sidecar.rs#L133), [`core/dr-pipeline/src/sidecar.rs:217`](../../core/dr-pipeline/src/sidecar.rs#L217), [`core/dr-pipeline/src/sidecar.rs:2722`](../../core/dr-pipeline/src/sidecar.rs#L2722), [`core/dr-pipeline/src/sidecar.rs:2798`](../../core/dr-pipeline/src/sidecar.rs#L2798), [`core/dr-pipeline/src/sidecar.rs:595`](../../core/dr-pipeline/src/sidecar.rs#L595), [`core/dr-pipeline/src/sidecar.rs:898`](../../core/dr-pipeline/src/sidecar.rs#L898), [`core/dr-pipeline/src/state.rs:100`](../../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop/render.rs:1212`](../../ui/dr-ui/src/develop/render.rs#L1212), [`ui/dr-ui/src/develop/render.rs:684`](../../ui/dr-ui/src/develop/render.rs#L684), [`ui/dr-ui/src/develop/render.rs:701`](../../ui/dr-ui/src/develop/render.rs#L701), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:751`](../../ui/dr-ui/src/develop/render.rs#L751), [`ui/dr-ui/src/develop/render.rs:760`](../../ui/dr-ui/src/develop/render.rs#L760), [`ui/dr-ui/src/develop/render.rs:863`](../../ui/dr-ui/src/develop/render.rs#L863), [`ui/dr-ui/src/develop/render.rs:889`](../../ui/dr-ui/src/develop/render.rs#L889), [`ui/dr-ui/src/develop/session.rs:588`](../../ui/dr-ui/src/develop/session.rs#L588), [`ui/dr-ui/src/develop/session.rs:608`](../../ui/dr-ui/src/develop/session.rs#L608), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:447`](../../ui/dr-ui/src/develop_ui.rs#L447), [`ui/dr-ui/src/lib.rs:856`](../../ui/dr-ui/src/lib.rs#L856), [`ui/dr-ui/src/lib.rs:921`](../../ui/dr-ui/src/lib.rs#L921), [`ui/dr-ui/src/library/sidecar.rs:324`](../../ui/dr-ui/src/library/sidecar.rs#L324), [`ui/dr-ui/src/library/sidecar.rs:49`](../../ui/dr-ui/src/library/sidecar.rs#L49), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:595`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L595), [`ui/dr-ui/src/presets.rs:1586`](../../ui/dr-ui/src/presets.rs#L1586), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/tests/film_list_reaches_every_stock.rs:1`](../../ui/dr-ui/tests/film_list_reaches_every_stock.rs#L1), [`ui/dr-ui/ui/adjust.slint:1071`](../../ui/dr-ui/ui/adjust.slint#L1071), [`ui/dr-ui/ui/adjust.slint:1081`](../../ui/dr-ui/ui/adjust.slint#L1081), [`ui/dr-ui/ui/adjust.slint:1298`](../../ui/dr-ui/ui/adjust.slint#L1298) |
+| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/adjust.rs:114`](../../core/dr-gpu/src/adjust.rs#L114), [`core/dr-gpu/src/adjust.rs:1297`](../../core/dr-gpu/src/adjust.rs#L1297), [`core/dr-gpu/src/adjust.rs:1391`](../../core/dr-gpu/src/adjust.rs#L1391), [`core/dr-gpu/src/adjust.rs:84`](../../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/graph.rs:997`](../../core/dr-pipeline/src/graph.rs#L997), [`core/dr-pipeline/src/operation.rs:32`](../../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:429`](../../core/dr-pipeline/src/operation.rs#L429), [`core/dr-pipeline/src/operation.rs:53`](../../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../../core/dr-pipeline/src/operation.rs#L71) |
+| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:127`](../../core/dr-decode/src/lib.rs#L127), [`core/dr-decode/src/lib.rs:777`](../../core/dr-decode/src/lib.rs#L777), [`core/dr-decode/src/lib.rs:817`](../../core/dr-decode/src/lib.rs#L817), [`core/dr-decode/src/profile.rs:102`](../../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:473`](../../core/dr-decode/src/profile.rs#L473), [`core/dr-decode/src/profile.rs:507`](../../core/dr-decode/src/profile.rs#L507), [`core/dr-decode/src/profile.rs:678`](../../core/dr-decode/src/profile.rs#L678), [`core/dr-gpu/src/adjust.rs:1278`](../../core/dr-gpu/src/adjust.rs#L1278), [`core/dr-gpu/src/adjust.rs:37`](../../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/demosaic.rs:142`](../../core/dr-gpu/src/demosaic.rs#L142), [`core/dr-gpu/src/demosaic.rs:86`](../../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:2240`](../../core/dr-pipeline/src/operation.rs#L2240), [`core/dr-pipeline/src/operation.rs:2323`](../../core/dr-pipeline/src/operation.rs#L2323), [`core/dr-pipeline/src/operation.rs:2348`](../../core/dr-pipeline/src/operation.rs#L2348), [`core/dr-pipeline/src/operation.rs:2363`](../../core/dr-pipeline/src/operation.rs#L2363), [`core/dr-pipeline/src/operation.rs:2387`](../../core/dr-pipeline/src/operation.rs#L2387), [`core/dr-pipeline/src/operation.rs:336`](../../core/dr-pipeline/src/operation.rs#L336), [`core/dr-pipeline/src/operation.rs:528`](../../core/dr-pipeline/src/operation.rs#L528), [`core/dr-pipeline/src/operation.rs:538`](../../core/dr-pipeline/src/operation.rs#L538), [`core/dr-pipeline/src/operation.rs:832`](../../core/dr-pipeline/src/operation.rs#L832) |
+| FR-DEV-3f | [`core/dr-film/src/bake.rs:161`](../../core/dr-film/src/bake.rs#L161), [`core/dr-film/src/bake.rs:384`](../../core/dr-film/src/bake.rs#L384), [`core/dr-film/src/bake.rs:52`](../../core/dr-film/src/bake.rs#L52), [`core/dr-film/src/bake.rs:662`](../../core/dr-film/src/bake.rs#L662), [`core/dr-film/src/bake.rs:689`](../../core/dr-film/src/bake.rs#L689), [`core/dr-film/src/bake.rs:710`](../../core/dr-film/src/bake.rs#L710), [`core/dr-film/src/bake.rs:747`](../../core/dr-film/src/bake.rs#L747), [`core/dr-film/src/bake.rs:95`](../../core/dr-film/src/bake.rs#L95), [`core/dr-film/src/boolean_grain.rs:1`](../../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:152`](../../core/dr-gpu/src/adjust.rs#L152), [`core/dr-gpu/src/adjust.rs:422`](../../core/dr-gpu/src/adjust.rs#L422), [`core/dr-gpu/src/adjust.rs:601`](../../core/dr-gpu/src/adjust.rs#L601), [`core/dr-gpu/src/adjust.rs:731`](../../core/dr-gpu/src/adjust.rs#L731), [`core/dr-gpu/src/adjust.rs:77`](../../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:1`](../../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-gpu/tests/film_sim.rs:214`](../../core/dr-gpu/tests/film_sim.rs#L214), [`core/dr-gpu/tests/film_sim.rs:348`](../../core/dr-gpu/tests/film_sim.rs#L348), [`core/dr-gpu/tests/film_sim.rs:387`](../../core/dr-gpu/tests/film_sim.rs#L387), [`core/dr-gpu/tests/film_sim.rs:416`](../../core/dr-gpu/tests/film_sim.rs#L416), [`core/dr-gpu/tests/film_sim.rs:456`](../../core/dr-gpu/tests/film_sim.rs#L456), [`core/dr-pipeline/src/graph.rs:103`](../../core/dr-pipeline/src/graph.rs#L103), [`core/dr-pipeline/src/graph.rs:168`](../../core/dr-pipeline/src/graph.rs#L168), [`core/dr-pipeline/src/graph.rs:566`](../../core/dr-pipeline/src/graph.rs#L566), [`core/dr-pipeline/src/mask.rs:2452`](../../core/dr-pipeline/src/mask.rs#L2452), [`core/dr-pipeline/src/mask.rs:2485`](../../core/dr-pipeline/src/mask.rs#L2485), [`core/dr-pipeline/src/operation.rs:1322`](../../core/dr-pipeline/src/operation.rs#L1322), [`core/dr-pipeline/src/operation.rs:1556`](../../core/dr-pipeline/src/operation.rs#L1556), [`core/dr-pipeline/src/operation.rs:1584`](../../core/dr-pipeline/src/operation.rs#L1584), [`core/dr-pipeline/src/operation.rs:1630`](../../core/dr-pipeline/src/operation.rs#L1630), [`core/dr-pipeline/src/operation.rs:2240`](../../core/dr-pipeline/src/operation.rs#L2240), [`core/dr-pipeline/src/operation.rs:296`](../../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:321`](../../core/dr-pipeline/src/operation.rs#L321), [`core/dr-pipeline/src/operation.rs:336`](../../core/dr-pipeline/src/operation.rs#L336), [`core/dr-pipeline/src/operation.rs:936`](../../core/dr-pipeline/src/operation.rs#L936), [`core/dr-pipeline/src/ops/film_sim.rs:126`](../../core/dr-pipeline/src/ops/film_sim.rs#L126), [`core/dr-pipeline/src/ops/film_sim.rs:131`](../../core/dr-pipeline/src/ops/film_sim.rs#L131), [`core/dr-pipeline/src/ops/film_sim.rs:183`](../../core/dr-pipeline/src/ops/film_sim.rs#L183), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:230`](../../core/dr-pipeline/src/ops/film_sim.rs#L230), [`core/dr-pipeline/src/ops/film_sim.rs:320`](../../core/dr-pipeline/src/ops/film_sim.rs#L320), [`core/dr-pipeline/src/ops/film_sim.rs:433`](../../core/dr-pipeline/src/ops/film_sim.rs#L433), [`core/dr-pipeline/src/ops/film_sim.rs:45`](../../core/dr-pipeline/src/ops/film_sim.rs#L45), [`core/dr-pipeline/src/preset.rs:1293`](../../core/dr-pipeline/src/preset.rs#L1293), [`core/dr-pipeline/src/preset.rs:1303`](../../core/dr-pipeline/src/preset.rs#L1303), [`core/dr-pipeline/src/preset.rs:1312`](../../core/dr-pipeline/src/preset.rs#L1312), [`core/dr-pipeline/src/preset.rs:1328`](../../core/dr-pipeline/src/preset.rs#L1328), [`core/dr-pipeline/src/preset.rs:1340`](../../core/dr-pipeline/src/preset.rs#L1340), [`core/dr-pipeline/src/preset.rs:1356`](../../core/dr-pipeline/src/preset.rs#L1356), [`core/dr-pipeline/src/preset.rs:1529`](../../core/dr-pipeline/src/preset.rs#L1529), [`core/dr-pipeline/src/preset.rs:1544`](../../core/dr-pipeline/src/preset.rs#L1544), [`core/dr-pipeline/src/preset.rs:248`](../../core/dr-pipeline/src/preset.rs#L248), [`core/dr-pipeline/src/preset.rs:689`](../../core/dr-pipeline/src/preset.rs#L689), [`core/dr-pipeline/src/preset.rs:778`](../../core/dr-pipeline/src/preset.rs#L778), [`core/dr-pipeline/src/sidecar.rs:1077`](../../core/dr-pipeline/src/sidecar.rs#L1077), [`core/dr-pipeline/src/sidecar.rs:133`](../../core/dr-pipeline/src/sidecar.rs#L133), [`core/dr-pipeline/src/sidecar.rs:217`](../../core/dr-pipeline/src/sidecar.rs#L217), [`core/dr-pipeline/src/sidecar.rs:2724`](../../core/dr-pipeline/src/sidecar.rs#L2724), [`core/dr-pipeline/src/sidecar.rs:2800`](../../core/dr-pipeline/src/sidecar.rs#L2800), [`core/dr-pipeline/src/sidecar.rs:595`](../../core/dr-pipeline/src/sidecar.rs#L595), [`core/dr-pipeline/src/sidecar.rs:898`](../../core/dr-pipeline/src/sidecar.rs#L898), [`core/dr-pipeline/src/state.rs:100`](../../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop/render.rs:1256`](../../ui/dr-ui/src/develop/render.rs#L1256), [`ui/dr-ui/src/develop/render.rs:1296`](../../ui/dr-ui/src/develop/render.rs#L1296), [`ui/dr-ui/src/develop/render.rs:1331`](../../ui/dr-ui/src/develop/render.rs#L1331), [`ui/dr-ui/src/develop/render.rs:684`](../../ui/dr-ui/src/develop/render.rs#L684), [`ui/dr-ui/src/develop/render.rs:701`](../../ui/dr-ui/src/develop/render.rs#L701), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:751`](../../ui/dr-ui/src/develop/render.rs#L751), [`ui/dr-ui/src/develop/render.rs:788`](../../ui/dr-ui/src/develop/render.rs#L788), [`ui/dr-ui/src/develop/render.rs:799`](../../ui/dr-ui/src/develop/render.rs#L799), [`ui/dr-ui/src/develop/render.rs:856`](../../ui/dr-ui/src/develop/render.rs#L856), [`ui/dr-ui/src/develop/render.rs:907`](../../ui/dr-ui/src/develop/render.rs#L907), [`ui/dr-ui/src/develop/render.rs:933`](../../ui/dr-ui/src/develop/render.rs#L933), [`ui/dr-ui/src/develop/session.rs:588`](../../ui/dr-ui/src/develop/session.rs#L588), [`ui/dr-ui/src/develop/session.rs:608`](../../ui/dr-ui/src/develop/session.rs#L608), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:447`](../../ui/dr-ui/src/develop_ui.rs#L447), [`ui/dr-ui/src/lib.rs:856`](../../ui/dr-ui/src/lib.rs#L856), [`ui/dr-ui/src/lib.rs:921`](../../ui/dr-ui/src/lib.rs#L921), [`ui/dr-ui/src/library/sidecar.rs:324`](../../ui/dr-ui/src/library/sidecar.rs#L324), [`ui/dr-ui/src/library/sidecar.rs:49`](../../ui/dr-ui/src/library/sidecar.rs#L49), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:595`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L595), [`ui/dr-ui/src/presets.rs:1586`](../../ui/dr-ui/src/presets.rs#L1586), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/tests/film_list_reaches_every_stock.rs:1`](../../ui/dr-ui/tests/film_list_reaches_every_stock.rs#L1), [`ui/dr-ui/ui/adjust.slint:1071`](../../ui/dr-ui/ui/adjust.slint#L1071), [`ui/dr-ui/ui/adjust.slint:1081`](../../ui/dr-ui/ui/adjust.slint#L1081), [`ui/dr-ui/ui/adjust.slint:1298`](../../ui/dr-ui/ui/adjust.slint#L1298) |
| FR-DEV-3h | [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-decode/src/preview.rs:29`](../../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1477`](../../core/dr-pipeline/src/framing.rs#L1477), [`core/dr-pipeline/src/framing.rs:486`](../../core/dr-pipeline/src/framing.rs#L486), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`core/dr-types/src/lib.rs:339`](../../core/dr-types/src/lib.rs#L339), [`core/dr-types/src/lib.rs:447`](../../core/dr-types/src/lib.rs#L447), [`core/dr-types/src/lib.rs:459`](../../core/dr-types/src/lib.rs#L459), [`core/dr-types/src/lib.rs:475`](../../core/dr-types/src/lib.rs#L475), [`ui/dr-ui/src/develop/masks.rs:280`](../../ui/dr-ui/src/develop/masks.rs#L280), [`ui/dr-ui/src/develop/segmentation.rs:114`](../../ui/dr-ui/src/develop/segmentation.rs#L114), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499) |
| FR-DEV-3i | [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-segment/src/lib.rs:1`](../../core/dr-segment/src/lib.rs#L1), [`ui/dr-ui/src/segmentation.rs:1`](../../ui/dr-ui/src/segmentation.rs#L1) |
-| FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/lib.rs:368`](../../core/dr-gpu/src/lib.rs#L368), [`ui/dr-ui/ui/crop.slint:1`](../../ui/dr-ui/ui/crop.slint#L1) |
-| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`core/dr-pipeline/src/history.rs:102`](../../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/sidecar.rs:1067`](../../core/dr-pipeline/src/sidecar.rs#L1067), [`core/dr-pipeline/src/sidecar.rs:147`](../../core/dr-pipeline/src/sidecar.rs#L147), [`core/dr-pipeline/src/sidecar.rs:2513`](../../core/dr-pipeline/src/sidecar.rs#L2513), [`core/dr-pipeline/src/sidecar.rs:2549`](../../core/dr-pipeline/src/sidecar.rs#L2549), [`core/dr-pipeline/src/sidecar.rs:2561`](../../core/dr-pipeline/src/sidecar.rs#L2561), [`core/dr-pipeline/src/sidecar.rs:318`](../../core/dr-pipeline/src/sidecar.rs#L318), [`core/dr-pipeline/src/sidecar.rs:713`](../../core/dr-pipeline/src/sidecar.rs#L713), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`core/dr-pipeline/src/state.rs:1`](../../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/history.rs:11`](../../ui/dr-ui/src/develop/history.rs#L11), [`ui/dr-ui/src/develop/history.rs:152`](../../ui/dr-ui/src/develop/history.rs#L152), [`ui/dr-ui/src/develop/history.rs:165`](../../ui/dr-ui/src/develop/history.rs#L165), [`ui/dr-ui/src/develop/history.rs:178`](../../ui/dr-ui/src/develop/history.rs#L178), [`ui/dr-ui/src/develop/history.rs:190`](../../ui/dr-ui/src/develop/history.rs#L190), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:242`](../../ui/dr-ui/src/develop/history.rs#L242), [`ui/dr-ui/src/develop/history.rs:261`](../../ui/dr-ui/src/develop/history.rs#L261), [`ui/dr-ui/src/develop/history.rs:277`](../../ui/dr-ui/src/develop/history.rs#L277), [`ui/dr-ui/src/develop/history.rs:299`](../../ui/dr-ui/src/develop/history.rs#L299), [`ui/dr-ui/src/develop/history.rs:29`](../../ui/dr-ui/src/develop/history.rs#L29), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/history.rs:68`](../../ui/dr-ui/src/develop/history.rs#L68), [`ui/dr-ui/src/develop/history.rs:82`](../../ui/dr-ui/src/develop/history.rs#L82), [`ui/dr-ui/src/develop/history.rs:93`](../../ui/dr-ui/src/develop/history.rs#L93), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:889`](../../ui/dr-ui/src/develop/render.rs#L889), [`ui/dr-ui/src/develop/render.rs:925`](../../ui/dr-ui/src/develop/render.rs#L925), [`ui/dr-ui/src/develop/session.rs:80`](../../ui/dr-ui/src/develop/session.rs#L80), [`ui/dr-ui/src/develop/session.rs:89`](../../ui/dr-ui/src/develop/session.rs#L89), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop_ui.rs:766`](../../ui/dr-ui/src/develop_ui.rs#L766), [`ui/dr-ui/src/develop_ui.rs:786`](../../ui/dr-ui/src/develop_ui.rs#L786), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:284`](../../ui/dr-ui/src/labels.rs#L284), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:2076`](../../ui/dr-ui/src/lib.rs#L2076), [`ui/dr-ui/src/lib.rs:2081`](../../ui/dr-ui/src/lib.rs#L2081), [`ui/dr-ui/src/lib.rs:2113`](../../ui/dr-ui/src/lib.rs#L2113), [`ui/dr-ui/src/lib.rs:2122`](../../ui/dr-ui/src/lib.rs#L2122), [`ui/dr-ui/src/lib.rs:2139`](../../ui/dr-ui/src/lib.rs#L2139), [`ui/dr-ui/src/library/sidecar.rs:350`](../../ui/dr-ui/src/library/sidecar.rs#L350), [`ui/dr-ui/src/library/sidecar.rs:60`](../../ui/dr-ui/src/library/sidecar.rs#L60), [`ui/dr-ui/src/presets.rs:1770`](../../ui/dr-ui/src/presets.rs#L1770), [`ui/dr-ui/src/presets.rs:373`](../../ui/dr-ui/src/presets.rs#L373), [`ui/dr-ui/src/presets.rs:437`](../../ui/dr-ui/src/presets.rs#L437), [`ui/dr-ui/src/presets.rs:557`](../../ui/dr-ui/src/presets.rs#L557), [`ui/dr-ui/ui/history.slint:192`](../../ui/dr-ui/ui/history.slint#L192), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:219`](../../ui/dr-ui/ui/history.slint#L219), [`ui/dr-ui/ui/history.slint:304`](../../ui/dr-ui/ui/history.slint#L304), [`ui/dr-ui/ui/history.slint:43`](../../ui/dr-ui/ui/history.slint#L43), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53) |
-| FR-DEV-6 | [`core/dr-pipeline/src/bundled.rs:1`](../../core/dr-pipeline/src/bundled.rs#L1), [`core/dr-pipeline/src/preset.rs:1291`](../../core/dr-pipeline/src/preset.rs#L1291), [`core/dr-pipeline/src/preset.rs:1301`](../../core/dr-pipeline/src/preset.rs#L1301), [`core/dr-pipeline/src/preset.rs:1310`](../../core/dr-pipeline/src/preset.rs#L1310), [`core/dr-pipeline/src/preset.rs:1326`](../../core/dr-pipeline/src/preset.rs#L1326), [`core/dr-pipeline/src/preset.rs:1338`](../../core/dr-pipeline/src/preset.rs#L1338), [`core/dr-pipeline/src/preset.rs:1354`](../../core/dr-pipeline/src/preset.rs#L1354), [`core/dr-pipeline/src/preset.rs:1380`](../../core/dr-pipeline/src/preset.rs#L1380), [`core/dr-pipeline/src/preset.rs:1402`](../../core/dr-pipeline/src/preset.rs#L1402), [`core/dr-pipeline/src/preset.rs:1416`](../../core/dr-pipeline/src/preset.rs#L1416), [`core/dr-pipeline/src/preset.rs:1434`](../../core/dr-pipeline/src/preset.rs#L1434), [`core/dr-pipeline/src/preset.rs:1527`](../../core/dr-pipeline/src/preset.rs#L1527), [`core/dr-pipeline/src/preset.rs:1542`](../../core/dr-pipeline/src/preset.rs#L1542), [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:501`](../../core/dr-pipeline/src/preset.rs#L501), [`core/dr-pipeline/src/preset.rs:534`](../../core/dr-pipeline/src/preset.rs#L534), [`core/dr-preset-xmp/src/lib.rs:1`](../../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-preset-xmp/src/lib.rs:492`](../../core/dr-preset-xmp/src/lib.rs#L492), [`core/dr-types/src/settings.rs:443`](../../core/dr-types/src/settings.rs#L443), [`ui/dr-ui/src/develop/session.rs:560`](../../ui/dr-ui/src/develop/session.rs#L560), [`ui/dr-ui/src/develop/session.rs:581`](../../ui/dr-ui/src/develop/session.rs#L581), [`ui/dr-ui/src/develop_ui.rs:167`](../../ui/dr-ui/src/develop_ui.rs#L167), [`ui/dr-ui/src/develop_ui.rs:196`](../../ui/dr-ui/src/develop_ui.rs#L196), [`ui/dr-ui/src/lib.rs:1257`](../../ui/dr-ui/src/lib.rs#L1257), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:39`](../../ui/dr-ui/src/library/sidecar.rs#L39), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui/controller.rs:677`](../../ui/dr-ui/src/library_ui/controller.rs#L677), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:488`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L488), [`ui/dr-ui/src/library_ui/window.rs:678`](../../ui/dr-ui/src/library_ui/window.rs#L678), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1586`](../../ui/dr-ui/src/presets.rs#L1586), [`ui/dr-ui/src/presets.rs:1`](../../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:707`](../../ui/dr-ui/src/presets.rs#L707), [`ui/dr-ui/src/presets.rs:741`](../../ui/dr-ui/src/presets.rs#L741), [`ui/dr-ui/ui/adjust.slint:937`](../../ui/dr-ui/ui/adjust.slint#L937), [`ui/dr-ui/ui/adjust.slint:962`](../../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1089`](../../ui/dr-ui/ui/app.slint#L1089), [`ui/dr-ui/ui/app.slint:1509`](../../ui/dr-ui/ui/app.slint#L1509), [`ui/dr-ui/ui/app.slint:2482`](../../ui/dr-ui/ui/app.slint#L2482), [`ui/dr-ui/ui/app.slint:3420`](../../ui/dr-ui/ui/app.slint#L3420), [`ui/dr-ui/ui/app.slint:3465`](../../ui/dr-ui/ui/app.slint#L3465), [`ui/dr-ui/ui/app.slint:525`](../../ui/dr-ui/ui/app.slint#L525), [`ui/dr-ui/ui/app.slint:536`](../../ui/dr-ui/ui/app.slint#L536), [`ui/dr-ui/ui/app.slint:693`](../../ui/dr-ui/ui/app.slint#L693), [`ui/dr-ui/ui/develop.slint:257`](../../ui/dr-ui/ui/develop.slint#L257), [`ui/dr-ui/ui/library.slint:2000`](../../ui/dr-ui/ui/library.slint#L2000), [`ui/dr-ui/ui/library.slint:2006`](../../ui/dr-ui/ui/library.slint#L2006), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:5132`](../../ui/dr-ui/ui/library.slint#L5132), [`ui/dr-ui/ui/library.slint:5150`](../../ui/dr-ui/ui/library.slint#L5150), [`ui/dr-ui/ui/presets.slint:132`](../../ui/dr-ui/ui/presets.slint#L132), [`ui/dr-ui/ui/presets.slint:140`](../../ui/dr-ui/ui/presets.slint#L140), [`ui/dr-ui/ui/presets.slint:20`](../../ui/dr-ui/ui/presets.slint#L20), [`ui/dr-ui/ui/presets.slint:310`](../../ui/dr-ui/ui/presets.slint#L310), [`ui/dr-ui/ui/presets.slint:32`](../../ui/dr-ui/ui/presets.slint#L32), [`ui/dr-ui/ui/presets.slint:348`](../../ui/dr-ui/ui/presets.slint#L348), [`ui/dr-ui/ui/presets.slint:383`](../../ui/dr-ui/ui/presets.slint#L383), [`ui/dr-ui/ui/presets.slint:45`](../../ui/dr-ui/ui/presets.slint#L45), [`ui/dr-ui/ui/presets.slint:6`](../../ui/dr-ui/ui/presets.slint#L6), [`ui/dr-ui/ui/presets.slint:86`](../../ui/dr-ui/ui/presets.slint#L86), [`ui/dr-ui/ui/settings.slint:126`](../../ui/dr-ui/ui/settings.slint#L126), [`ui/dr-ui/ui/settings.slint:690`](../../ui/dr-ui/ui/settings.slint#L690) |
-| FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop/history.rs:107`](../../ui/dr-ui/src/develop/history.rs#L107), [`ui/dr-ui/src/develop/history.rs:124`](../../ui/dr-ui/src/develop/history.rs#L124), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/render.rs:493`](../../ui/dr-ui/src/develop/render.rs#L493), [`ui/dr-ui/src/develop/render.rs:534`](../../ui/dr-ui/src/develop/render.rs#L534), [`ui/dr-ui/src/develop/render.rs:925`](../../ui/dr-ui/src/develop/render.rs#L925), [`ui/dr-ui/src/develop/session.rs:105`](../../ui/dr-ui/src/develop/session.rs#L105), [`ui/dr-ui/src/develop_ui.rs:707`](../../ui/dr-ui/src/develop_ui.rs#L707), [`ui/dr-ui/src/develop_ui.rs:766`](../../ui/dr-ui/src/develop_ui.rs#L766), [`ui/dr-ui/src/develop_ui.rs:838`](../../ui/dr-ui/src/develop_ui.rs#L838), [`ui/dr-ui/src/lib.rs:2122`](../../ui/dr-ui/src/lib.rs#L2122), [`ui/dr-ui/src/lib.rs:2199`](../../ui/dr-ui/src/lib.rs#L2199), [`ui/dr-ui/src/lib.rs:2213`](../../ui/dr-ui/src/lib.rs#L2213), [`ui/dr-ui/src/lib.rs:447`](../../ui/dr-ui/src/lib.rs#L447), [`ui/dr-ui/ui/app.slint:224`](../../ui/dr-ui/ui/app.slint#L224), [`ui/dr-ui/ui/app.slint:2349`](../../ui/dr-ui/ui/app.slint#L2349), [`ui/dr-ui/ui/app.slint:2395`](../../ui/dr-ui/ui/app.slint#L2395), [`ui/dr-ui/ui/app.slint:2964`](../../ui/dr-ui/ui/app.slint#L2964), [`ui/dr-ui/ui/history.slint:103`](../../ui/dr-ui/ui/history.slint#L103), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:227`](../../ui/dr-ui/ui/history.slint#L227), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) |
-| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:487`](../../core/dr-pipeline/src/detail.rs#L487), [`core/dr-pipeline/src/detail.rs:573`](../../core/dr-pipeline/src/detail.rs#L573), [`core/dr-pipeline/src/graph.rs:1098`](../../core/dr-pipeline/src/graph.rs#L1098), [`core/dr-pipeline/src/graph.rs:115`](../../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:247`](../../core/dr-pipeline/src/graph.rs#L247), [`core/dr-pipeline/src/operation.rs:330`](../../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:750`](../../core/dr-pipeline/src/operation.rs#L750), [`core/dr-pipeline/src/sidecar.rs:1104`](../../core/dr-pipeline/src/sidecar.rs#L1104), [`core/dr-pipeline/src/sidecar.rs:1158`](../../core/dr-pipeline/src/sidecar.rs#L1158), [`core/dr-pipeline/src/sidecar.rs:1188`](../../core/dr-pipeline/src/sidecar.rs#L1188), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:910`](../../core/dr-pipeline/src/sidecar.rs#L910), [`core/dr-pipeline/src/spot.rs:115`](../../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop/repairs.rs:114`](../../ui/dr-ui/src/develop/repairs.rs#L114), [`ui/dr-ui/src/develop/repairs.rs:11`](../../ui/dr-ui/src/develop/repairs.rs#L11), [`ui/dr-ui/src/develop/repairs.rs:197`](../../ui/dr-ui/src/develop/repairs.rs#L197), [`ui/dr-ui/src/develop/repairs.rs:211`](../../ui/dr-ui/src/develop/repairs.rs#L211), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/develop/session.rs:216`](../../ui/dr-ui/src/develop/session.rs#L216), [`ui/dr-ui/src/develop_ui.rs:993`](../../ui/dr-ui/src/develop_ui.rs#L993), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2157`](../../ui/dr-ui/src/lib.rs#L2157), [`ui/dr-ui/src/lib.rs:490`](../../ui/dr-ui/src/lib.rs#L490), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/app.slint:2202`](../../ui/dr-ui/ui/app.slint#L2202), [`ui/dr-ui/ui/app.slint:2445`](../../ui/dr-ui/ui/app.slint#L2445), [`ui/dr-ui/ui/app.slint:3313`](../../ui/dr-ui/ui/app.slint#L3313), [`ui/dr-ui/ui/icons.slint:378`](../../ui/dr-ui/ui/icons.slint#L378), [`ui/dr-ui/ui/session.slint:102`](../../ui/dr-ui/ui/session.slint#L102), [`ui/dr-ui/ui/session.slint:52`](../../ui/dr-ui/ui/session.slint#L52), [`ui/dr-ui/ui/spots.slint:191`](../../ui/dr-ui/ui/spots.slint#L191), [`ui/dr-ui/ui/spots.slint:49`](../../ui/dr-ui/ui/spots.slint#L49), [`ui/dr-ui/ui/spots.slint:6`](../../ui/dr-ui/ui/spots.slint#L6), [`ui/dr-ui/ui/spots.slint:91`](../../ui/dr-ui/ui/spots.slint#L91) |
-| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/adjust.rs:2698`](../../core/dr-gpu/src/adjust.rs#L2698), [`core/dr-gpu/src/adjust.rs:2775`](../../core/dr-gpu/src/adjust.rs#L2775), [`core/dr-gpu/src/adjust.rs:2860`](../../core/dr-gpu/src/adjust.rs#L2860), [`core/dr-gpu/src/adjust.rs:54`](../../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/lib.rs:174`](../../core/dr-gpu/src/lib.rs#L174), [`core/dr-gpu/src/lib.rs:194`](../../core/dr-gpu/src/lib.rs#L194), [`core/dr-gpu/src/lib.rs:72`](../../core/dr-gpu/src/lib.rs#L72), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/graph.rs:928`](../../core/dr-pipeline/src/graph.rs#L928), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:565`](../../core/dr-pipeline/src/ops/dehaze.rs#L565), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:1016`](../../ui/dr-ui/src/develop/render.rs#L1016), [`ui/dr-ui/src/develop/render.rs:1154`](../../ui/dr-ui/src/develop/render.rs#L1154), [`ui/dr-ui/src/develop/render.rs:217`](../../ui/dr-ui/src/develop/render.rs#L217), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop/render.rs:982`](../../ui/dr-ui/src/develop/render.rs#L982), [`ui/dr-ui/src/lib.rs:1077`](../../ui/dr-ui/src/lib.rs#L1077), [`ui/dr-ui/src/lib.rs:115`](../../ui/dr-ui/src/lib.rs#L115) |
+| FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/lib.rs:368`](../../core/dr-gpu/src/lib.rs#L368), [`ui/dr-ui/ui/crop.slint:1`](../../ui/dr-ui/ui/crop.slint#L1) |
+| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`core/dr-pipeline/src/history.rs:102`](../../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/sidecar.rs:1067`](../../core/dr-pipeline/src/sidecar.rs#L1067), [`core/dr-pipeline/src/sidecar.rs:147`](../../core/dr-pipeline/src/sidecar.rs#L147), [`core/dr-pipeline/src/sidecar.rs:2515`](../../core/dr-pipeline/src/sidecar.rs#L2515), [`core/dr-pipeline/src/sidecar.rs:2551`](../../core/dr-pipeline/src/sidecar.rs#L2551), [`core/dr-pipeline/src/sidecar.rs:2563`](../../core/dr-pipeline/src/sidecar.rs#L2563), [`core/dr-pipeline/src/sidecar.rs:318`](../../core/dr-pipeline/src/sidecar.rs#L318), [`core/dr-pipeline/src/sidecar.rs:713`](../../core/dr-pipeline/src/sidecar.rs#L713), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`core/dr-pipeline/src/state.rs:1`](../../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/history.rs:11`](../../ui/dr-ui/src/develop/history.rs#L11), [`ui/dr-ui/src/develop/history.rs:152`](../../ui/dr-ui/src/develop/history.rs#L152), [`ui/dr-ui/src/develop/history.rs:165`](../../ui/dr-ui/src/develop/history.rs#L165), [`ui/dr-ui/src/develop/history.rs:178`](../../ui/dr-ui/src/develop/history.rs#L178), [`ui/dr-ui/src/develop/history.rs:190`](../../ui/dr-ui/src/develop/history.rs#L190), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:242`](../../ui/dr-ui/src/develop/history.rs#L242), [`ui/dr-ui/src/develop/history.rs:261`](../../ui/dr-ui/src/develop/history.rs#L261), [`ui/dr-ui/src/develop/history.rs:277`](../../ui/dr-ui/src/develop/history.rs#L277), [`ui/dr-ui/src/develop/history.rs:299`](../../ui/dr-ui/src/develop/history.rs#L299), [`ui/dr-ui/src/develop/history.rs:29`](../../ui/dr-ui/src/develop/history.rs#L29), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/history.rs:68`](../../ui/dr-ui/src/develop/history.rs#L68), [`ui/dr-ui/src/develop/history.rs:82`](../../ui/dr-ui/src/develop/history.rs#L82), [`ui/dr-ui/src/develop/history.rs:93`](../../ui/dr-ui/src/develop/history.rs#L93), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:933`](../../ui/dr-ui/src/develop/render.rs#L933), [`ui/dr-ui/src/develop/render.rs:969`](../../ui/dr-ui/src/develop/render.rs#L969), [`ui/dr-ui/src/develop/session.rs:80`](../../ui/dr-ui/src/develop/session.rs#L80), [`ui/dr-ui/src/develop/session.rs:89`](../../ui/dr-ui/src/develop/session.rs#L89), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop_ui.rs:765`](../../ui/dr-ui/src/develop_ui.rs#L765), [`ui/dr-ui/src/develop_ui.rs:785`](../../ui/dr-ui/src/develop_ui.rs#L785), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:284`](../../ui/dr-ui/src/labels.rs#L284), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:2076`](../../ui/dr-ui/src/lib.rs#L2076), [`ui/dr-ui/src/lib.rs:2081`](../../ui/dr-ui/src/lib.rs#L2081), [`ui/dr-ui/src/lib.rs:2113`](../../ui/dr-ui/src/lib.rs#L2113), [`ui/dr-ui/src/lib.rs:2122`](../../ui/dr-ui/src/lib.rs#L2122), [`ui/dr-ui/src/lib.rs:2139`](../../ui/dr-ui/src/lib.rs#L2139), [`ui/dr-ui/src/library/sidecar.rs:350`](../../ui/dr-ui/src/library/sidecar.rs#L350), [`ui/dr-ui/src/library/sidecar.rs:60`](../../ui/dr-ui/src/library/sidecar.rs#L60), [`ui/dr-ui/src/presets.rs:1770`](../../ui/dr-ui/src/presets.rs#L1770), [`ui/dr-ui/src/presets.rs:373`](../../ui/dr-ui/src/presets.rs#L373), [`ui/dr-ui/src/presets.rs:437`](../../ui/dr-ui/src/presets.rs#L437), [`ui/dr-ui/src/presets.rs:557`](../../ui/dr-ui/src/presets.rs#L557), [`ui/dr-ui/ui/history.slint:192`](../../ui/dr-ui/ui/history.slint#L192), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:219`](../../ui/dr-ui/ui/history.slint#L219), [`ui/dr-ui/ui/history.slint:304`](../../ui/dr-ui/ui/history.slint#L304), [`ui/dr-ui/ui/history.slint:43`](../../ui/dr-ui/ui/history.slint#L43), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53) |
+| FR-DEV-6 | [`core/dr-pipeline/src/bundled.rs:1`](../../core/dr-pipeline/src/bundled.rs#L1), [`core/dr-pipeline/src/preset.rs:1293`](../../core/dr-pipeline/src/preset.rs#L1293), [`core/dr-pipeline/src/preset.rs:1303`](../../core/dr-pipeline/src/preset.rs#L1303), [`core/dr-pipeline/src/preset.rs:1312`](../../core/dr-pipeline/src/preset.rs#L1312), [`core/dr-pipeline/src/preset.rs:1328`](../../core/dr-pipeline/src/preset.rs#L1328), [`core/dr-pipeline/src/preset.rs:1340`](../../core/dr-pipeline/src/preset.rs#L1340), [`core/dr-pipeline/src/preset.rs:1356`](../../core/dr-pipeline/src/preset.rs#L1356), [`core/dr-pipeline/src/preset.rs:1382`](../../core/dr-pipeline/src/preset.rs#L1382), [`core/dr-pipeline/src/preset.rs:1404`](../../core/dr-pipeline/src/preset.rs#L1404), [`core/dr-pipeline/src/preset.rs:1418`](../../core/dr-pipeline/src/preset.rs#L1418), [`core/dr-pipeline/src/preset.rs:1436`](../../core/dr-pipeline/src/preset.rs#L1436), [`core/dr-pipeline/src/preset.rs:1529`](../../core/dr-pipeline/src/preset.rs#L1529), [`core/dr-pipeline/src/preset.rs:1544`](../../core/dr-pipeline/src/preset.rs#L1544), [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:501`](../../core/dr-pipeline/src/preset.rs#L501), [`core/dr-pipeline/src/preset.rs:534`](../../core/dr-pipeline/src/preset.rs#L534), [`core/dr-preset-xmp/src/lib.rs:1`](../../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-preset-xmp/src/lib.rs:492`](../../core/dr-preset-xmp/src/lib.rs#L492), [`core/dr-types/src/settings.rs:443`](../../core/dr-types/src/settings.rs#L443), [`ui/dr-ui/src/develop/session.rs:560`](../../ui/dr-ui/src/develop/session.rs#L560), [`ui/dr-ui/src/develop/session.rs:581`](../../ui/dr-ui/src/develop/session.rs#L581), [`ui/dr-ui/src/develop_ui.rs:167`](../../ui/dr-ui/src/develop_ui.rs#L167), [`ui/dr-ui/src/develop_ui.rs:196`](../../ui/dr-ui/src/develop_ui.rs#L196), [`ui/dr-ui/src/lib.rs:1257`](../../ui/dr-ui/src/lib.rs#L1257), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:39`](../../ui/dr-ui/src/library/sidecar.rs#L39), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui/controller.rs:677`](../../ui/dr-ui/src/library_ui/controller.rs#L677), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:488`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L488), [`ui/dr-ui/src/library_ui/window.rs:678`](../../ui/dr-ui/src/library_ui/window.rs#L678), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1586`](../../ui/dr-ui/src/presets.rs#L1586), [`ui/dr-ui/src/presets.rs:1`](../../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:707`](../../ui/dr-ui/src/presets.rs#L707), [`ui/dr-ui/src/presets.rs:741`](../../ui/dr-ui/src/presets.rs#L741), [`ui/dr-ui/ui/adjust.slint:937`](../../ui/dr-ui/ui/adjust.slint#L937), [`ui/dr-ui/ui/adjust.slint:962`](../../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1089`](../../ui/dr-ui/ui/app.slint#L1089), [`ui/dr-ui/ui/app.slint:1509`](../../ui/dr-ui/ui/app.slint#L1509), [`ui/dr-ui/ui/app.slint:2482`](../../ui/dr-ui/ui/app.slint#L2482), [`ui/dr-ui/ui/app.slint:3420`](../../ui/dr-ui/ui/app.slint#L3420), [`ui/dr-ui/ui/app.slint:3465`](../../ui/dr-ui/ui/app.slint#L3465), [`ui/dr-ui/ui/app.slint:525`](../../ui/dr-ui/ui/app.slint#L525), [`ui/dr-ui/ui/app.slint:536`](../../ui/dr-ui/ui/app.slint#L536), [`ui/dr-ui/ui/app.slint:693`](../../ui/dr-ui/ui/app.slint#L693), [`ui/dr-ui/ui/develop.slint:257`](../../ui/dr-ui/ui/develop.slint#L257), [`ui/dr-ui/ui/library.slint:2000`](../../ui/dr-ui/ui/library.slint#L2000), [`ui/dr-ui/ui/library.slint:2006`](../../ui/dr-ui/ui/library.slint#L2006), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:5132`](../../ui/dr-ui/ui/library.slint#L5132), [`ui/dr-ui/ui/library.slint:5150`](../../ui/dr-ui/ui/library.slint#L5150), [`ui/dr-ui/ui/presets.slint:132`](../../ui/dr-ui/ui/presets.slint#L132), [`ui/dr-ui/ui/presets.slint:140`](../../ui/dr-ui/ui/presets.slint#L140), [`ui/dr-ui/ui/presets.slint:20`](../../ui/dr-ui/ui/presets.slint#L20), [`ui/dr-ui/ui/presets.slint:310`](../../ui/dr-ui/ui/presets.slint#L310), [`ui/dr-ui/ui/presets.slint:32`](../../ui/dr-ui/ui/presets.slint#L32), [`ui/dr-ui/ui/presets.slint:348`](../../ui/dr-ui/ui/presets.slint#L348), [`ui/dr-ui/ui/presets.slint:383`](../../ui/dr-ui/ui/presets.slint#L383), [`ui/dr-ui/ui/presets.slint:45`](../../ui/dr-ui/ui/presets.slint#L45), [`ui/dr-ui/ui/presets.slint:6`](../../ui/dr-ui/ui/presets.slint#L6), [`ui/dr-ui/ui/presets.slint:86`](../../ui/dr-ui/ui/presets.slint#L86), [`ui/dr-ui/ui/settings.slint:126`](../../ui/dr-ui/ui/settings.slint#L126), [`ui/dr-ui/ui/settings.slint:690`](../../ui/dr-ui/ui/settings.slint#L690) |
+| FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop/history.rs:107`](../../ui/dr-ui/src/develop/history.rs#L107), [`ui/dr-ui/src/develop/history.rs:124`](../../ui/dr-ui/src/develop/history.rs#L124), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/render.rs:493`](../../ui/dr-ui/src/develop/render.rs#L493), [`ui/dr-ui/src/develop/render.rs:534`](../../ui/dr-ui/src/develop/render.rs#L534), [`ui/dr-ui/src/develop/render.rs:969`](../../ui/dr-ui/src/develop/render.rs#L969), [`ui/dr-ui/src/develop/session.rs:105`](../../ui/dr-ui/src/develop/session.rs#L105), [`ui/dr-ui/src/develop_ui.rs:706`](../../ui/dr-ui/src/develop_ui.rs#L706), [`ui/dr-ui/src/develop_ui.rs:765`](../../ui/dr-ui/src/develop_ui.rs#L765), [`ui/dr-ui/src/develop_ui.rs:837`](../../ui/dr-ui/src/develop_ui.rs#L837), [`ui/dr-ui/src/lib.rs:2122`](../../ui/dr-ui/src/lib.rs#L2122), [`ui/dr-ui/src/lib.rs:2199`](../../ui/dr-ui/src/lib.rs#L2199), [`ui/dr-ui/src/lib.rs:2213`](../../ui/dr-ui/src/lib.rs#L2213), [`ui/dr-ui/src/lib.rs:447`](../../ui/dr-ui/src/lib.rs#L447), [`ui/dr-ui/ui/app.slint:224`](../../ui/dr-ui/ui/app.slint#L224), [`ui/dr-ui/ui/app.slint:2349`](../../ui/dr-ui/ui/app.slint#L2349), [`ui/dr-ui/ui/app.slint:2395`](../../ui/dr-ui/ui/app.slint#L2395), [`ui/dr-ui/ui/app.slint:2964`](../../ui/dr-ui/ui/app.slint#L2964), [`ui/dr-ui/ui/history.slint:103`](../../ui/dr-ui/ui/history.slint#L103), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:227`](../../ui/dr-ui/ui/history.slint#L227), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) |
+| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:487`](../../core/dr-pipeline/src/detail.rs#L487), [`core/dr-pipeline/src/detail.rs:573`](../../core/dr-pipeline/src/detail.rs#L573), [`core/dr-pipeline/src/graph.rs:1098`](../../core/dr-pipeline/src/graph.rs#L1098), [`core/dr-pipeline/src/graph.rs:115`](../../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:247`](../../core/dr-pipeline/src/graph.rs#L247), [`core/dr-pipeline/src/operation.rs:356`](../../core/dr-pipeline/src/operation.rs#L356), [`core/dr-pipeline/src/operation.rs:776`](../../core/dr-pipeline/src/operation.rs#L776), [`core/dr-pipeline/src/sidecar.rs:1104`](../../core/dr-pipeline/src/sidecar.rs#L1104), [`core/dr-pipeline/src/sidecar.rs:1158`](../../core/dr-pipeline/src/sidecar.rs#L1158), [`core/dr-pipeline/src/sidecar.rs:1188`](../../core/dr-pipeline/src/sidecar.rs#L1188), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:910`](../../core/dr-pipeline/src/sidecar.rs#L910), [`core/dr-pipeline/src/spot.rs:115`](../../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop/repairs.rs:114`](../../ui/dr-ui/src/develop/repairs.rs#L114), [`ui/dr-ui/src/develop/repairs.rs:11`](../../ui/dr-ui/src/develop/repairs.rs#L11), [`ui/dr-ui/src/develop/repairs.rs:197`](../../ui/dr-ui/src/develop/repairs.rs#L197), [`ui/dr-ui/src/develop/repairs.rs:211`](../../ui/dr-ui/src/develop/repairs.rs#L211), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/develop/session.rs:216`](../../ui/dr-ui/src/develop/session.rs#L216), [`ui/dr-ui/src/develop_ui.rs:992`](../../ui/dr-ui/src/develop_ui.rs#L992), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2157`](../../ui/dr-ui/src/lib.rs#L2157), [`ui/dr-ui/src/lib.rs:490`](../../ui/dr-ui/src/lib.rs#L490), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/app.slint:2202`](../../ui/dr-ui/ui/app.slint#L2202), [`ui/dr-ui/ui/app.slint:2445`](../../ui/dr-ui/ui/app.slint#L2445), [`ui/dr-ui/ui/app.slint:3313`](../../ui/dr-ui/ui/app.slint#L3313), [`ui/dr-ui/ui/icons.slint:378`](../../ui/dr-ui/ui/icons.slint#L378), [`ui/dr-ui/ui/session.slint:102`](../../ui/dr-ui/ui/session.slint#L102), [`ui/dr-ui/ui/session.slint:52`](../../ui/dr-ui/ui/session.slint#L52), [`ui/dr-ui/ui/spots.slint:191`](../../ui/dr-ui/ui/spots.slint#L191), [`ui/dr-ui/ui/spots.slint:49`](../../ui/dr-ui/ui/spots.slint#L49), [`ui/dr-ui/ui/spots.slint:6`](../../ui/dr-ui/ui/spots.slint#L6), [`ui/dr-ui/ui/spots.slint:91`](../../ui/dr-ui/ui/spots.slint#L91) |
+| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/adjust.rs:2718`](../../core/dr-gpu/src/adjust.rs#L2718), [`core/dr-gpu/src/adjust.rs:2795`](../../core/dr-gpu/src/adjust.rs#L2795), [`core/dr-gpu/src/adjust.rs:2880`](../../core/dr-gpu/src/adjust.rs#L2880), [`core/dr-gpu/src/adjust.rs:54`](../../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/lib.rs:174`](../../core/dr-gpu/src/lib.rs#L174), [`core/dr-gpu/src/lib.rs:194`](../../core/dr-gpu/src/lib.rs#L194), [`core/dr-gpu/src/lib.rs:72`](../../core/dr-gpu/src/lib.rs#L72), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/graph.rs:928`](../../core/dr-pipeline/src/graph.rs#L928), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:565`](../../core/dr-pipeline/src/ops/dehaze.rs#L565), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:1026`](../../ui/dr-ui/src/develop/render.rs#L1026), [`ui/dr-ui/src/develop/render.rs:1060`](../../ui/dr-ui/src/develop/render.rs#L1060), [`ui/dr-ui/src/develop/render.rs:1198`](../../ui/dr-ui/src/develop/render.rs#L1198), [`ui/dr-ui/src/develop/render.rs:217`](../../ui/dr-ui/src/develop/render.rs#L217), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/lib.rs:1077`](../../ui/dr-ui/src/lib.rs#L1077), [`ui/dr-ui/src/lib.rs:115`](../../ui/dr-ui/src/lib.rs#L115) |
| FR-DSP-3 | [`core/dr-gpu/tests/frame_budget.rs:101`](../../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/local_contrast.rs:329`](../../core/dr-gpu/tests/local_contrast.rs#L329), [`core/dr-pipeline/src/ops/local_contrast.rs:447`](../../core/dr-pipeline/src/ops/local_contrast.rs#L447) |
| FR-DSP-4 | [`ui/dr-ui/src/lib.rs:2222`](../../ui/dr-ui/src/lib.rs#L2222), [`ui/dr-ui/src/lib.rs:2401`](../../ui/dr-ui/src/lib.rs#L2401), [`ui/dr-ui/src/refine.rs:1`](../../ui/dr-ui/src/refine.rs#L1), [`ui/dr-ui/ui/app.slint:112`](../../ui/dr-ui/ui/app.slint#L112), [`ui/dr-ui/ui/app.slint:1836`](../../ui/dr-ui/ui/app.slint#L1836), [`ui/dr-ui/ui/histogram.slint:182`](../../ui/dr-ui/ui/histogram.slint#L182), [`ui/dr-ui/ui/histogram.slint:250`](../../ui/dr-ui/ui/histogram.slint#L250) |
| FR-DSP-5 | [`core/dr-gpu/tests/frame_budget.rs:101`](../../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/zoom_resolution.rs:135`](../../core/dr-gpu/tests/zoom_resolution.rs#L135), [`core/dr-gpu/tests/zoom_resolution.rs:166`](../../core/dr-gpu/tests/zoom_resolution.rs#L166), [`core/dr-gpu/tests/zoom_resolution.rs:1`](../../core/dr-gpu/tests/zoom_resolution.rs#L1), [`core/dr-gpu/tests/zoom_resolution.rs:216`](../../core/dr-gpu/tests/zoom_resolution.rs#L216) |
-| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:559`](../../core/dr-pipeline/src/operation.rs#L559), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop/render.rs:1477`](../../ui/dr-ui/src/develop/render.rs#L1477), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/lib.rs:2098`](../../ui/dr-ui/src/lib.rs#L2098), [`ui/dr-ui/src/lib.rs:2939`](../../ui/dr-ui/src/lib.rs#L2939) |
-| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1267`](../../ui/dr-ui/src/develop/render.rs#L1267), [`ui/dr-ui/src/develop/render.rs:1299`](../../ui/dr-ui/src/develop/render.rs#L1299), [`ui/dr-ui/src/develop/render.rs:285`](../../ui/dr-ui/src/develop/render.rs#L285), [`ui/dr-ui/src/develop/session.rs:116`](../../ui/dr-ui/src/develop/session.rs#L116), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:2254`](../../ui/dr-ui/src/lib.rs#L2254), [`ui/dr-ui/src/lib.rs:467`](../../ui/dr-ui/src/lib.rs#L467), [`ui/dr-ui/ui/histogram.slint:191`](../../ui/dr-ui/ui/histogram.slint#L191), [`ui/dr-ui/ui/histogram.slint:1`](../../ui/dr-ui/ui/histogram.slint#L1) |
-| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop/render.rs:1115`](../../ui/dr-ui/src/develop/render.rs#L1115), [`ui/dr-ui/src/develop/render.rs:1477`](../../ui/dr-ui/src/develop/render.rs#L1477), [`ui/dr-ui/src/develop/render.rs:1523`](../../ui/dr-ui/src/develop/render.rs#L1523), [`ui/dr-ui/src/develop/render.rs:1542`](../../ui/dr-ui/src/develop/render.rs#L1542), [`ui/dr-ui/src/develop/render.rs:193`](../../ui/dr-ui/src/develop/render.rs#L193), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/session.rs:269`](../../ui/dr-ui/src/develop/session.rs#L269), [`ui/dr-ui/src/display_ui.rs:192`](../../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1279`](../../ui/dr-ui/src/lib.rs#L1279), [`ui/dr-ui/src/lib.rs:2098`](../../ui/dr-ui/src/lib.rs#L2098), [`ui/dr-ui/src/lib.rs:2916`](../../ui/dr-ui/src/lib.rs#L2916), [`ui/dr-ui/src/lib.rs:2939`](../../ui/dr-ui/src/lib.rs#L2939), [`ui/dr-ui/ui/app.slint:130`](../../ui/dr-ui/ui/app.slint#L130), [`ui/dr-ui/ui/app.slint:1822`](../../ui/dr-ui/ui/app.slint#L1822), [`ui/dr-ui/ui/settings.slint:176`](../../ui/dr-ui/ui/settings.slint#L176), [`ui/dr-ui/ui/settings.slint:864`](../../ui/dr-ui/ui/settings.slint#L864) |
+| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:585`](../../core/dr-pipeline/src/operation.rs#L585), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop/render.rs:1605`](../../ui/dr-ui/src/develop/render.rs#L1605), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/lib.rs:2098`](../../ui/dr-ui/src/lib.rs#L2098), [`ui/dr-ui/src/lib.rs:2939`](../../ui/dr-ui/src/lib.rs#L2939) |
+| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1395`](../../ui/dr-ui/src/develop/render.rs#L1395), [`ui/dr-ui/src/develop/render.rs:1427`](../../ui/dr-ui/src/develop/render.rs#L1427), [`ui/dr-ui/src/develop/render.rs:285`](../../ui/dr-ui/src/develop/render.rs#L285), [`ui/dr-ui/src/develop/session.rs:116`](../../ui/dr-ui/src/develop/session.rs#L116), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:2254`](../../ui/dr-ui/src/lib.rs#L2254), [`ui/dr-ui/src/lib.rs:467`](../../ui/dr-ui/src/lib.rs#L467), [`ui/dr-ui/ui/histogram.slint:191`](../../ui/dr-ui/ui/histogram.slint#L191), [`ui/dr-ui/ui/histogram.slint:1`](../../ui/dr-ui/ui/histogram.slint#L1) |
+| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop/render.rs:1159`](../../ui/dr-ui/src/develop/render.rs#L1159), [`ui/dr-ui/src/develop/render.rs:1605`](../../ui/dr-ui/src/develop/render.rs#L1605), [`ui/dr-ui/src/develop/render.rs:1651`](../../ui/dr-ui/src/develop/render.rs#L1651), [`ui/dr-ui/src/develop/render.rs:1670`](../../ui/dr-ui/src/develop/render.rs#L1670), [`ui/dr-ui/src/develop/render.rs:193`](../../ui/dr-ui/src/develop/render.rs#L193), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/session.rs:269`](../../ui/dr-ui/src/develop/session.rs#L269), [`ui/dr-ui/src/display_ui.rs:192`](../../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1279`](../../ui/dr-ui/src/lib.rs#L1279), [`ui/dr-ui/src/lib.rs:2098`](../../ui/dr-ui/src/lib.rs#L2098), [`ui/dr-ui/src/lib.rs:2916`](../../ui/dr-ui/src/lib.rs#L2916), [`ui/dr-ui/src/lib.rs:2939`](../../ui/dr-ui/src/lib.rs#L2939), [`ui/dr-ui/ui/app.slint:130`](../../ui/dr-ui/ui/app.slint#L130), [`ui/dr-ui/ui/app.slint:1822`](../../ui/dr-ui/ui/app.slint#L1822), [`ui/dr-ui/ui/settings.slint:176`](../../ui/dr-ui/ui/settings.slint#L176), [`ui/dr-ui/ui/settings.slint:864`](../../ui/dr-ui/ui/settings.slint#L864) |
| FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) |
| FR-EXP-10 | [`core/dr-catalog/src/albums.rs:1`](../../core/dr-catalog/src/albums.rs#L1), [`core/dr-types/src/settings.rs:731`](../../core/dr-types/src/settings.rs#L731), [`ui/dr-ui/src/albums_ui.rs:1`](../../ui/dr-ui/src/albums_ui.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:129`](../../ui/dr-ui/src/collections_ui/controller.rs#L129), [`ui/dr-ui/src/collections_ui/controller.rs:432`](../../ui/dr-ui/src/collections_ui/controller.rs#L432), [`ui/dr-ui/src/develop_ui.rs:273`](../../ui/dr-ui/src/develop_ui.rs#L273), [`ui/dr-ui/src/export.rs:216`](../../ui/dr-ui/src/export.rs#L216), [`ui/dr-ui/src/export.rs:587`](../../ui/dr-ui/src/export.rs#L587), [`ui/dr-ui/src/export.rs:99`](../../ui/dr-ui/src/export.rs#L99), [`ui/dr-ui/src/lib.rs:1622`](../../ui/dr-ui/src/lib.rs#L1622), [`ui/dr-ui/src/library_ui/controller.rs:205`](../../ui/dr-ui/src/library_ui/controller.rs#L205), [`ui/dr-ui/src/library_ui/controller.rs:934`](../../ui/dr-ui/src/library_ui/controller.rs#L934), [`ui/dr-ui/src/library_ui/controller.rs:974`](../../ui/dr-ui/src/library_ui/controller.rs#L974), [`ui/dr-ui/src/library_ui/window.rs:359`](../../ui/dr-ui/src/library_ui/window.rs#L359), [`ui/dr-ui/src/saf.rs:1`](../../ui/dr-ui/src/saf.rs#L1), [`ui/dr-ui/ui/albums.slint:1`](../../ui/dr-ui/ui/albums.slint#L1), [`ui/dr-ui/ui/app.slint:3546`](../../ui/dr-ui/ui/app.slint#L3546), [`ui/dr-ui/ui/collections.slint:789`](../../ui/dr-ui/ui/collections.slint#L789), [`ui/dr-ui/ui/export.slint:204`](../../ui/dr-ui/ui/export.slint#L204) |
-| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:157`](../../core/dr-export/src/lib.rs#L157), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:57`](../../core/dr-export/src/lib.rs#L57), [`core/dr-gpu/src/adjust.rs:3133`](../../core/dr-gpu/src/adjust.rs#L3133), [`core/dr-pipeline/src/graph.rs:841`](../../core/dr-pipeline/src/graph.rs#L841), [`core/dr-pipeline/src/graph.rs:972`](../../core/dr-pipeline/src/graph.rs#L972), [`core/dr-pipeline/src/operation.rs:559`](../../core/dr-pipeline/src/operation.rs#L559), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:976`](../../core/dr-types/src/settings.rs#L976), [`ui/dr-ui/src/develop/render.rs:1542`](../../ui/dr-ui/src/develop/render.rs#L1542), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) |
+| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:157`](../../core/dr-export/src/lib.rs#L157), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:57`](../../core/dr-export/src/lib.rs#L57), [`core/dr-gpu/src/adjust.rs:3153`](../../core/dr-gpu/src/adjust.rs#L3153), [`core/dr-pipeline/src/graph.rs:841`](../../core/dr-pipeline/src/graph.rs#L841), [`core/dr-pipeline/src/graph.rs:972`](../../core/dr-pipeline/src/graph.rs#L972), [`core/dr-pipeline/src/operation.rs:585`](../../core/dr-pipeline/src/operation.rs#L585), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:976`](../../core/dr-types/src/settings.rs#L976), [`ui/dr-ui/src/develop/render.rs:1670`](../../ui/dr-ui/src/develop/render.rs#L1670), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) |
| FR-EXP-3 | [`core/dr-export/src/lib.rs:186`](../../core/dr-export/src/lib.rs#L186), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:136`](../../core/dr-export/src/size.rs#L136), [`core/dr-export/src/size.rs:1`](../../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../../core/dr-export/src/size.rs#L25), [`core/dr-export/src/size.rs:84`](../../core/dr-export/src/size.rs#L84), [`core/dr-types/src/settings.rs:1021`](../../core/dr-types/src/settings.rs#L1021), [`core/dr-types/src/settings.rs:1029`](../../core/dr-types/src/settings.rs#L1029), [`core/dr-types/src/settings.rs:1045`](../../core/dr-types/src/settings.rs#L1045), [`core/dr-types/src/settings.rs:1114`](../../core/dr-types/src/settings.rs#L1114), [`core/dr-types/src/settings.rs:1142`](../../core/dr-types/src/settings.rs#L1142), [`core/dr-types/src/settings.rs:1153`](../../core/dr-types/src/settings.rs#L1153), [`core/dr-types/src/settings.rs:1159`](../../core/dr-types/src/settings.rs#L1159), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:256`](../../ui/dr-ui/src/settings_ui.rs#L256), [`ui/dr-ui/src/settings_ui.rs:269`](../../ui/dr-ui/src/settings_ui.rs#L269), [`ui/dr-ui/src/settings_ui.rs:288`](../../ui/dr-ui/src/settings_ui.rs#L288), [`ui/dr-ui/src/settings_ui.rs:701`](../../ui/dr-ui/src/settings_ui.rs#L701), [`ui/dr-ui/ui/export.slint:138`](../../ui/dr-ui/ui/export.slint#L138), [`ui/dr-ui/ui/export.slint:148`](../../ui/dr-ui/ui/export.slint#L148) |
| FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) |
| FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1) |
| FR-EXP-6 | [`core/dr-catalog/src/albums.rs:1`](../../core/dr-catalog/src/albums.rs#L1), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/albums_ui.rs:1`](../../ui/dr-ui/src/albums_ui.rs#L1), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:651`](../../ui/dr-ui/src/lib.rs#L651), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/albums.slint:1`](../../ui/dr-ui/ui/albums.slint#L1) |
| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/develop_ui.rs:370`](../../ui/dr-ui/src/develop_ui.rs#L370), [`ui/dr-ui/src/export.rs:1119`](../../ui/dr-ui/src/export.rs#L1119), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:292`](../../ui/dr-ui/src/lib.rs#L292), [`ui/dr-ui/src/lib.rs:651`](../../ui/dr-ui/src/lib.rs#L651), [`ui/dr-ui/src/lib.rs:697`](../../ui/dr-ui/src/lib.rs#L697), [`ui/dr-ui/src/lib.rs:726`](../../ui/dr-ui/src/lib.rs#L726), [`ui/dr-ui/src/library_ui/controller.rs:1099`](../../ui/dr-ui/src/library_ui/controller.rs#L1099), [`ui/dr-ui/src/library_ui/controller.rs:1111`](../../ui/dr-ui/src/library_ui/controller.rs#L1111), [`ui/dr-ui/src/library_ui/controller.rs:1123`](../../ui/dr-ui/src/library_ui/controller.rs#L1123), [`ui/dr-ui/src/library_ui/controller.rs:768`](../../ui/dr-ui/src/library_ui/controller.rs#L768), [`ui/dr-ui/src/library_ui/controller.rs:868`](../../ui/dr-ui/src/library_ui/controller.rs#L868), [`ui/dr-ui/src/library_ui/controller.rs:925`](../../ui/dr-ui/src/library_ui/controller.rs#L925), [`ui/dr-ui/src/library_ui/sync.rs:100`](../../ui/dr-ui/src/library_ui/sync.rs#L100), [`ui/dr-ui/src/settings_ui.rs:394`](../../ui/dr-ui/src/settings_ui.rs#L394), [`ui/dr-ui/ui/app.slint:1089`](../../ui/dr-ui/ui/app.slint#L1089), [`ui/dr-ui/ui/app.slint:1519`](../../ui/dr-ui/ui/app.slint#L1519), [`ui/dr-ui/ui/app.slint:2538`](../../ui/dr-ui/ui/app.slint#L2538), [`ui/dr-ui/ui/app.slint:3446`](../../ui/dr-ui/ui/app.slint#L3446), [`ui/dr-ui/ui/app.slint:686`](../../ui/dr-ui/ui/app.slint#L686), [`ui/dr-ui/ui/app.slint:703`](../../ui/dr-ui/ui/app.slint#L703), [`ui/dr-ui/ui/app.slint:939`](../../ui/dr-ui/ui/app.slint#L939), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:246`](../../ui/dr-ui/ui/export.slint#L246), [`ui/dr-ui/ui/library.slint:2009`](../../ui/dr-ui/ui/library.slint#L2009), [`ui/dr-ui/ui/library.slint:2018`](../../ui/dr-ui/ui/library.slint#L2018), [`ui/dr-ui/ui/library.slint:3177`](../../ui/dr-ui/ui/library.slint#L3177), [`ui/dr-ui/ui/library.slint:5205`](../../ui/dr-ui/ui/library.slint#L5205) |
| FR-EXP-8 | [`core/dr-decode/src/lib.rs:372`](../../core/dr-decode/src/lib.rs#L372), [`core/dr-decode/src/lib.rs:396`](../../core/dr-decode/src/lib.rs#L396), [`core/dr-decode/src/lib.rs:410`](../../core/dr-decode/src/lib.rs#L410), [`core/dr-decode/src/lib.rs:77`](../../core/dr-decode/src/lib.rs#L77), [`core/dr-decode/src/lib.rs:85`](../../core/dr-decode/src/lib.rs#L85), [`core/dr-decode/src/lib.rs:88`](../../core/dr-decode/src/lib.rs#L88), [`core/dr-decode/src/locate.rs:1233`](../../core/dr-decode/src/locate.rs#L1233), [`core/dr-decode/src/locate.rs:1292`](../../core/dr-decode/src/locate.rs#L1292), [`core/dr-decode/src/locate.rs:359`](../../core/dr-decode/src/locate.rs#L359), [`core/dr-decode/src/locate.rs:556`](../../core/dr-decode/src/locate.rs#L556), [`core/dr-decode/src/locate.rs:640`](../../core/dr-decode/src/locate.rs#L640), [`core/dr-decode/src/locate.rs:653`](../../core/dr-decode/src/locate.rs#L653), [`core/dr-decode/src/locate.rs:736`](../../core/dr-decode/src/locate.rs#L736), [`core/dr-export/examples/export.rs:99`](../../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:140`](../../core/dr-export/src/lib.rs#L140), [`core/dr-export/src/metadata.rs:1`](../../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:655`](../../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:671`](../../core/dr-types/src/settings.rs#L671), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/develop/session.rs:37`](../../ui/dr-ui/src/develop/session.rs#L37), [`ui/dr-ui/src/develop/session.rs:417`](../../ui/dr-ui/src/develop/session.rs#L417), [`ui/dr-ui/src/develop/session.rs:529`](../../ui/dr-ui/src/develop/session.rs#L529), [`ui/dr-ui/src/export.rs:554`](../../ui/dr-ui/src/export.rs#L554), [`ui/dr-ui/src/export.rs:737`](../../ui/dr-ui/src/export.rs#L737), [`ui/dr-ui/src/export.rs:783`](../../ui/dr-ui/src/export.rs#L783), [`ui/dr-ui/src/export.rs:811`](../../ui/dr-ui/src/export.rs#L811), [`ui/dr-ui/src/export.rs:848`](../../ui/dr-ui/src/export.rs#L848), [`ui/dr-ui/src/export.rs:949`](../../ui/dr-ui/src/export.rs#L949), [`ui/dr-ui/src/export.rs:967`](../../ui/dr-ui/src/export.rs#L967), [`ui/dr-ui/src/lib.rs:292`](../../ui/dr-ui/src/lib.rs#L292), [`ui/dr-ui/src/lib.rs:316`](../../ui/dr-ui/src/lib.rs#L316), [`ui/dr-ui/src/lib.rs:683`](../../ui/dr-ui/src/lib.rs#L683), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) |
-| FR-EXP-9 | [`core/dr-decode/src/lib.rs:552`](../../core/dr-decode/src/lib.rs#L552), [`core/dr-export/src/lib.rs:132`](../../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1400`](../../core/dr-gpu/src/adjust.rs#L1400), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/render.rs:588`](../../ui/dr-ui/src/develop/render.rs#L588), [`ui/dr-ui/src/develop/render.rs:626`](../../ui/dr-ui/src/develop/render.rs#L626), [`ui/dr-ui/src/lib.rs:651`](../../ui/dr-ui/src/lib.rs#L651), [`ui/dr-ui/src/library/sweep.rs:510`](../../ui/dr-ui/src/library/sweep.rs#L510), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) |
+| FR-EXP-9 | [`core/dr-decode/src/lib.rs:552`](../../core/dr-decode/src/lib.rs#L552), [`core/dr-export/src/lib.rs:132`](../../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1418`](../../core/dr-gpu/src/adjust.rs#L1418), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/render.rs:588`](../../ui/dr-ui/src/develop/render.rs#L588), [`ui/dr-ui/src/develop/render.rs:626`](../../ui/dr-ui/src/develop/render.rs#L626), [`ui/dr-ui/src/lib.rs:651`](../../ui/dr-ui/src/lib.rs#L651), [`ui/dr-ui/src/library/sweep.rs:510`](../../ui/dr-ui/src/library/sweep.rs#L510), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) |
| FR-INF-1 | [`ui/dr-ui/src/lib.rs:1459`](../../ui/dr-ui/src/lib.rs#L1459), [`ui/dr-ui/ui/settings.slint:824`](../../ui/dr-ui/ui/settings.slint#L824) |
| FR-MRG-1 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1), [`ui/dr-ui/src/lib.rs:1885`](../../ui/dr-ui/src/lib.rs#L1885), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1524`](../../ui/dr-ui/ui/app.slint#L1524), [`ui/dr-ui/ui/app.slint:589`](../../ui/dr-ui/ui/app.slint#L589), [`ui/dr-ui/ui/library.slint:2023`](../../ui/dr-ui/ui/library.slint#L2023), [`ui/dr-ui/ui/library.slint:5162`](../../ui/dr-ui/ui/library.slint#L5162), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) |
| FR-MRG-10 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1) |
| FR-MRG-11 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1) |
-| FR-MRG-2 | [`core/dr-gpu/src/adjust.rs:104`](../../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1423`](../../core/dr-gpu/src/adjust.rs#L1423), [`core/dr-gpu/src/adjust.rs:1552`](../../core/dr-gpu/src/adjust.rs#L1552), [`core/dr-gpu/src/adjust.rs:518`](../../core/dr-gpu/src/adjust.rs#L518), [`core/dr-pipeline/src/graph.rs:858`](../../core/dr-pipeline/src/graph.rs#L858), [`core/dr-pipeline/src/operation.rs:2343`](../../core/dr-pipeline/src/operation.rs#L2343), [`core/dr-pipeline/src/operation.rs:427`](../../core/dr-pipeline/src/operation.rs#L427), [`core/dr-pipeline/src/operation.rs:645`](../../core/dr-pipeline/src/operation.rs#L645), [`core/dr-pipeline/src/operation.rs:984`](../../core/dr-pipeline/src/operation.rs#L984), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) |
+| FR-MRG-2 | [`core/dr-gpu/src/adjust.rs:104`](../../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1441`](../../core/dr-gpu/src/adjust.rs#L1441), [`core/dr-gpu/src/adjust.rs:1570`](../../core/dr-gpu/src/adjust.rs#L1570), [`core/dr-gpu/src/adjust.rs:536`](../../core/dr-gpu/src/adjust.rs#L536), [`core/dr-pipeline/src/graph.rs:858`](../../core/dr-pipeline/src/graph.rs#L858), [`core/dr-pipeline/src/operation.rs:1020`](../../core/dr-pipeline/src/operation.rs#L1020), [`core/dr-pipeline/src/operation.rs:2440`](../../core/dr-pipeline/src/operation.rs#L2440), [`core/dr-pipeline/src/operation.rs:453`](../../core/dr-pipeline/src/operation.rs#L453), [`core/dr-pipeline/src/operation.rs:671`](../../core/dr-pipeline/src/operation.rs#L671), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) |
| FR-MRG-3 | [`core/dr-decode/examples/linear_dng.rs:1`](../../core/dr-decode/examples/linear_dng.rs#L1), [`core/dr-decode/src/lib.rs:143`](../../core/dr-decode/src/lib.rs#L143), [`core/dr-decode/src/lib.rs:150`](../../core/dr-decode/src/lib.rs#L150), [`core/dr-decode/src/lib.rs:604`](../../core/dr-decode/src/lib.rs#L604), [`core/dr-decode/src/profile.rs:395`](../../core/dr-decode/src/profile.rs#L395), [`core/dr-export/src/dng.rs:1`](../../core/dr-export/src/dng.rs#L1), [`core/dr-gpu/src/demosaic.rs:276`](../../core/dr-gpu/src/demosaic.rs#L276), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) |
| FR-MRG-4 | [`core/dr-export/src/inscribed.rs:1`](../../core/dr-export/src/inscribed.rs#L1), [`core/dr-pano/src/fill.rs:1`](../../core/dr-pano/src/fill.rs#L1), [`core/dr-pano/src/migan.rs:1`](../../core/dr-pano/src/migan.rs#L1), [`core/dr-pano/src/projection.rs:1`](../../core/dr-pano/src/projection.rs#L1), [`ui/dr-ui/src/library/paths.rs:365`](../../ui/dr-ui/src/library/paths.rs#L365), [`ui/dr-ui/src/library/paths.rs:369`](../../ui/dr-ui/src/library/paths.rs#L369), [`ui/dr-ui/src/merge.rs:147`](../../ui/dr-ui/src/merge.rs#L147), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1), [`ui/dr-ui/ui/merge.slint:46`](../../ui/dr-ui/ui/merge.slint#L46) |
| FR-MRG-5 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) |
-| FR-MRG-6 | [`core/dr-pipeline/src/sidecar.rs:1055`](../../core/dr-pipeline/src/sidecar.rs#L1055), [`core/dr-pipeline/src/sidecar.rs:116`](../../core/dr-pipeline/src/sidecar.rs#L116), [`core/dr-pipeline/src/sidecar.rs:126`](../../core/dr-pipeline/src/sidecar.rs#L126), [`core/dr-pipeline/src/sidecar.rs:2282`](../../core/dr-pipeline/src/sidecar.rs#L2282), [`core/dr-pipeline/src/sidecar.rs:860`](../../core/dr-pipeline/src/sidecar.rs#L860), [`ui/dr-ui/src/merge.rs:849`](../../ui/dr-ui/src/merge.rs#L849) |
+| FR-MRG-6 | [`core/dr-pipeline/src/sidecar.rs:1055`](../../core/dr-pipeline/src/sidecar.rs#L1055), [`core/dr-pipeline/src/sidecar.rs:116`](../../core/dr-pipeline/src/sidecar.rs#L116), [`core/dr-pipeline/src/sidecar.rs:126`](../../core/dr-pipeline/src/sidecar.rs#L126), [`core/dr-pipeline/src/sidecar.rs:2284`](../../core/dr-pipeline/src/sidecar.rs#L2284), [`core/dr-pipeline/src/sidecar.rs:860`](../../core/dr-pipeline/src/sidecar.rs#L860), [`ui/dr-ui/src/merge.rs:849`](../../ui/dr-ui/src/merge.rs#L849) |
| FR-MRG-7 | [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:589`](../../ui/dr-ui/ui/app.slint#L589), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) |
| FR-MRG-8 | [`core/dr-pano/src/xfeat.rs:1`](../../core/dr-pano/src/xfeat.rs#L1), [`core/dr-segment/examples/onnx_probe.rs:1`](../../core/dr-segment/examples/onnx_probe.rs#L1) |
| FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:206`](../../core/dr-sync-nextcloud/src/auth.rs#L206), [`core/dr-sync-nextcloud/src/auth.rs:49`](../../core/dr-sync-nextcloud/src/auth.rs#L49), [`core/dr-sync-nextcloud/src/provider.rs:1`](../../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:375`](../../core/dr-sync/src/account.rs#L375), [`ui/dr-ui/src/launch.rs:316`](../../ui/dr-ui/src/launch.rs#L316), [`ui/dr-ui/src/launch.rs:61`](../../ui/dr-ui/src/launch.rs#L61), [`ui/dr-ui/src/launch_ui.rs:525`](../../ui/dr-ui/src/launch_ui.rs#L525) |
@@ -132,12 +132,12 @@ _None._
| FR-NC-7 | [`core/dr-catalog/src/face_shard.rs:1`](../../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:120`](../../core/dr-sync-nextcloud/src/lib.rs#L120), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library/sweep.rs:562`](../../ui/dr-ui/src/library/sweep.rs#L562), [`ui/dr-ui/src/library_ui/sync.rs:392`](../../ui/dr-ui/src/library_ui/sync.rs#L392), [`ui/dr-ui/ui/settings.slint:375`](../../ui/dr-ui/ui/settings.slint#L375) |
| FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:377`](../../core/dr-types/src/settings.rs#L377), [`ui/dr-ui/src/import.rs:100`](../../ui/dr-ui/src/import.rs#L100), [`ui/dr-ui/src/import.rs:1`](../../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import_ui.rs:1`](../../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1831`](../../ui/dr-ui/src/lib.rs#L1831), [`ui/dr-ui/ui/import.slint:6`](../../ui/dr-ui/ui/import.slint#L6) |
| FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:100`](../../ui/dr-ui/src/import.rs#L100), [`ui/dr-ui/src/import.rs:125`](../../ui/dr-ui/src/import.rs#L125), [`ui/dr-ui/src/import.rs:346`](../../ui/dr-ui/src/import.rs#L346), [`ui/dr-ui/src/import.rs:600`](../../ui/dr-ui/src/import.rs#L600), [`ui/dr-ui/src/import_ui.rs:1`](../../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1831`](../../ui/dr-ui/src/lib.rs#L1831) |
-| FR-NC-8 | [`core/dr-catalog/src/rating.rs:1225`](../../core/dr-catalog/src/rating.rs#L1225), [`core/dr-catalog/src/rating.rs:210`](../../core/dr-catalog/src/rating.rs#L210), [`core/dr-catalog/src/rating.rs:72`](../../core/dr-catalog/src/rating.rs#L72), [`core/dr-catalog/src/schema.rs:330`](../../core/dr-catalog/src/schema.rs#L330), [`core/dr-pipeline/src/sidecar.rs:140`](../../core/dr-pipeline/src/sidecar.rs#L140), [`core/dr-pipeline/src/sidecar.rs:3064`](../../core/dr-pipeline/src/sidecar.rs#L3064), [`core/dr-pipeline/src/sidecar.rs:679`](../../core/dr-pipeline/src/sidecar.rs#L679), [`core/dr-pipeline/src/sidecar.rs:754`](../../core/dr-pipeline/src/sidecar.rs#L754), [`core/dr-pipeline/src/sidecar.rs:99`](../../core/dr-pipeline/src/sidecar.rs#L99), [`ui/dr-ui/src/lib.rs:2655`](../../ui/dr-ui/src/lib.rs#L2655), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:273`](../../ui/dr-ui/src/library/sidecar.rs#L273), [`ui/dr-ui/src/library/sidecar.rs:580`](../../ui/dr-ui/src/library/sidecar.rs#L580), [`ui/dr-ui/src/library/thumbnails_fetch.rs:504`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L504), [`ui/dr-ui/src/library_ui/controller.rs:677`](../../ui/dr-ui/src/library_ui/controller.rs#L677), [`ui/dr-ui/src/presets.rs:288`](../../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../../ui/dr-ui/src/presets.rs#L336) |
-| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1225`](../../core/dr-catalog/src/rating.rs#L1225), [`core/dr-catalog/src/rating.rs:210`](../../core/dr-catalog/src/rating.rs#L210), [`core/dr-catalog/src/rating.rs:72`](../../core/dr-catalog/src/rating.rs#L72), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-catalog/src/schema.rs:330`](../../core/dr-catalog/src/schema.rs#L330), [`core/dr-catalog/src/schema.rs:770`](../../core/dr-catalog/src/schema.rs#L770), [`core/dr-catalog/src/sync.rs:1`](../../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:206`](../../core/dr-pipeline/src/sidecar.rs#L206), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:2561`](../../core/dr-pipeline/src/sidecar.rs#L2561), [`core/dr-pipeline/src/sidecar.rs:2798`](../../core/dr-pipeline/src/sidecar.rs#L2798), [`core/dr-pipeline/src/sidecar.rs:3064`](../../core/dr-pipeline/src/sidecar.rs#L3064), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:508`](../../core/dr-pipeline/src/sidecar.rs#L508), [`core/dr-pipeline/src/sidecar.rs:679`](../../core/dr-pipeline/src/sidecar.rs#L679), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:754`](../../core/dr-pipeline/src/sidecar.rs#L754), [`core/dr-pipeline/src/spot.rs:245`](../../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/mask_sidecar.rs:1075`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1075), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:139`](../../core/dr-sync-nextcloud/src/lib.rs#L139), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`core/dr-xmp/src/lib.rs:733`](../../core/dr-xmp/src/lib.rs#L733), [`core/dr-xmp/src/lib.rs:746`](../../core/dr-xmp/src/lib.rs#L746), [`ui/dr-ui/src/derived_sync.rs:1081`](../../ui/dr-ui/src/derived_sync.rs#L1081), [`ui/dr-ui/src/derived_sync.rs:623`](../../ui/dr-ui/src/derived_sync.rs#L623), [`ui/dr-ui/src/derived_sync.rs:652`](../../ui/dr-ui/src/derived_sync.rs#L652), [`ui/dr-ui/src/derived_sync.rs:685`](../../ui/dr-ui/src/derived_sync.rs#L685), [`ui/dr-ui/src/derived_sync.rs:832`](../../ui/dr-ui/src/derived_sync.rs#L832), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:273`](../../ui/dr-ui/src/library/sidecar.rs#L273), [`ui/dr-ui/src/library/sidecar.rs:350`](../../ui/dr-ui/src/library/sidecar.rs#L350), [`ui/dr-ui/src/library/sidecar.rs:434`](../../ui/dr-ui/src/library/sidecar.rs#L434), [`ui/dr-ui/src/library/sidecar.rs:580`](../../ui/dr-ui/src/library/sidecar.rs#L580), [`ui/dr-ui/src/library/sidecar.rs:600`](../../ui/dr-ui/src/library/sidecar.rs#L600), [`ui/dr-ui/src/library/thumbnails_fetch.rs:504`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L504), [`ui/dr-ui/src/library_ui/open.rs:455`](../../ui/dr-ui/src/library_ui/open.rs#L455), [`ui/dr-ui/src/presets.rs:288`](../../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../../ui/dr-ui/src/presets.rs#L336), [`ui/dr-ui/src/xmp_sync.rs:1`](../../ui/dr-ui/src/xmp_sync.rs#L1), [`ui/dr-ui/src/xmp_sync.rs:287`](../../ui/dr-ui/src/xmp_sync.rs#L287) |
+| FR-NC-8 | [`core/dr-catalog/src/rating.rs:1225`](../../core/dr-catalog/src/rating.rs#L1225), [`core/dr-catalog/src/rating.rs:210`](../../core/dr-catalog/src/rating.rs#L210), [`core/dr-catalog/src/rating.rs:72`](../../core/dr-catalog/src/rating.rs#L72), [`core/dr-catalog/src/schema.rs:330`](../../core/dr-catalog/src/schema.rs#L330), [`core/dr-pipeline/src/sidecar.rs:140`](../../core/dr-pipeline/src/sidecar.rs#L140), [`core/dr-pipeline/src/sidecar.rs:3066`](../../core/dr-pipeline/src/sidecar.rs#L3066), [`core/dr-pipeline/src/sidecar.rs:679`](../../core/dr-pipeline/src/sidecar.rs#L679), [`core/dr-pipeline/src/sidecar.rs:754`](../../core/dr-pipeline/src/sidecar.rs#L754), [`core/dr-pipeline/src/sidecar.rs:99`](../../core/dr-pipeline/src/sidecar.rs#L99), [`ui/dr-ui/src/lib.rs:2655`](../../ui/dr-ui/src/lib.rs#L2655), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:273`](../../ui/dr-ui/src/library/sidecar.rs#L273), [`ui/dr-ui/src/library/sidecar.rs:580`](../../ui/dr-ui/src/library/sidecar.rs#L580), [`ui/dr-ui/src/library/thumbnails_fetch.rs:504`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L504), [`ui/dr-ui/src/library_ui/controller.rs:677`](../../ui/dr-ui/src/library_ui/controller.rs#L677), [`ui/dr-ui/src/presets.rs:288`](../../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../../ui/dr-ui/src/presets.rs#L336) |
+| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1225`](../../core/dr-catalog/src/rating.rs#L1225), [`core/dr-catalog/src/rating.rs:210`](../../core/dr-catalog/src/rating.rs#L210), [`core/dr-catalog/src/rating.rs:72`](../../core/dr-catalog/src/rating.rs#L72), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-catalog/src/schema.rs:330`](../../core/dr-catalog/src/schema.rs#L330), [`core/dr-catalog/src/schema.rs:770`](../../core/dr-catalog/src/schema.rs#L770), [`core/dr-catalog/src/sync.rs:1`](../../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:206`](../../core/dr-pipeline/src/sidecar.rs#L206), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:2563`](../../core/dr-pipeline/src/sidecar.rs#L2563), [`core/dr-pipeline/src/sidecar.rs:2800`](../../core/dr-pipeline/src/sidecar.rs#L2800), [`core/dr-pipeline/src/sidecar.rs:3066`](../../core/dr-pipeline/src/sidecar.rs#L3066), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:508`](../../core/dr-pipeline/src/sidecar.rs#L508), [`core/dr-pipeline/src/sidecar.rs:679`](../../core/dr-pipeline/src/sidecar.rs#L679), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:754`](../../core/dr-pipeline/src/sidecar.rs#L754), [`core/dr-pipeline/src/spot.rs:245`](../../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/mask_sidecar.rs:1075`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1075), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:139`](../../core/dr-sync-nextcloud/src/lib.rs#L139), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`core/dr-xmp/src/lib.rs:733`](../../core/dr-xmp/src/lib.rs#L733), [`core/dr-xmp/src/lib.rs:746`](../../core/dr-xmp/src/lib.rs#L746), [`ui/dr-ui/src/derived_sync.rs:1081`](../../ui/dr-ui/src/derived_sync.rs#L1081), [`ui/dr-ui/src/derived_sync.rs:623`](../../ui/dr-ui/src/derived_sync.rs#L623), [`ui/dr-ui/src/derived_sync.rs:652`](../../ui/dr-ui/src/derived_sync.rs#L652), [`ui/dr-ui/src/derived_sync.rs:685`](../../ui/dr-ui/src/derived_sync.rs#L685), [`ui/dr-ui/src/derived_sync.rs:832`](../../ui/dr-ui/src/derived_sync.rs#L832), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:273`](../../ui/dr-ui/src/library/sidecar.rs#L273), [`ui/dr-ui/src/library/sidecar.rs:350`](../../ui/dr-ui/src/library/sidecar.rs#L350), [`ui/dr-ui/src/library/sidecar.rs:434`](../../ui/dr-ui/src/library/sidecar.rs#L434), [`ui/dr-ui/src/library/sidecar.rs:580`](../../ui/dr-ui/src/library/sidecar.rs#L580), [`ui/dr-ui/src/library/sidecar.rs:600`](../../ui/dr-ui/src/library/sidecar.rs#L600), [`ui/dr-ui/src/library/thumbnails_fetch.rs:504`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L504), [`ui/dr-ui/src/library_ui/open.rs:455`](../../ui/dr-ui/src/library_ui/open.rs#L455), [`ui/dr-ui/src/presets.rs:288`](../../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../../ui/dr-ui/src/presets.rs#L336), [`ui/dr-ui/src/xmp_sync.rs:1`](../../ui/dr-ui/src/xmp_sync.rs#L1), [`ui/dr-ui/src/xmp_sync.rs:287`](../../ui/dr-ui/src/xmp_sync.rs#L287) |
| FR-PLAT-AND-2 | [`core/dr-catalog/src/walk.rs:432`](../../core/dr-catalog/src/walk.rs#L432), [`core/dr-catalog/src/walk.rs:989`](../../core/dr-catalog/src/walk.rs#L989), [`core/dr-sync-folder/src/lib.rs:242`](../../core/dr-sync-folder/src/lib.rs#L242), [`core/dr-sync-folder/src/tests.rs:51`](../../core/dr-sync-folder/src/tests.rs#L51), [`core/dr-sync/src/error.rs:113`](../../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/error.rs:195`](../../core/dr-sync/src/error.rs#L195), [`core/dr-sync/src/scan.rs:211`](../../core/dr-sync/src/scan.rs#L211), [`core/dr-sync/src/scan.rs:519`](../../core/dr-sync/src/scan.rs#L519), [`core/dr-sync/src/scan.rs:545`](../../core/dr-sync/src/scan.rs#L545), [`core/dr-sync/src/scan.rs:574`](../../core/dr-sync/src/scan.rs#L574), [`ui/dr-ui/src/library/scan.rs:126`](../../ui/dr-ui/src/library/scan.rs#L126), [`ui/dr-ui/src/library/scan.rs:179`](../../ui/dr-ui/src/library/scan.rs#L179), [`ui/dr-ui/src/library/scan.rs:210`](../../ui/dr-ui/src/library/scan.rs#L210), [`ui/dr-ui/src/library/scan.rs:635`](../../ui/dr-ui/src/library/scan.rs#L635), [`ui/dr-ui/src/library_ui/controller.rs:344`](../../ui/dr-ui/src/library_ui/controller.rs#L344), [`ui/dr-ui/src/library_ui/controller.rs:592`](../../ui/dr-ui/src/library_ui/controller.rs#L592), [`ui/dr-ui/src/library_ui/offline.rs:569`](../../ui/dr-ui/src/library_ui/offline.rs#L569), [`ui/dr-ui/src/library_ui/open.rs:426`](../../ui/dr-ui/src/library_ui/open.rs#L426), [`ui/dr-ui/src/library_ui/open.rs:535`](../../ui/dr-ui/src/library_ui/open.rs#L535) |
| FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/runner.rs:1`](../../core/dr-catalog/src/runner.rs#L1), [`core/dr-catalog/tests/every_queued_kind_has_a_consumer.rs:1`](../../core/dr-catalog/tests/every_queued_kind_has_a_consumer.rs#L1) |
| FR-PLAT-AND-4 | [`core/dr-catalog/src/runner.rs:1`](../../core/dr-catalog/src/runner.rs#L1) |
-| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1322`](../../core/dr-gpu/src/adjust.rs#L1322), [`core/dr-gpu/src/detail.rs:161`](../../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:1231`](../../ui/dr-ui/src/lib.rs#L1231), [`ui/dr-ui/src/lib.rs:1630`](../../ui/dr-ui/src/lib.rs#L1630), [`ui/dr-ui/src/memory.rs:163`](../../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) |
+| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1340`](../../core/dr-gpu/src/adjust.rs#L1340), [`core/dr-gpu/src/detail.rs:161`](../../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:1231`](../../ui/dr-ui/src/lib.rs#L1231), [`ui/dr-ui/src/lib.rs:1630`](../../ui/dr-ui/src/lib.rs#L1630), [`ui/dr-ui/src/memory.rs:163`](../../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) |
| FR-PLAT-AND-6 | [`apps/darkroom-android/src/lib.rs:441`](../../apps/darkroom-android/src/lib.rs#L441) |
| FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:1408`](../../ui/dr-ui/src/lib.rs#L1408), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1) |
| FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../../platform/dr-plat/src/display/x11.rs#L1) |
@@ -152,8 +152,8 @@ _None._
| FR-UI-1 | [`core/dr-types/src/settings.rs:1390`](../../core/dr-types/src/settings.rs#L1390), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`core/dr-types/src/settings.rs:512`](../../core/dr-types/src/settings.rs#L512), [`ui/dr-ui/src/develop_ui.rs:204`](../../ui/dr-ui/src/develop_ui.rs#L204), [`ui/dr-ui/src/lib.rs:123`](../../ui/dr-ui/src/lib.rs#L123), [`ui/dr-ui/src/lib.rs:138`](../../ui/dr-ui/src/lib.rs#L138), [`ui/dr-ui/src/lib.rs:160`](../../ui/dr-ui/src/lib.rs#L160), [`ui/dr-ui/src/lib.rs:3211`](../../ui/dr-ui/src/lib.rs#L3211), [`ui/dr-ui/src/lib.rs:3230`](../../ui/dr-ui/src/lib.rs#L3230), [`ui/dr-ui/src/lib.rs:3257`](../../ui/dr-ui/src/lib.rs#L3257), [`ui/dr-ui/src/lib.rs:3308`](../../ui/dr-ui/src/lib.rs#L3308), [`ui/dr-ui/src/lib.rs:3341`](../../ui/dr-ui/src/lib.rs#L3341), [`ui/dr-ui/src/library_ui/grid.rs:232`](../../ui/dr-ui/src/library_ui/grid.rs#L232), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/settings_ui.rs:143`](../../ui/dr-ui/src/settings_ui.rs#L143), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:3084`](../../ui/dr-ui/ui/app.slint#L3084), [`ui/dr-ui/ui/app.slint:665`](../../ui/dr-ui/ui/app.slint#L665), [`ui/dr-ui/ui/app.slint:810`](../../ui/dr-ui/ui/app.slint#L810), [`ui/dr-ui/ui/identity.slint:249`](../../ui/dr-ui/ui/identity.slint#L249), [`ui/dr-ui/ui/library.slint:1447`](../../ui/dr-ui/ui/library.slint#L1447), [`ui/dr-ui/ui/settings.slint:129`](../../ui/dr-ui/ui/settings.slint#L129), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`ui/dr-ui/ui/toolrail.slint:156`](../../ui/dr-ui/ui/toolrail.slint#L156), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89), [`ui/dr-ui/ui/widgets.slint:1117`](../../ui/dr-ui/ui/widgets.slint#L1117) |
| FR-UI-2 | [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:123`](../../ui/dr-ui/src/lib.rs#L123), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/window.rs:1000`](../../ui/dr-ui/src/library_ui/window.rs#L1000), [`ui/dr-ui/ui/app.slint:137`](../../ui/dr-ui/ui/app.slint#L137), [`ui/dr-ui/ui/app.slint:3481`](../../ui/dr-ui/ui/app.slint#L3481), [`ui/dr-ui/ui/app.slint:794`](../../ui/dr-ui/ui/app.slint#L794), [`ui/dr-ui/ui/collections.slint:1354`](../../ui/dr-ui/ui/collections.slint#L1354), [`ui/dr-ui/ui/controls.slint:626`](../../ui/dr-ui/ui/controls.slint#L626), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1334`](../../ui/dr-ui/ui/library.slint#L1334), [`ui/dr-ui/ui/library.slint:1367`](../../ui/dr-ui/ui/library.slint#L1367), [`ui/dr-ui/ui/library.slint:1691`](../../ui/dr-ui/ui/library.slint#L1691), [`ui/dr-ui/ui/library.slint:1698`](../../ui/dr-ui/ui/library.slint#L1698), [`ui/dr-ui/ui/library.slint:1799`](../../ui/dr-ui/ui/library.slint#L1799), [`ui/dr-ui/ui/library.slint:3135`](../../ui/dr-ui/ui/library.slint#L3135), [`ui/dr-ui/ui/library.slint:572`](../../ui/dr-ui/ui/library.slint#L572), [`ui/dr-ui/ui/presets.slint:45`](../../ui/dr-ui/ui/presets.slint#L45), [`ui/dr-ui/ui/settings.slint:143`](../../ui/dr-ui/ui/settings.slint#L143) |
| FR-UI-3 | [`ui/dr-ui/src/collections_ui/controller.rs:143`](../../ui/dr-ui/src/collections_ui/controller.rs#L143), [`ui/dr-ui/src/collections_ui/controller.rs:348`](../../ui/dr-ui/src/collections_ui/controller.rs#L348), [`ui/dr-ui/src/collections_ui/controller.rs:361`](../../ui/dr-ui/src/collections_ui/controller.rs#L361), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/masks_ui.rs:1700`](../../ui/dr-ui/src/masks_ui.rs#L1700), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:2288`](../../ui/dr-ui/ui/app.slint#L2288), [`ui/dr-ui/ui/collections.slint:1228`](../../ui/dr-ui/ui/collections.slint#L1228), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:78`](../../ui/dr-ui/ui/collections.slint#L78), [`ui/dr-ui/ui/collections.slint:978`](../../ui/dr-ui/ui/collections.slint#L978), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) |
-| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:143`](../../ui/dr-ui/src/collections_ui/controller.rs#L143), [`ui/dr-ui/src/collections_ui/controller.rs:348`](../../ui/dr-ui/src/collections_ui/controller.rs#L348), [`ui/dr-ui/src/collections_ui/controller.rs:361`](../../ui/dr-ui/src/collections_ui/controller.rs#L361), [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/controller.rs:55`](../../ui/dr-ui/src/collections_ui/controller.rs#L55), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:278`](../../ui/dr-ui/src/collections_ui/press.rs#L278), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/tree_sync.rs:344`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:577`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L577), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/develop/framing.rs:257`](../../ui/dr-ui/src/develop/framing.rs#L257), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/framing.rs:619`](../../ui/dr-ui/src/develop/framing.rs#L619), [`ui/dr-ui/src/develop/framing.rs:630`](../../ui/dr-ui/src/develop/framing.rs#L630), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:818`](../../ui/dr-ui/src/develop/framing.rs#L818), [`ui/dr-ui/src/develop/framing.rs:849`](../../ui/dr-ui/src/develop/framing.rs#L849), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/framing.rs:995`](../../ui/dr-ui/src/develop/framing.rs#L995), [`ui/dr-ui/src/develop/render.rs:1067`](../../ui/dr-ui/src/develop/render.rs#L1067), [`ui/dr-ui/src/develop/render.rs:1092`](../../ui/dr-ui/src/develop/render.rs#L1092), [`ui/dr-ui/src/develop/render.rs:1115`](../../ui/dr-ui/src/develop/render.rs#L1115), [`ui/dr-ui/src/develop/render.rs:1154`](../../ui/dr-ui/src/develop/render.rs#L1154), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/render.rs:66`](../../ui/dr-ui/src/develop/render.rs#L66), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop_ui.rs:913`](../../ui/dr-ui/src/develop_ui.rs#L913), [`ui/dr-ui/src/duplicates_ui.rs:1`](../../ui/dr-ui/src/duplicates_ui.rs#L1), [`ui/dr-ui/src/gesture_book.rs:3`](../../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:127`](../../ui/dr-ui/src/gestures.rs#L127), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:1298`](../../ui/dr-ui/src/lib.rs#L1298), [`ui/dr-ui/src/lib.rs:1595`](../../ui/dr-ui/src/lib.rs#L1595), [`ui/dr-ui/src/lib.rs:2517`](../../ui/dr-ui/src/lib.rs#L2517), [`ui/dr-ui/src/lib.rs:2802`](../../ui/dr-ui/src/lib.rs#L2802), [`ui/dr-ui/src/lib.rs:2862`](../../ui/dr-ui/src/lib.rs#L2862), [`ui/dr-ui/src/lib.rs:3021`](../../ui/dr-ui/src/lib.rs#L3021), [`ui/dr-ui/src/lib.rs:3423`](../../ui/dr-ui/src/lib.rs#L3423), [`ui/dr-ui/src/lib.rs:3467`](../../ui/dr-ui/src/lib.rs#L3467), [`ui/dr-ui/src/lib.rs:497`](../../ui/dr-ui/src/lib.rs#L497), [`ui/dr-ui/src/lib.rs:526`](../../ui/dr-ui/src/lib.rs#L526), [`ui/dr-ui/src/library/thumbnails_fetch.rs:759`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L759), [`ui/dr-ui/src/library/thumbnails_fetch.rs:774`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L774), [`ui/dr-ui/src/library_ui/controller.rs:403`](../../ui/dr-ui/src/library_ui/controller.rs#L403), [`ui/dr-ui/src/library_ui/controller.rs:546`](../../ui/dr-ui/src/library_ui/controller.rs#L546), [`ui/dr-ui/src/library_ui/controller.rs:651`](../../ui/dr-ui/src/library_ui/controller.rs#L651), [`ui/dr-ui/src/library_ui/filter_bar.rs:500`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L500), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/library_ui/grid.rs:241`](../../ui/dr-ui/src/library_ui/grid.rs#L241), [`ui/dr-ui/src/library_ui/grid.rs:262`](../../ui/dr-ui/src/library_ui/grid.rs#L262), [`ui/dr-ui/src/library_ui/grid.rs:336`](../../ui/dr-ui/src/library_ui/grid.rs#L336), [`ui/dr-ui/src/library_ui/grid.rs:479`](../../ui/dr-ui/src/library_ui/grid.rs#L479), [`ui/dr-ui/src/library_ui/grid.rs:509`](../../ui/dr-ui/src/library_ui/grid.rs#L509), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/manual.rs:1`](../../ui/dr-ui/src/manual.rs#L1), [`ui/dr-ui/src/settings_ui.rs:121`](../../ui/dr-ui/src/settings_ui.rs#L121), [`ui/dr-ui/src/settings_ui.rs:508`](../../ui/dr-ui/src/settings_ui.rs#L508), [`ui/dr-ui/ui/app.slint:1809`](../../ui/dr-ui/ui/app.slint#L1809), [`ui/dr-ui/ui/app.slint:1976`](../../ui/dr-ui/ui/app.slint#L1976), [`ui/dr-ui/ui/app.slint:2102`](../../ui/dr-ui/ui/app.slint#L2102), [`ui/dr-ui/ui/app.slint:2388`](../../ui/dr-ui/ui/app.slint#L2388), [`ui/dr-ui/ui/app.slint:2550`](../../ui/dr-ui/ui/app.slint#L2550), [`ui/dr-ui/ui/app.slint:2580`](../../ui/dr-ui/ui/app.slint#L2580), [`ui/dr-ui/ui/app.slint:264`](../../ui/dr-ui/ui/app.slint#L264), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/app.slint:2925`](../../ui/dr-ui/ui/app.slint#L2925), [`ui/dr-ui/ui/app.slint:3481`](../../ui/dr-ui/ui/app.slint#L3481), [`ui/dr-ui/ui/app.slint:3508`](../../ui/dr-ui/ui/app.slint#L3508), [`ui/dr-ui/ui/app.slint:697`](../../ui/dr-ui/ui/app.slint#L697), [`ui/dr-ui/ui/collections.slint:1354`](../../ui/dr-ui/ui/collections.slint#L1354), [`ui/dr-ui/ui/collections.slint:78`](../../ui/dr-ui/ui/collections.slint#L78), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:332`](../../ui/dr-ui/ui/develop.slint#L332), [`ui/dr-ui/ui/develop.slint:66`](../../ui/dr-ui/ui/develop.slint#L66), [`ui/dr-ui/ui/duplicates.slint:1`](../../ui/dr-ui/ui/duplicates.slint#L1), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/icons.slint:367`](../../ui/dr-ui/ui/icons.slint#L367), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1334`](../../ui/dr-ui/ui/library.slint#L1334), [`ui/dr-ui/ui/library.slint:1361`](../../ui/dr-ui/ui/library.slint#L1361), [`ui/dr-ui/ui/library.slint:1367`](../../ui/dr-ui/ui/library.slint#L1367), [`ui/dr-ui/ui/library.slint:1422`](../../ui/dr-ui/ui/library.slint#L1422), [`ui/dr-ui/ui/library.slint:1617`](../../ui/dr-ui/ui/library.slint#L1617), [`ui/dr-ui/ui/library.slint:1668`](../../ui/dr-ui/ui/library.slint#L1668), [`ui/dr-ui/ui/library.slint:1691`](../../ui/dr-ui/ui/library.slint#L1691), [`ui/dr-ui/ui/library.slint:1698`](../../ui/dr-ui/ui/library.slint#L1698), [`ui/dr-ui/ui/library.slint:1734`](../../ui/dr-ui/ui/library.slint#L1734), [`ui/dr-ui/ui/library.slint:1792`](../../ui/dr-ui/ui/library.slint#L1792), [`ui/dr-ui/ui/library.slint:1799`](../../ui/dr-ui/ui/library.slint#L1799), [`ui/dr-ui/ui/library.slint:3135`](../../ui/dr-ui/ui/library.slint#L3135), [`ui/dr-ui/ui/library.slint:3240`](../../ui/dr-ui/ui/library.slint#L3240), [`ui/dr-ui/ui/library.slint:3391`](../../ui/dr-ui/ui/library.slint#L3391), [`ui/dr-ui/ui/library.slint:5038`](../../ui/dr-ui/ui/library.slint#L5038), [`ui/dr-ui/ui/library.slint:5076`](../../ui/dr-ui/ui/library.slint#L5076), [`ui/dr-ui/ui/library.slint:5180`](../../ui/dr-ui/ui/library.slint#L5180), [`ui/dr-ui/ui/library.slint:567`](../../ui/dr-ui/ui/library.slint#L567), [`ui/dr-ui/ui/library.slint:577`](../../ui/dr-ui/ui/library.slint#L577), [`ui/dr-ui/ui/library.slint:671`](../../ui/dr-ui/ui/library.slint#L671), [`ui/dr-ui/ui/library.slint:740`](../../ui/dr-ui/ui/library.slint#L740), [`ui/dr-ui/ui/settings.slint:121`](../../ui/dr-ui/ui/settings.slint#L121), [`ui/dr-ui/ui/settings.slint:172`](../../ui/dr-ui/ui/settings.slint#L172), [`ui/dr-ui/ui/settings.slint:635`](../../ui/dr-ui/ui/settings.slint#L635) |
-| FR-UI-5 | [`core/dr-types/src/place.rs:118`](../../core/dr-types/src/place.rs#L118), [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/develop_ui.rs:958`](../../ui/dr-ui/src/develop_ui.rs#L958), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/identity_ui.rs:1643`](../../ui/dr-ui/src/identity_ui.rs#L1643), [`ui/dr-ui/src/identity_ui.rs:802`](../../ui/dr-ui/src/identity_ui.rs#L802), [`ui/dr-ui/src/lib.rs:3405`](../../ui/dr-ui/src/lib.rs#L3405), [`ui/dr-ui/src/lib.rs:3423`](../../ui/dr-ui/src/lib.rs#L3423), [`ui/dr-ui/src/lib.rs:3437`](../../ui/dr-ui/src/lib.rs#L3437), [`ui/dr-ui/src/lib.rs:3618`](../../ui/dr-ui/src/lib.rs#L3618), [`ui/dr-ui/src/library/filters.rs:79`](../../ui/dr-ui/src/library/filters.rs#L79), [`ui/dr-ui/src/library_ui/filter_bar.rs:57`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L57), [`ui/dr-ui/src/library_ui/grid.rs:356`](../../ui/dr-ui/src/library_ui/grid.rs#L356), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1205`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1205), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1275`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1275), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/src/masks_ui.rs:1655`](../../ui/dr-ui/src/masks_ui.rs#L1655), [`ui/dr-ui/src/place.rs:331`](../../ui/dr-ui/src/place.rs#L331), [`ui/dr-ui/src/settings_ui.rs:394`](../../ui/dr-ui/src/settings_ui.rs#L394), [`ui/dr-ui/ui/app.slint:1089`](../../ui/dr-ui/ui/app.slint#L1089), [`ui/dr-ui/ui/app.slint:1673`](../../ui/dr-ui/ui/app.slint#L1673), [`ui/dr-ui/ui/app.slint:2388`](../../ui/dr-ui/ui/app.slint#L2388), [`ui/dr-ui/ui/app.slint:2538`](../../ui/dr-ui/ui/app.slint#L2538), [`ui/dr-ui/ui/app.slint:2680`](../../ui/dr-ui/ui/app.slint#L2680), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/app.slint:2778`](../../ui/dr-ui/ui/app.slint#L2778), [`ui/dr-ui/ui/app.slint:2809`](../../ui/dr-ui/ui/app.slint#L2809), [`ui/dr-ui/ui/app.slint:3446`](../../ui/dr-ui/ui/app.slint#L3446), [`ui/dr-ui/ui/app.slint:3465`](../../ui/dr-ui/ui/app.slint#L3465), [`ui/dr-ui/ui/app.slint:424`](../../ui/dr-ui/ui/app.slint#L424), [`ui/dr-ui/ui/app.slint:686`](../../ui/dr-ui/ui/app.slint#L686), [`ui/dr-ui/ui/app.slint:693`](../../ui/dr-ui/ui/app.slint#L693), [`ui/dr-ui/ui/app.slint:697`](../../ui/dr-ui/ui/app.slint#L697), [`ui/dr-ui/ui/app.slint:703`](../../ui/dr-ui/ui/app.slint#L703), [`ui/dr-ui/ui/app.slint:759`](../../ui/dr-ui/ui/app.slint#L759), [`ui/dr-ui/ui/collections.slint:111`](../../ui/dr-ui/ui/collections.slint#L111), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:246`](../../ui/dr-ui/ui/export.slint#L246), [`ui/dr-ui/ui/identity.slint:294`](../../ui/dr-ui/ui/identity.slint#L294), [`ui/dr-ui/ui/identity.slint:400`](../../ui/dr-ui/ui/identity.slint#L400), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/library.slint:1044`](../../ui/dr-ui/ui/library.slint#L1044), [`ui/dr-ui/ui/library.slint:1614`](../../ui/dr-ui/ui/library.slint#L1614), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:1628`](../../ui/dr-ui/ui/library.slint#L1628), [`ui/dr-ui/ui/library.slint:1640`](../../ui/dr-ui/ui/library.slint#L1640), [`ui/dr-ui/ui/library.slint:1888`](../../ui/dr-ui/ui/library.slint#L1888), [`ui/dr-ui/ui/library.slint:2018`](../../ui/dr-ui/ui/library.slint#L2018), [`ui/dr-ui/ui/library.slint:2658`](../../ui/dr-ui/ui/library.slint#L2658), [`ui/dr-ui/ui/library.slint:3101`](../../ui/dr-ui/ui/library.slint#L3101), [`ui/dr-ui/ui/library.slint:3177`](../../ui/dr-ui/ui/library.slint#L3177), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3202`](../../ui/dr-ui/ui/library.slint#L3202), [`ui/dr-ui/ui/library.slint:3209`](../../ui/dr-ui/ui/library.slint#L3209), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:3240`](../../ui/dr-ui/ui/library.slint#L3240), [`ui/dr-ui/ui/library.slint:3318`](../../ui/dr-ui/ui/library.slint#L3318), [`ui/dr-ui/ui/library.slint:3391`](../../ui/dr-ui/ui/library.slint#L3391), [`ui/dr-ui/ui/library.slint:3998`](../../ui/dr-ui/ui/library.slint#L3998), [`ui/dr-ui/ui/library.slint:5093`](../../ui/dr-ui/ui/library.slint#L5093), [`ui/dr-ui/ui/library.slint:647`](../../ui/dr-ui/ui/library.slint#L647), [`ui/dr-ui/ui/library.slint:665`](../../ui/dr-ui/ui/library.slint#L665), [`ui/dr-ui/ui/library.slint:686`](../../ui/dr-ui/ui/library.slint#L686), [`ui/dr-ui/ui/presets.slint:383`](../../ui/dr-ui/ui/presets.slint#L383), [`ui/dr-ui/ui/session.slint:75`](../../ui/dr-ui/ui/session.slint#L75) |
+| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:143`](../../ui/dr-ui/src/collections_ui/controller.rs#L143), [`ui/dr-ui/src/collections_ui/controller.rs:348`](../../ui/dr-ui/src/collections_ui/controller.rs#L348), [`ui/dr-ui/src/collections_ui/controller.rs:361`](../../ui/dr-ui/src/collections_ui/controller.rs#L361), [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/controller.rs:55`](../../ui/dr-ui/src/collections_ui/controller.rs#L55), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:278`](../../ui/dr-ui/src/collections_ui/press.rs#L278), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/tree_sync.rs:344`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:577`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L577), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/develop/framing.rs:257`](../../ui/dr-ui/src/develop/framing.rs#L257), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/framing.rs:619`](../../ui/dr-ui/src/develop/framing.rs#L619), [`ui/dr-ui/src/develop/framing.rs:630`](../../ui/dr-ui/src/develop/framing.rs#L630), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:818`](../../ui/dr-ui/src/develop/framing.rs#L818), [`ui/dr-ui/src/develop/framing.rs:849`](../../ui/dr-ui/src/develop/framing.rs#L849), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/framing.rs:995`](../../ui/dr-ui/src/develop/framing.rs#L995), [`ui/dr-ui/src/develop/render.rs:1111`](../../ui/dr-ui/src/develop/render.rs#L1111), [`ui/dr-ui/src/develop/render.rs:1136`](../../ui/dr-ui/src/develop/render.rs#L1136), [`ui/dr-ui/src/develop/render.rs:1159`](../../ui/dr-ui/src/develop/render.rs#L1159), [`ui/dr-ui/src/develop/render.rs:1198`](../../ui/dr-ui/src/develop/render.rs#L1198), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/render.rs:66`](../../ui/dr-ui/src/develop/render.rs#L66), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop_ui.rs:912`](../../ui/dr-ui/src/develop_ui.rs#L912), [`ui/dr-ui/src/duplicates_ui.rs:1`](../../ui/dr-ui/src/duplicates_ui.rs#L1), [`ui/dr-ui/src/gesture_book.rs:3`](../../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:127`](../../ui/dr-ui/src/gestures.rs#L127), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:1298`](../../ui/dr-ui/src/lib.rs#L1298), [`ui/dr-ui/src/lib.rs:1595`](../../ui/dr-ui/src/lib.rs#L1595), [`ui/dr-ui/src/lib.rs:2517`](../../ui/dr-ui/src/lib.rs#L2517), [`ui/dr-ui/src/lib.rs:2802`](../../ui/dr-ui/src/lib.rs#L2802), [`ui/dr-ui/src/lib.rs:2862`](../../ui/dr-ui/src/lib.rs#L2862), [`ui/dr-ui/src/lib.rs:3021`](../../ui/dr-ui/src/lib.rs#L3021), [`ui/dr-ui/src/lib.rs:3423`](../../ui/dr-ui/src/lib.rs#L3423), [`ui/dr-ui/src/lib.rs:3467`](../../ui/dr-ui/src/lib.rs#L3467), [`ui/dr-ui/src/lib.rs:497`](../../ui/dr-ui/src/lib.rs#L497), [`ui/dr-ui/src/lib.rs:526`](../../ui/dr-ui/src/lib.rs#L526), [`ui/dr-ui/src/library/thumbnails_fetch.rs:759`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L759), [`ui/dr-ui/src/library/thumbnails_fetch.rs:774`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L774), [`ui/dr-ui/src/library_ui/controller.rs:403`](../../ui/dr-ui/src/library_ui/controller.rs#L403), [`ui/dr-ui/src/library_ui/controller.rs:546`](../../ui/dr-ui/src/library_ui/controller.rs#L546), [`ui/dr-ui/src/library_ui/controller.rs:651`](../../ui/dr-ui/src/library_ui/controller.rs#L651), [`ui/dr-ui/src/library_ui/filter_bar.rs:500`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L500), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/library_ui/grid.rs:241`](../../ui/dr-ui/src/library_ui/grid.rs#L241), [`ui/dr-ui/src/library_ui/grid.rs:262`](../../ui/dr-ui/src/library_ui/grid.rs#L262), [`ui/dr-ui/src/library_ui/grid.rs:336`](../../ui/dr-ui/src/library_ui/grid.rs#L336), [`ui/dr-ui/src/library_ui/grid.rs:479`](../../ui/dr-ui/src/library_ui/grid.rs#L479), [`ui/dr-ui/src/library_ui/grid.rs:509`](../../ui/dr-ui/src/library_ui/grid.rs#L509), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/manual.rs:1`](../../ui/dr-ui/src/manual.rs#L1), [`ui/dr-ui/src/settings_ui.rs:121`](../../ui/dr-ui/src/settings_ui.rs#L121), [`ui/dr-ui/src/settings_ui.rs:508`](../../ui/dr-ui/src/settings_ui.rs#L508), [`ui/dr-ui/ui/app.slint:1809`](../../ui/dr-ui/ui/app.slint#L1809), [`ui/dr-ui/ui/app.slint:1976`](../../ui/dr-ui/ui/app.slint#L1976), [`ui/dr-ui/ui/app.slint:2102`](../../ui/dr-ui/ui/app.slint#L2102), [`ui/dr-ui/ui/app.slint:2388`](../../ui/dr-ui/ui/app.slint#L2388), [`ui/dr-ui/ui/app.slint:2550`](../../ui/dr-ui/ui/app.slint#L2550), [`ui/dr-ui/ui/app.slint:2580`](../../ui/dr-ui/ui/app.slint#L2580), [`ui/dr-ui/ui/app.slint:264`](../../ui/dr-ui/ui/app.slint#L264), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/app.slint:2925`](../../ui/dr-ui/ui/app.slint#L2925), [`ui/dr-ui/ui/app.slint:3481`](../../ui/dr-ui/ui/app.slint#L3481), [`ui/dr-ui/ui/app.slint:3508`](../../ui/dr-ui/ui/app.slint#L3508), [`ui/dr-ui/ui/app.slint:697`](../../ui/dr-ui/ui/app.slint#L697), [`ui/dr-ui/ui/collections.slint:1354`](../../ui/dr-ui/ui/collections.slint#L1354), [`ui/dr-ui/ui/collections.slint:78`](../../ui/dr-ui/ui/collections.slint#L78), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:332`](../../ui/dr-ui/ui/develop.slint#L332), [`ui/dr-ui/ui/develop.slint:66`](../../ui/dr-ui/ui/develop.slint#L66), [`ui/dr-ui/ui/duplicates.slint:1`](../../ui/dr-ui/ui/duplicates.slint#L1), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/icons.slint:367`](../../ui/dr-ui/ui/icons.slint#L367), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1334`](../../ui/dr-ui/ui/library.slint#L1334), [`ui/dr-ui/ui/library.slint:1361`](../../ui/dr-ui/ui/library.slint#L1361), [`ui/dr-ui/ui/library.slint:1367`](../../ui/dr-ui/ui/library.slint#L1367), [`ui/dr-ui/ui/library.slint:1422`](../../ui/dr-ui/ui/library.slint#L1422), [`ui/dr-ui/ui/library.slint:1617`](../../ui/dr-ui/ui/library.slint#L1617), [`ui/dr-ui/ui/library.slint:1668`](../../ui/dr-ui/ui/library.slint#L1668), [`ui/dr-ui/ui/library.slint:1691`](../../ui/dr-ui/ui/library.slint#L1691), [`ui/dr-ui/ui/library.slint:1698`](../../ui/dr-ui/ui/library.slint#L1698), [`ui/dr-ui/ui/library.slint:1734`](../../ui/dr-ui/ui/library.slint#L1734), [`ui/dr-ui/ui/library.slint:1792`](../../ui/dr-ui/ui/library.slint#L1792), [`ui/dr-ui/ui/library.slint:1799`](../../ui/dr-ui/ui/library.slint#L1799), [`ui/dr-ui/ui/library.slint:3135`](../../ui/dr-ui/ui/library.slint#L3135), [`ui/dr-ui/ui/library.slint:3240`](../../ui/dr-ui/ui/library.slint#L3240), [`ui/dr-ui/ui/library.slint:3391`](../../ui/dr-ui/ui/library.slint#L3391), [`ui/dr-ui/ui/library.slint:5038`](../../ui/dr-ui/ui/library.slint#L5038), [`ui/dr-ui/ui/library.slint:5076`](../../ui/dr-ui/ui/library.slint#L5076), [`ui/dr-ui/ui/library.slint:5180`](../../ui/dr-ui/ui/library.slint#L5180), [`ui/dr-ui/ui/library.slint:567`](../../ui/dr-ui/ui/library.slint#L567), [`ui/dr-ui/ui/library.slint:577`](../../ui/dr-ui/ui/library.slint#L577), [`ui/dr-ui/ui/library.slint:671`](../../ui/dr-ui/ui/library.slint#L671), [`ui/dr-ui/ui/library.slint:740`](../../ui/dr-ui/ui/library.slint#L740), [`ui/dr-ui/ui/settings.slint:121`](../../ui/dr-ui/ui/settings.slint#L121), [`ui/dr-ui/ui/settings.slint:172`](../../ui/dr-ui/ui/settings.slint#L172), [`ui/dr-ui/ui/settings.slint:635`](../../ui/dr-ui/ui/settings.slint#L635) |
+| FR-UI-5 | [`core/dr-types/src/place.rs:118`](../../core/dr-types/src/place.rs#L118), [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/develop_ui.rs:957`](../../ui/dr-ui/src/develop_ui.rs#L957), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/identity_ui.rs:1643`](../../ui/dr-ui/src/identity_ui.rs#L1643), [`ui/dr-ui/src/identity_ui.rs:802`](../../ui/dr-ui/src/identity_ui.rs#L802), [`ui/dr-ui/src/lib.rs:3405`](../../ui/dr-ui/src/lib.rs#L3405), [`ui/dr-ui/src/lib.rs:3423`](../../ui/dr-ui/src/lib.rs#L3423), [`ui/dr-ui/src/lib.rs:3437`](../../ui/dr-ui/src/lib.rs#L3437), [`ui/dr-ui/src/lib.rs:3618`](../../ui/dr-ui/src/lib.rs#L3618), [`ui/dr-ui/src/library/filters.rs:79`](../../ui/dr-ui/src/library/filters.rs#L79), [`ui/dr-ui/src/library_ui/filter_bar.rs:57`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L57), [`ui/dr-ui/src/library_ui/grid.rs:356`](../../ui/dr-ui/src/library_ui/grid.rs#L356), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1205`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1205), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1275`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1275), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/src/masks_ui.rs:1655`](../../ui/dr-ui/src/masks_ui.rs#L1655), [`ui/dr-ui/src/place.rs:331`](../../ui/dr-ui/src/place.rs#L331), [`ui/dr-ui/src/settings_ui.rs:394`](../../ui/dr-ui/src/settings_ui.rs#L394), [`ui/dr-ui/ui/app.slint:1089`](../../ui/dr-ui/ui/app.slint#L1089), [`ui/dr-ui/ui/app.slint:1673`](../../ui/dr-ui/ui/app.slint#L1673), [`ui/dr-ui/ui/app.slint:2388`](../../ui/dr-ui/ui/app.slint#L2388), [`ui/dr-ui/ui/app.slint:2538`](../../ui/dr-ui/ui/app.slint#L2538), [`ui/dr-ui/ui/app.slint:2680`](../../ui/dr-ui/ui/app.slint#L2680), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/app.slint:2778`](../../ui/dr-ui/ui/app.slint#L2778), [`ui/dr-ui/ui/app.slint:2809`](../../ui/dr-ui/ui/app.slint#L2809), [`ui/dr-ui/ui/app.slint:3446`](../../ui/dr-ui/ui/app.slint#L3446), [`ui/dr-ui/ui/app.slint:3465`](../../ui/dr-ui/ui/app.slint#L3465), [`ui/dr-ui/ui/app.slint:424`](../../ui/dr-ui/ui/app.slint#L424), [`ui/dr-ui/ui/app.slint:686`](../../ui/dr-ui/ui/app.slint#L686), [`ui/dr-ui/ui/app.slint:693`](../../ui/dr-ui/ui/app.slint#L693), [`ui/dr-ui/ui/app.slint:697`](../../ui/dr-ui/ui/app.slint#L697), [`ui/dr-ui/ui/app.slint:703`](../../ui/dr-ui/ui/app.slint#L703), [`ui/dr-ui/ui/app.slint:759`](../../ui/dr-ui/ui/app.slint#L759), [`ui/dr-ui/ui/collections.slint:111`](../../ui/dr-ui/ui/collections.slint#L111), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:246`](../../ui/dr-ui/ui/export.slint#L246), [`ui/dr-ui/ui/identity.slint:294`](../../ui/dr-ui/ui/identity.slint#L294), [`ui/dr-ui/ui/identity.slint:400`](../../ui/dr-ui/ui/identity.slint#L400), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/library.slint:1044`](../../ui/dr-ui/ui/library.slint#L1044), [`ui/dr-ui/ui/library.slint:1614`](../../ui/dr-ui/ui/library.slint#L1614), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:1628`](../../ui/dr-ui/ui/library.slint#L1628), [`ui/dr-ui/ui/library.slint:1640`](../../ui/dr-ui/ui/library.slint#L1640), [`ui/dr-ui/ui/library.slint:1888`](../../ui/dr-ui/ui/library.slint#L1888), [`ui/dr-ui/ui/library.slint:2018`](../../ui/dr-ui/ui/library.slint#L2018), [`ui/dr-ui/ui/library.slint:2658`](../../ui/dr-ui/ui/library.slint#L2658), [`ui/dr-ui/ui/library.slint:3101`](../../ui/dr-ui/ui/library.slint#L3101), [`ui/dr-ui/ui/library.slint:3177`](../../ui/dr-ui/ui/library.slint#L3177), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3202`](../../ui/dr-ui/ui/library.slint#L3202), [`ui/dr-ui/ui/library.slint:3209`](../../ui/dr-ui/ui/library.slint#L3209), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:3240`](../../ui/dr-ui/ui/library.slint#L3240), [`ui/dr-ui/ui/library.slint:3318`](../../ui/dr-ui/ui/library.slint#L3318), [`ui/dr-ui/ui/library.slint:3391`](../../ui/dr-ui/ui/library.slint#L3391), [`ui/dr-ui/ui/library.slint:3998`](../../ui/dr-ui/ui/library.slint#L3998), [`ui/dr-ui/ui/library.slint:5093`](../../ui/dr-ui/ui/library.slint#L5093), [`ui/dr-ui/ui/library.slint:647`](../../ui/dr-ui/ui/library.slint#L647), [`ui/dr-ui/ui/library.slint:665`](../../ui/dr-ui/ui/library.slint#L665), [`ui/dr-ui/ui/library.slint:686`](../../ui/dr-ui/ui/library.slint#L686), [`ui/dr-ui/ui/presets.slint:383`](../../ui/dr-ui/ui/presets.slint#L383), [`ui/dr-ui/ui/session.slint:75`](../../ui/dr-ui/ui/session.slint#L75) |
| FR-UI-6 | [`ui/dr-ui/ui/widgets.slint:1`](../../ui/dr-ui/ui/widgets.slint#L1) |
| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:195`](../../core/dr-pipeline/src/descriptor.rs#L195), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/lib.rs:3230`](../../ui/dr-ui/src/lib.rs#L3230), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:2226`](../../ui/dr-ui/ui/app.slint#L2226), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89) |
| FR-UI-8 | [`core/dr-catalog/src/collections.rs:654`](../../core/dr-catalog/src/collections.rs#L654), [`core/dr-catalog/src/collections.rs:675`](../../core/dr-catalog/src/collections.rs#L675), [`core/dr-types/src/place.rs:1`](../../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/derived_sync.rs:226`](../../ui/dr-ui/src/derived_sync.rs#L226), [`ui/dr-ui/src/derived_sync.rs:84`](../../ui/dr-ui/src/derived_sync.rs#L84), [`ui/dr-ui/src/derived_sync.rs:905`](../../ui/dr-ui/src/derived_sync.rs#L905), [`ui/dr-ui/src/derived_sync.rs:915`](../../ui/dr-ui/src/derived_sync.rs#L915), [`ui/dr-ui/src/derived_sync.rs:997`](../../ui/dr-ui/src/derived_sync.rs#L997), [`ui/dr-ui/src/library/cells.rs:266`](../../ui/dr-ui/src/library/cells.rs#L266), [`ui/dr-ui/src/library/cells.rs:545`](../../ui/dr-ui/src/library/cells.rs#L545), [`ui/dr-ui/src/library/cells.rs:611`](../../ui/dr-ui/src/library/cells.rs#L611), [`ui/dr-ui/src/library/paths.rs:22`](../../ui/dr-ui/src/library/paths.rs#L22), [`ui/dr-ui/src/library_ui/controller.rs:1019`](../../ui/dr-ui/src/library_ui/controller.rs#L1019), [`ui/dr-ui/src/library_ui/controller.rs:210`](../../ui/dr-ui/src/library_ui/controller.rs#L210), [`ui/dr-ui/src/library_ui/filter_bar.rs:793`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L793), [`ui/dr-ui/src/library_ui/grid.rs:172`](../../ui/dr-ui/src/library_ui/grid.rs#L172), [`ui/dr-ui/src/library_ui/grid.rs:285`](../../ui/dr-ui/src/library_ui/grid.rs#L285), [`ui/dr-ui/src/library_ui/grid.rs:327`](../../ui/dr-ui/src/library_ui/grid.rs#L327), [`ui/dr-ui/src/library_ui/grid.rs:668`](../../ui/dr-ui/src/library_ui/grid.rs#L668), [`ui/dr-ui/src/library_ui/grid.rs:794`](../../ui/dr-ui/src/library_ui/grid.rs#L794), [`ui/dr-ui/src/library_ui/open.rs:118`](../../ui/dr-ui/src/library_ui/open.rs#L118), [`ui/dr-ui/src/library_ui/open.rs:36`](../../ui/dr-ui/src/library_ui/open.rs#L36), [`ui/dr-ui/src/library_ui/open.rs:71`](../../ui/dr-ui/src/library_ui/open.rs#L71), [`ui/dr-ui/src/library_ui/open.rs:842`](../../ui/dr-ui/src/library_ui/open.rs#L842), [`ui/dr-ui/src/library_ui/open.rs:859`](../../ui/dr-ui/src/library_ui/open.rs#L859), [`ui/dr-ui/src/library_ui/open.rs:957`](../../ui/dr-ui/src/library_ui/open.rs#L957), [`ui/dr-ui/src/place.rs:1`](../../ui/dr-ui/src/place.rs#L1) |
@@ -170,7 +170,7 @@ _None._
| NFR-P13 | [`core/dr-decode/src/preview.rs:121`](../../core/dr-decode/src/preview.rs#L121) |
| NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:409`](../../ui/dr-ui/src/develop/render.rs#L409), [`ui/dr-ui/src/lib.rs:2299`](../../ui/dr-ui/src/lib.rs#L2299) |
| NFR-P3 | [`tools/bench/src/thumbnails.rs:1`](../../tools/bench/src/thumbnails.rs#L1) |
-| NFR-P5 | [`core/dr-catalog/src/schema.rs:594`](../../core/dr-catalog/src/schema.rs#L594), [`core/dr-gpu/src/adjust.rs:176`](../../core/dr-gpu/src/adjust.rs#L176), [`core/dr-gpu/src/adjust.rs:2931`](../../core/dr-gpu/src/adjust.rs#L2931), [`core/dr-pipeline/src/detail.rs:1268`](../../core/dr-pipeline/src/detail.rs#L1268), [`core/dr-pipeline/src/detail.rs:667`](../../core/dr-pipeline/src/detail.rs#L667), [`ui/dr-ui/src/library/cells.rs:374`](../../ui/dr-ui/src/library/cells.rs#L374), [`ui/dr-ui/src/library/cells.rs:715`](../../ui/dr-ui/src/library/cells.rs#L715), [`ui/dr-ui/src/library_ui/controller.rs:58`](../../ui/dr-ui/src/library_ui/controller.rs#L58), [`ui/dr-ui/src/library_ui/timeline.rs:382`](../../ui/dr-ui/src/library_ui/timeline.rs#L382) |
+| NFR-P5 | [`core/dr-catalog/src/schema.rs:594`](../../core/dr-catalog/src/schema.rs#L594), [`core/dr-gpu/src/adjust.rs:176`](../../core/dr-gpu/src/adjust.rs#L176), [`core/dr-gpu/src/adjust.rs:2951`](../../core/dr-gpu/src/adjust.rs#L2951), [`core/dr-pipeline/src/detail.rs:1268`](../../core/dr-pipeline/src/detail.rs#L1268), [`core/dr-pipeline/src/detail.rs:667`](../../core/dr-pipeline/src/detail.rs#L667), [`ui/dr-ui/src/library/cells.rs:374`](../../ui/dr-ui/src/library/cells.rs#L374), [`ui/dr-ui/src/library/cells.rs:715`](../../ui/dr-ui/src/library/cells.rs#L715), [`ui/dr-ui/src/library_ui/controller.rs:58`](../../ui/dr-ui/src/library_ui/controller.rs#L58), [`ui/dr-ui/src/library_ui/timeline.rs:382`](../../ui/dr-ui/src/library_ui/timeline.rs#L382) |
| NFR-P9 | [`core/dr-catalog/src/backfilled.rs:1`](../../core/dr-catalog/src/backfilled.rs#L1), [`core/dr-catalog/src/schema.rs:947`](../../core/dr-catalog/src/schema.rs#L947), [`core/dr-face/src/references.rs:1`](../../core/dr-face/src/references.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/duplicates.rs:1`](../../ui/dr-ui/src/duplicates.rs#L1), [`ui/dr-ui/src/duplicates_ui.rs:1`](../../ui/dr-ui/src/duplicates_ui.rs#L1), [`ui/dr-ui/src/export.rs:1119`](../../ui/dr-ui/src/export.rs#L1119), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.rs#L1), [`ui/dr-ui/src/library_ui/mod.rs:1`](../../ui/dr-ui/src/library_ui/mod.rs#L1), [`ui/dr-ui/src/trash.rs:1`](../../ui/dr-ui/src/trash.rs#L1) |
| NFR-PORT-1 | [`core/dr-catalog/src/walk.rs:1`](../../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:272`](../../core/dr-types/src/lib.rs#L272), [`core/dr-types/src/lib.rs:305`](../../core/dr-types/src/lib.rs#L305), [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1), [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/storage.rs:1`](../../platform/dr-plat/src/storage.rs#L1), [`platform/dr-plat/src/storage.rs:216`](../../platform/dr-plat/src/storage.rs#L216), [`platform/dr-plat/src/storage.rs:287`](../../platform/dr-plat/src/storage.rs#L287), [`platform/dr-plat/src/storage.rs:344`](../../platform/dr-plat/src/storage.rs#L344), [`platform/dr-plat/src/volumes.rs:1`](../../platform/dr-plat/src/volumes.rs#L1), [`platform/dr-plat/src/volumes.rs:62`](../../platform/dr-plat/src/volumes.rs#L62) |
| NFR-PORT-2 | [`core/dr-gpu/src/lib.rs:1`](../../core/dr-gpu/src/lib.rs#L1) |
@@ -183,7 +183,7 @@ _None._
| NFR-R6 | [`core/dr-catalog/src/error.rs:1`](../../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/lib.rs:270`](../../core/dr-catalog/src/lib.rs#L270), [`core/dr-catalog/src/recovery.rs:1`](../../core/dr-catalog/src/recovery.rs#L1) |
| NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../../core/dr-gpu/src/error.rs#L1) |
| NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../../core/dr-gpu/src/error.rs#L1) |
-| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1322`](../../core/dr-gpu/src/adjust.rs#L1322), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:115`](../../ui/dr-ui/src/lib.rs#L115), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) |
+| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1340`](../../core/dr-gpu/src/adjust.rs#L1340), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:115`](../../ui/dr-ui/src/lib.rs#L115), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) |
| NFR-RES-2 | [`ui/dr-ui/src/faces.rs:462`](../../ui/dr-ui/src/faces.rs#L462), [`ui/dr-ui/src/library/sweep.rs:242`](../../ui/dr-ui/src/library/sweep.rs#L242), [`ui/dr-ui/src/repairs.rs:1149`](../../ui/dr-ui/src/repairs.rs#L1149), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1) |
| NFR-RES-4 | [`core/dr-catalog/src/cache.rs:1`](../../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/face_shard.rs:1`](../../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/schema.rs:1212`](../../core/dr-catalog/src/schema.rs#L1212), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`core/dr-thumbs/src/codec.rs:1`](../../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../../core/dr-thumbs/src/lib.rs#L1), [`core/dr-thumbs/src/lib.rs:393`](../../core/dr-thumbs/src/lib.rs#L393) |
| NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../../core/dr-decode/src/error.rs#L1), [`core/dr-decode/src/error.rs:30`](../../core/dr-decode/src/error.rs#L30) |
@@ -221,7 +221,7 @@ Defined in `requirements.md` and marked `(post-v1)` on the defining line. Not in
- FR-PLG-6
- FR-PLG-6a
- FR-PLG-7
-- FR-PLG-8 — tagged in [`core/dr-pipeline/src/sidecar.rs:2386`](../../core/dr-pipeline/src/sidecar.rs#L2386), [`core/dr-pipeline/src/sidecar.rs:2419`](../../core/dr-pipeline/src/sidecar.rs#L2419)
+- FR-PLG-8 — tagged in [`core/dr-pipeline/src/sidecar.rs:2388`](../../core/dr-pipeline/src/sidecar.rs#L2388), [`core/dr-pipeline/src/sidecar.rs:2421`](../../core/dr-pipeline/src/sidecar.rs#L2421)
- FR-PLG-9
- NFR-SEC-6
diff --git a/docs/manual/README.md b/docs/manual/README.md
index 5d01028..04b552e 100644
--- a/docs/manual/README.md
+++ b/docs/manual/README.md
@@ -268,6 +268,12 @@ 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
+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.
+

### History, snapshots, presets
diff --git a/docs/manual/index.html b/docs/manual/index.html
index fbd47ff..55358b2 100644
--- a/docs/manual/index.html
+++ b/docs/manual/index.html
@@ -327,6 +327,11 @@ 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 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
+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.
Opening the film list, scrolling it, choosing Velvia, then holding Before
History, snapshots, presets
Every change is a step; Undo and the History panel walk them. Snapshot
diff --git a/ui/dr-ui/src/develop/render.rs b/ui/dr-ui/src/develop/render.rs
index a67544c..62fb2a2 100644
--- a/ui/dr-ui/src/develop/render.rs
+++ b/ui/dr-ui/src/develop/render.rs
@@ -749,77 +749,103 @@ impl DevelopSession {
None
};
// TRACES: FR-DEV-3f
- // Grain, at the scale this photograph is being sampled at.
- //
- // A digital frame has no film format, so simulating one means choosing
- // what it *would have been* — 35 mm, because that is the format every
- // published granularity figure and every intuition about how grainy a
- // stock looks comes from. The sensor's width in pixels then says how
- // much film one pixel covers, and the grain model needs nothing else
- // to be correct at any zoom.
- // TRACES: FR-DEV-3f
- // The frame this is being simulated on, against the pixels it is being
- // rendered to: together they are the enlargement, and the enlargement
- // is what decides how grainy the result looks. A crystal is a fixed
- // size in micrometres — the same emulsion on a sheet averages far more
- // of them into each pixel than it does on 35 mm.
- let format = dr_film::Format::from_index(
- self.graph
- .param(
- dr_pipeline::ops::film_sim::ID,
- dr_pipeline::ops::film_sim::FORMAT,
- )
- .unwrap_or(0.0)
- .max(0.0) as usize,
- );
- let (source_width, _) = self.demosaiced.size();
- let pixel_size_um = format.width_um() / source_width.max(1) as f32;
- let grain = dr_film::Grain::for_pixel_size(profile, pixel_size_um);
+ // The photograph's exposure, which is where the enlarger is balanced.
+ // Nothing else a slider moves is baked: push, print exposure and the
+ // format are per-pixel settings the shader applies, so that a mask
+ // layer can hold its own.
+ let exposure_ev = self
+ .graph
+ .param(
+ dr_pipeline::ops::film_sim::ID,
+ dr_pipeline::ops::film_sim::EXPOSURE,
+ )
+ .unwrap_or(0.0);
- let baked = dr_film::bake(&dr_film::Recipe {
- film: profile,
- print: paper,
- exposure_ev: self
- .graph
- .param(
- dr_pipeline::ops::film_sim::ID,
- dr_pipeline::ops::film_sim::EXPOSURE,
- )
- .unwrap_or(0.0),
- push_stops: self
- .graph
- .param(
- dr_pipeline::ops::film_sim::ID,
- dr_pipeline::ops::film_sim::PUSH,
- )
- .unwrap_or(0.0),
- print_exposure_ev: self
- .graph
- .param(
- dr_pipeline::ops::film_sim::ID,
- dr_pipeline::ops::film_sim::PRINT_EXPOSURE,
- )
- .unwrap_or(0.0),
+ // Only the balance moves when the same stock and paper are chosen
+ // again — which is what a moved exposure does — and it is three
+ // numbers against a bake of two spectral lookups.
+ let same = self.graph.film().filter(|f| {
+ f.stock == profile.stock && f.print.as_deref() == paper.map(|p| p.stock.as_str())
});
+ let tables = match same {
+ Some(f) => {
+ let mut tables = f.tables.clone();
+ if let (Some(held), Some(paper)) = (tables.paper.as_mut(), paper) {
+ held.balance = dr_film::bake::print_balance(profile, paper, exposure_ev);
+ }
+ tables
+ }
+ None => self.bake_film(profile, paper, exposure_ev),
+ };
self.set_film(Some(dr_pipeline::graph::Film {
stock: profile.stock.clone(),
print: paper.map(|p| p.stock.clone()),
- tables: dr_pipeline::ops::FilmTables {
- exposure_matrix: baked.exposure_matrix,
- curves: baked.curves,
- curve_log_min: baked.curve_log_min,
- curve_log_max: baked.curve_log_max,
- lut: baked.lut,
- density_max: baked.density_max,
- lut_size: baked.lut_size,
- grain_particles: grain.particles,
- grain_density_max: grain.density_max,
- grain_uniformity: grain.uniformity,
- },
+ tables,
}));
}
+ /// TRACES: FR-DEV-3f
+ /// A stock and its paper as the shader binds them.
+ ///
+ /// The paper, when there is one, rides behind the film: its curve as one
+ /// more row, its lookup stacked after the film's. See `FilmTables`.
+ fn bake_film(
+ &self,
+ profile: &dr_film::Profile,
+ paper: Option<&dr_film::Profile>,
+ exposure_ev: f32,
+ ) -> dr_pipeline::ops::FilmTables {
+ // TRACES: FR-DEV-3f
+ // Grain, at the scale this photograph is being sampled at, on every
+ // frame it could be simulated on.
+ //
+ // A digital frame has no film format, so simulating one means choosing
+ // what it *would have been*. A crystal is a fixed size in micrometres,
+ // so the sensor's width in pixels against the frame's width says how
+ // much film one pixel covers — and the same emulsion on a sheet
+ // averages far more of them into each pixel than it does on 35 mm.
+ // All six are baked because the format is a per-pixel setting: a
+ // layer may be on another one.
+ let (source_width, _) = self.demosaiced.size();
+ let grains = dr_film::Format::ALL.map(|format| {
+ let pixel_size_um = format.width_um() / source_width.max(1) as f32;
+ dr_film::Grain::for_pixel_size(profile, pixel_size_um)
+ });
+
+ let baked = dr_film::bake(&dr_film::Recipe {
+ film: profile,
+ print: paper,
+ exposure_ev,
+ });
+ let mut curves = baked.curves;
+ let mut lut = baked.lut;
+ let paper = baked.paper.map(|p| {
+ curves.extend_from_slice(&p.curves);
+ lut.extend_from_slice(&p.lut);
+ dr_pipeline::ops::PaperTables {
+ balance: p.balance,
+ log_min: p.log_min,
+ log_max: p.log_max,
+ density_max: p.density_max,
+ }
+ });
+ dr_pipeline::ops::FilmTables {
+ exposure_matrix: baked.exposure_matrix,
+ curves,
+ push_stations: baked.push_stations,
+ curve_log_min: baked.curve_log_min,
+ curve_log_max: baked.curve_log_max,
+ lut,
+ density_max: baked.density_max,
+ lut_size: baked.lut_size,
+ paper,
+ grain_particles: grains.map(|g| g.particles),
+ grain_density_max: grains[0].density_max,
+ grain_uniformity: grains[0].uniformity,
+ }
+ }
+
/// The stock and paper currently chosen, by id.
pub fn film(&self) -> Option<(&str, bool)> {
self.graph
@@ -827,33 +853,51 @@ impl DevelopSession {
.map(|f| (f.stock.as_str(), f.print.is_some()))
}
- /// Re-bake if `op_index` names the film, and do nothing otherwise.
+ /// TRACES: FR-DEV-3f
+ /// Re-balance the enlarger if `op_index`/`param_index` name the
+ /// photograph's film exposure, and do nothing otherwise.
///
- /// `op_index` counts over [`Self::scoped_capabilities`] — the same list
- /// [`Self::lookup`] resolves a slider through — so that is the only list to
- /// ask. An earlier version also indexed `rows()`, which is one entry per
- /// *parameter* and filtered by the active tab: past its end the check
+ /// The one film slider the tables depend on: the print balance is solved
+ /// against the photograph's exposure, as an enlarger's filtration is set
+ /// for the negative in front of it. Everything else the film's sliders
+ /// move — push, print exposure, format, and exposure on a mask layer — is
+ /// a per-pixel setting the shader reads, and needs nothing from here.
+ ///
+ /// Both indices count over [`Self::scoped_capabilities`] — the same list
+ /// [`Self::lookup`] resolves a slider through — so that is the only list
+ /// to ask. An earlier version also indexed `rows()`, which is one entry
+ /// per *parameter* and filtered by the active tab: past its end the check
/// short-circuited, the tables were never rebuilt, and the film's own
/// sliders moved nothing at all.
///
/// The test is here rather than at the call site so the callback in
/// `lib.rs` goes on naming no operation, which is the rule the whole panel
/// is built on (ARCH §4.3a).
- pub fn rebake_film_if_affected(&mut self, op_index: i32) {
- let is_film = usize::try_from(op_index)
+ pub fn rebake_film_if_affected(&mut self, op_index: i32, param_index: i32) {
+ if self.active_layer().is_some() {
+ return;
+ }
+ let caps = self.scoped_capabilities();
+ let Some(op) = usize::try_from(op_index).ok().and_then(|i| caps.get(i)) else {
+ return;
+ };
+ let param = usize::try_from(param_index)
.ok()
- .and_then(|i| self.scoped_capabilities().get(i).map(|c| c.id))
- .is_some_and(|id| id == dr_pipeline::ops::film_sim::ID);
- if is_film {
+ .and_then(|i| op.params.get(i))
+ .map(|p| p.id);
+ if op.id == dr_pipeline::ops::film_sim::ID
+ && param == Some(dr_pipeline::ops::film_sim::EXPOSURE)
+ && self.graph.film().is_some_and(|f| f.print.is_some())
+ {
self.rebake_film();
}
}
- /// The stock, the paper, and how far it was developed.
+ /// The stock and the paper again, at the photograph's current exposure.
///
- /// Push rides with the other two through every path that re-bakes, because
- /// it is the same kind of fact: a decision about the material rather than
- /// an adjustment to the picture it produced.
+ /// Only the enlarger's balance changes when they are the same stock and
+ /// paper, so this is cheap on the path a slider takes; see
+ /// [`Self::choose_film`].
pub fn rebake_film(&mut self) {
if let Some((stock, print)) = self.film().map(|(s, p)| (s.to_string(), p)) {
self.choose_film(Some(&stock), print);
@@ -1249,6 +1293,90 @@ mod tests {
}
}
+ /// TRACES: FR-DEV-3f
+ /// A mask layer offers the film's settings and not the stock.
+ ///
+ /// The layer holds offsets to the photograph's film, which the shader
+ /// blends per pixel, so its sliders belong on it. The stock does not: it
+ /// is what the whole photograph was made on, and a picker in the layer's
+ /// panel would change it for every pixel.
+ #[test]
+ fn a_mask_layer_offers_film_settings_but_not_a_stock() {
+ let Some(ctx) = headless() else { return };
+ let (mut session, _) = grey_session(&ctx);
+ session.set_active_tab(-1);
+ assert!(
+ session.film_in_group(),
+ "the photograph has a stock to pick"
+ );
+
+ let id = session.add_gradient_mask(true).expect("radial");
+ session.set_active_mask(Some(&id));
+ assert!(!session.film_in_group(), "a layer has no stock of its own");
+ assert!(
+ session
+ .scoped_capabilities()
+ .iter()
+ .any(|c| c.id == dr_pipeline::ops::film_sim::ID),
+ "but it has the film's settings"
+ );
+
+ session.set_active_mask(None);
+ assert!(
+ session.film_in_group(),
+ "and the stock returns with the photograph"
+ );
+ }
+
+ /// TRACES: FR-DEV-3f
+ /// Only the photograph's exposure touches the tables, and only the
+ /// balance in them.
+ ///
+ /// Everything else a film slider moves is a per-pixel setting. Re-baking
+ /// for those would cost two spectral lookups per tick of a slider for
+ /// nothing, and re-baking for a layer's exposure would rebalance the whole
+ /// print around one region.
+ #[test]
+ fn a_film_slider_rebalances_only_where_the_balance_depends_on_it() {
+ use dr_pipeline::ops::film_sim;
+ let Some(ctx) = headless() else { return };
+ let (mut session, _) = grey_session(&ctx);
+ session.set_active_tab(-1);
+ session.choose_film(Some("kodak_portra_400"), true);
+ let balance = |s: &DevelopSession| {
+ s.graph
+ .film()
+ .and_then(|f| f.tables.paper)
+ .map(|p| p.balance)
+ };
+ let before = balance(&session).expect("a printed negative has a balance");
+
+ let caps = session.scoped_capabilities();
+ let op = caps
+ .iter()
+ .position(|c| c.id == film_sim::ID)
+ .expect("film");
+ let param = |id| caps[op].params.iter().position(|p| p.id == id).unwrap() as i32;
+
+ session.graph.set_param(film_sim::ID, film_sim::PUSH, 1.0);
+ session.rebake_film_if_affected(op as i32, param(film_sim::PUSH));
+ assert_eq!(
+ balance(&session),
+ Some(before),
+ "push is not the enlarger's business"
+ );
+
+ session
+ .graph
+ .set_param(film_sim::ID, film_sim::EXPOSURE, 1.0);
+ session.rebake_film_if_affected(op as i32, param(film_sim::EXPOSURE));
+ assert_ne!(
+ balance(&session),
+ Some(before),
+ "the balance follows the exposure"
+ );
+ }
+
/// A frame black on the left half and white on the right, at `size`
/// square. Both ends of the histogram are occupied and both clipping
/// counters are non-zero, and cropping to one half leaves exactly one of
diff --git a/ui/dr-ui/src/develop/tabs.rs b/ui/dr-ui/src/develop/tabs.rs
index 6cf3ba9..d3deabe 100644
--- a/ui/dr-ui/src/develop/tabs.rs
+++ b/ui/dr-ui/src/develop/tabs.rs
@@ -90,7 +90,15 @@ impl DevelopSession {
/// the operation — what is this control *about* — and the panel is not
/// allowed to know. It asks the descriptor, so a stock that were ever
/// re-declared as something other than an effect would move on its own.
+ ///
+ /// Never on a mask layer. A layer holds the film's *settings* — a region
+ /// pushed further, or burned in under the enlarger — but the stock is what
+ /// the whole photograph was made on, and a picker in a layer's panel would
+ /// change it for every pixel while looking as though it changed some.
pub fn film_in_group(&self) -> bool {
+ if self.active_layer().is_some() {
+ return false;
+ }
let Some(active) = self.active_tab else {
// "All" shows everything, the stock included.
return true;
diff --git a/ui/dr-ui/src/develop_ui.rs b/ui/dr-ui/src/develop_ui.rs
index 1b2b007..79aee35 100644
--- a/ui/dr-ui/src/develop_ui.rs
+++ b/ui/dr-ui/src/develop_ui.rs
@@ -445,12 +445,11 @@ fn wire_adjustments(window: &AppWindow, w: &DevelopWiring) {
if let Some(s) = session.borrow_mut().as_mut() {
s.set_param(op, param, value);
// TRACES: FR-DEV-3f
- // The film's own exposures ride *inside* the baked tables
- // rather than arriving as uniforms, because the print balance
- // is solved against them — an enlarger's filtration depends on
- // how the negative was exposed. So moving one has to rebuild
- // the lookup, which no other slider in the panel does.
- s.rebake_film_if_affected(op);
+ // The print balance is solved against the photograph's
+ // exposure — an enlarger's filtration depends on how the
+ // negative was exposed — so moving it re-solves the
+ // balance, which no other slider in the panel does.
+ s.rebake_film_if_affected(op, param);
}
sync_rows(&w, &rows, &session);
redraw(&w);
@@ -506,7 +505,7 @@ fn wire_adjustments(window: &AppWindow, w: &DevelopWiring) {
(row.value + direction.signum() as f32 * step).clamp(row.minimum, row.maximum);
if let Some(s) = session.borrow_mut().as_mut() {
s.set_param(op, param, value);
- s.rebake_film_if_affected(op);
+ s.rebake_film_if_affected(op, param);
}
sync_rows(&w, &rows, &session);
redraw(&w);