Merge remote-tracking branch 'origin/master' into worktree-spot-removal
🐳 Android image / Build and push (push) Successful in 2s
Build and test / android-image (push) Successful in 2s
Build and test / Desktop (Linux) (push) Successful in 20m9s
Build and test / Layer separation (push) Successful in 39s
Traceability / Requirement traces (push) Successful in 26s
Build and test / Android (aarch64) (push) Failing after 33m10s
🐳 Android image / Build and push (push) Successful in 2s
Build and test / android-image (push) Successful in 2s
Build and test / Desktop (Linux) (push) Successful in 20m9s
Build and test / Layer separation (push) Successful in 39s
Traceability / Requirement traces (push) Successful in 26s
Build and test / Android (aarch64) (push) Failing after 33m10s
# Conflicts: # docs/traceability.md
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
//! TRACES: FR-DEV-3f
|
||||
//! Grain as a Boolean model — grains that overlap, rather than noise that does
|
||||
//! not.
|
||||
//!
|
||||
//! # Why the counting model was not enough
|
||||
//!
|
||||
//! [`crate::grain`] gets the *variance* of a developed density right: a count
|
||||
//! of independent yes/no events, `D(Dmax − uD)/N`, calibrated from published
|
||||
//! granularity. What it cannot get right is the **structure**, because it
|
||||
//! treats every pixel as an independent draw.
|
||||
//!
|
||||
//! Measured at 35 mm, a pixel of a 5472-wide frame covers about 6.6 µm and
|
||||
//! holds some 300 crystals of ~370 nm. Three hundred independent events per
|
||||
//! pixel average almost flat, and the little that survives has no spatial
|
||||
//! extent — which is exactly why it reads as sensor noise rather than as film.
|
||||
//!
|
||||
//! Real grain is visible because it *clumps*. A crystal is far smaller than a
|
||||
//! pixel, but crystals overlap into structures that are not, and those survive
|
||||
//! the filtering that averages independent noise away.
|
||||
//!
|
||||
//! # The model
|
||||
//!
|
||||
//! Newson, Delon & Galerne, *A Stochastic Film Grain Model for
|
||||
//! Resolution-Independent Rendering* (Computer Graphics Forum, 2017).
|
||||
//!
|
||||
//! Grain centres are a Poisson process of local intensity `λ(y)`; each centre
|
||||
//! carries a disc. The developed film is the **union** of those discs, and a
|
||||
//! point is opaque exactly when some disc covers it. Because a disc covers a
|
||||
//! whole neighbourhood, nearby points are *correlated* — and that correlation
|
||||
//! is the clumping, which arrives for free rather than being added.
|
||||
//!
|
||||
//! # Why it couples to density and not to a grey level
|
||||
//!
|
||||
//! The paper drives `λ` from an image's grey level, because it renders grain
|
||||
//! onto a finished picture. We are not doing that: we have a *density* per
|
||||
//! layer, from a measured characteristic curve, and a dye that absorbs through
|
||||
//! it.
|
||||
//!
|
||||
//! The two meet exactly. In a Boolean model the chance a point is left
|
||||
//! uncovered is
|
||||
//!
|
||||
//! ```text
|
||||
//! P(uncovered) = exp(−λ · E[A])
|
||||
//! ```
|
||||
//!
|
||||
//! which is Beer–Lambert. So the model's coverage *is* optical density, and
|
||||
//!
|
||||
//! ```text
|
||||
//! λ = D · ln(10) / E[A]
|
||||
//! ```
|
||||
//!
|
||||
//! puts our measured densities straight into it — with `E[A]`, the mean grain
|
||||
//! area, already computed from published RMS granularity in
|
||||
//! [`crate::grain`]. Nothing here is tuned by eye.
|
||||
//!
|
||||
//! # What it costs
|
||||
//!
|
||||
//! Monte Carlo per pixel, against the counting model's two hashes and a square
|
||||
//! root. The shader form stores no grains: space is cut into cells, a
|
||||
//! generator is seeded from each cell's index, and only the cells a sample
|
||||
//! could reach are visited. That keeps it inside the fused pass — no
|
||||
//! neighbouring *pixel* is read — but it is emphatically not free, and
|
||||
//! [`BooleanGrain::samples_for`] is where that trade is made explicit.
|
||||
|
||||
use crate::grain::Grain;
|
||||
|
||||
/// Mean grain area, in µm², recovered from the counting model's calibration.
|
||||
///
|
||||
/// The two models are the same emulsion seen two ways, so they must not
|
||||
/// disagree about how big a crystal is: `Grain` already inverts published RMS
|
||||
/// granularity for exactly this number, and taking it from there is what stops
|
||||
/// a Boolean render and a counting render describing different films.
|
||||
pub fn mean_grain_area_um2(grain: &Grain, pixel_size_um: f32) -> [f32; 3] {
|
||||
let pixel_area = (pixel_size_um * pixel_size_um).max(1e-6);
|
||||
grain.particles.map(|n| pixel_area / n.max(1e-6))
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-3f
|
||||
/// What the shader needs to render the Boolean model.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct BooleanGrain {
|
||||
/// Grain radius per layer, in *pixels* at the current sampling scale.
|
||||
///
|
||||
/// In pixels rather than micrometres because that is the unit the shader
|
||||
/// works in, and converting once here keeps the conversion out of the
|
||||
/// inner loop.
|
||||
pub radius_px: [f32; 3],
|
||||
/// `ln(10) / E[A]`, per layer: the factor taking a density to a Poisson
|
||||
/// intensity. Precomputed because it is constant per bake and the shader
|
||||
/// would otherwise recompute a logarithm per pixel per layer.
|
||||
pub lambda_per_density: [f32; 3],
|
||||
/// The density each layer saturates at, as in the counting model.
|
||||
pub density_max: [f32; 3],
|
||||
/// Monte Carlo samples per pixel.
|
||||
pub samples: u32,
|
||||
/// Standard deviation of the sampling kernel, in pixels.
|
||||
///
|
||||
/// The pixel's own footprint: what a scanner or an eye integrates over.
|
||||
/// Too small and the render is binary salt and pepper; too large and the
|
||||
/// grain is blurred out of existence.
|
||||
pub sigma_px: f32,
|
||||
}
|
||||
|
||||
impl BooleanGrain {
|
||||
/// Derive the parameters for a stock at a given sampling scale.
|
||||
pub fn new(grain: &Grain, pixel_size_um: f32, samples: u32) -> Self {
|
||||
let area = mean_grain_area_um2(grain, pixel_size_um);
|
||||
let pixel_area = (pixel_size_um * pixel_size_um).max(1e-6);
|
||||
|
||||
let mut radius_px = [0.0f32; 3];
|
||||
let mut lambda_per_density = [0.0f32; 3];
|
||||
for l in 0..3 {
|
||||
// A disc of this area, expressed as a fraction of a pixel.
|
||||
let area_px = area[l] / pixel_area;
|
||||
radius_px[l] = (area_px / std::f32::consts::PI).sqrt();
|
||||
// Beer-Lambert, read backwards: coverage exp(-lambda*E[A]) is
|
||||
// transmittance 10^-D, so lambda = D * ln(10) / E[A].
|
||||
lambda_per_density[l] = std::f32::consts::LN_10 / area_px.max(1e-9);
|
||||
}
|
||||
|
||||
Self {
|
||||
radius_px,
|
||||
lambda_per_density,
|
||||
density_max: grain.density_max,
|
||||
samples: samples.max(1),
|
||||
// Half a pixel: the footprint of one sample of a sensor whose
|
||||
// pixels abut. Wider would be a soft scanner, narrower a sharper
|
||||
// one than exists.
|
||||
sigma_px: 0.5,
|
||||
}
|
||||
}
|
||||
|
||||
/// How many Monte Carlo samples a given quality asks for.
|
||||
///
|
||||
/// The estimator's own noise falls as `1/sqrt(N)`, so this trades one kind
|
||||
/// of grain against another: too few samples and the *sampling* shows as a
|
||||
/// second, wrong texture on top of the film's.
|
||||
pub fn samples_for(quality: Quality) -> u32 {
|
||||
match quality {
|
||||
Quality::Preview => 16,
|
||||
Quality::Export => 64,
|
||||
}
|
||||
}
|
||||
|
||||
/// Expected coverage at a density — what the render must average to.
|
||||
///
|
||||
/// The Boolean model's mean is analytic even though its texture is not,
|
||||
/// which is what makes it testable without rendering anything: whatever
|
||||
/// the grain does locally, across a flat patch it has to come back to the
|
||||
/// density the characteristic curve asked for.
|
||||
pub fn expected_coverage(&self, layer: usize, density: f32) -> f32 {
|
||||
let d = density.clamp(0.0, self.density_max[layer]);
|
||||
1.0 - 10f32.powf(-d)
|
||||
}
|
||||
}
|
||||
|
||||
/// How hard to work at the Monte Carlo.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Quality {
|
||||
/// Interactive. Some sampling noise, which at preview scale is hidden
|
||||
/// under the grain it is sampling.
|
||||
Preview,
|
||||
/// Final render, where the sampling noise must be well below the grain.
|
||||
Export,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::profile::Profile;
|
||||
|
||||
fn portra() -> Profile {
|
||||
Profile::parse(include_str!("../profiles/kodak_portra_400.yaml")).unwrap()
|
||||
}
|
||||
|
||||
fn model(pixel_size_um: f32) -> BooleanGrain {
|
||||
let g = Grain::for_pixel_size(&portra(), pixel_size_um);
|
||||
BooleanGrain::new(&g, pixel_size_um, 16)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coverage_is_beer_lambert() {
|
||||
// The identity the whole coupling rests on: a Boolean model's uncovered
|
||||
// fraction is exp(-lambda E[A]), and transmittance is 10^-D, so the
|
||||
// model's coverage *is* the film's opacity. If this drifts, the grain
|
||||
// is no longer rendering the density the curve asked for.
|
||||
let m = model(6.6);
|
||||
// Inside the layer's own range. Past its Dmax the coverage clamps —
|
||||
// correctly, since a film cannot develop denser than its maximum — and
|
||||
// an earlier version of this test probed 2.0 against a layer that
|
||||
// reaches 1.798, then blamed the model for the clamp.
|
||||
for d in [0.0f32, 0.3, 1.0, 1.7] {
|
||||
let coverage = m.expected_coverage(1, d);
|
||||
let transmittance = 1.0 - coverage;
|
||||
assert!(
|
||||
(transmittance - 10f32.powf(-d)).abs() < 1e-5,
|
||||
"at density {d}: transmittance {transmittance}, expected {}",
|
||||
10f32.powf(-d)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_film_has_no_grains_and_fully_developed_film_is_nearly_solid() {
|
||||
let m = model(6.6);
|
||||
assert!(m.expected_coverage(1, 0.0) < 1e-6);
|
||||
// At its own maximum, not at some density it never reaches: Portra's
|
||||
// green layer tops out near 1.8, which transmits about 1.6% — dense,
|
||||
// and not opaque. A film that went fully black would be one whose
|
||||
// shadows carried no detail at all.
|
||||
let dmax = m.density_max[1];
|
||||
assert!(
|
||||
m.expected_coverage(1, dmax) > 0.98,
|
||||
"{}",
|
||||
m.expected_coverage(1, dmax)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coverage_clamps_at_the_layers_own_maximum() {
|
||||
// The property the two tests above tripped over, asserted directly:
|
||||
// asking for more density than the emulsion has gives the emulsion's
|
||||
// own ceiling rather than extrapolating one.
|
||||
let m = model(6.6);
|
||||
let dmax = m.density_max[1];
|
||||
assert_eq!(
|
||||
m.expected_coverage(1, dmax),
|
||||
m.expected_coverage(1, dmax + 5.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_two_models_describe_the_same_crystal() {
|
||||
// The counting model and this one are one emulsion seen two ways. If
|
||||
// they disagreed about grain size they would render as different
|
||||
// films, and the difference would look like a modelling choice rather
|
||||
// than the bug it is.
|
||||
let px = 6.6;
|
||||
let g = Grain::for_pixel_size(&portra(), px);
|
||||
let area = mean_grain_area_um2(&g, px);
|
||||
// Portra's green layer: ~0.14 um^2, about 370 nm across.
|
||||
assert!(
|
||||
(0.10..0.20).contains(&area[1]),
|
||||
"grain area {} um^2 is not what the counting model calibrated",
|
||||
area[1]
|
||||
);
|
||||
let m = BooleanGrain::new(&g, px, 16);
|
||||
// And the radius in pixels must match that area at this scale.
|
||||
let area_px = area[1] / (px * px);
|
||||
let expect_r = (area_px / std::f32::consts::PI).sqrt();
|
||||
assert!((m.radius_px[1] - expect_r).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zooming_in_makes_the_grains_bigger_in_pixels() {
|
||||
// Resolution independence, which is the paper's headline claim and the
|
||||
// thing the counting model can only approximate: a grain is a fixed
|
||||
// size *on the film*, so looking closer must resolve it, not merely
|
||||
// reduce the variance.
|
||||
let close = model(2.0);
|
||||
let far = model(12.0);
|
||||
assert!(
|
||||
close.radius_px[1] > far.radius_px[1] * 3.0,
|
||||
"close {} far {}",
|
||||
close.radius_px[1],
|
||||
far.radius_px[1]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_finer_stock_has_smaller_grains() {
|
||||
let mut fine = portra();
|
||||
let mut coarse = portra();
|
||||
fine.rms_granularity = [4.0; 3];
|
||||
coarse.rms_granularity = [16.0; 3];
|
||||
|
||||
let f = BooleanGrain::new(&Grain::for_pixel_size(&fine, 6.6), 6.6, 16);
|
||||
let c = BooleanGrain::new(&Grain::for_pixel_size(&coarse, 6.6), 6.6, 16);
|
||||
assert!(
|
||||
c.radius_px[1] > f.radius_px[1],
|
||||
"coarse {} is not larger than fine {}",
|
||||
c.radius_px[1],
|
||||
f.radius_px[1]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_samples_more_than_preview() {
|
||||
assert!(
|
||||
BooleanGrain::samples_for(Quality::Export)
|
||||
> BooleanGrain::samples_for(Quality::Preview)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -76,13 +76,65 @@ const RMS_APERTURE_AREA_UM2: f32 = std::f32::consts::PI * 24.0 * 24.0;
|
||||
/// The net density the granularity figure is quoted at.
|
||||
const RMS_REFERENCE_NET_DENSITY: f32 = 1.0;
|
||||
|
||||
/// A 35 mm frame's width, in micrometres.
|
||||
/// TRACES: FR-DEV-3f
|
||||
/// The frame a photograph is being simulated on.
|
||||
///
|
||||
/// What turns a pixel count into a grain size. A photograph has no inherent
|
||||
/// film format, so simulating one means choosing what the frame *would have
|
||||
/// been*; 35 mm is the choice that makes the numbers mean what a photographer
|
||||
/// expects, since published granularity and every intuition about how grainy a
|
||||
/// stock looks come from 35 mm.
|
||||
/// **Grain is a function of enlargement, and this is the half of it the
|
||||
/// photograph cannot supply.** A crystal is a fixed size in micrometres, so
|
||||
/// how grainy a picture looks depends entirely on how much the frame was
|
||||
/// magnified to make it — and that is film size against output size.
|
||||
///
|
||||
/// The same emulsion on 4x5 packs about 3,800 crystals into the pixel that
|
||||
/// holds 300 on 35 mm, so it renders roughly 3.5 times smoother at the same
|
||||
/// output size. Treating everything as 35 mm, as this did, made every
|
||||
/// photograph as grainy as the smallest common format.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Format {
|
||||
Mm35,
|
||||
Format645,
|
||||
Format6x6,
|
||||
Format6x7,
|
||||
Sheet4x5,
|
||||
Sheet8x10,
|
||||
}
|
||||
|
||||
impl Format {
|
||||
/// The formats, in the order the picker offers them.
|
||||
///
|
||||
/// Smallest first, so index zero is 35 mm — the commonest frame, and the
|
||||
/// one whose grain every published figure and every photographer's
|
||||
/// intuition is calibrated against.
|
||||
pub const ALL: [Format; 6] = [
|
||||
Format::Mm35,
|
||||
Format::Format645,
|
||||
Format::Format6x6,
|
||||
Format::Format6x7,
|
||||
Format::Sheet4x5,
|
||||
Format::Sheet8x10,
|
||||
];
|
||||
|
||||
/// The frame's width in micrometres — the *image* area, not the sheet.
|
||||
pub fn width_um(self) -> f32 {
|
||||
match self {
|
||||
Format::Mm35 => 36_000.0,
|
||||
// 6x4.5 and 6x6 share a 56 mm gate; only the other axis differs,
|
||||
// and grain scales with the linear magnification of the axis being
|
||||
// enlarged.
|
||||
Format::Format645 | Format::Format6x6 => 56_000.0,
|
||||
Format::Format6x7 => 70_000.0,
|
||||
// The image area of a sheet, which is smaller than the nominal
|
||||
// inches: a "4x5" exposes about 121 x 97 mm.
|
||||
Format::Sheet4x5 => 121_000.0,
|
||||
Format::Sheet8x10 => 248_000.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_index(i: usize) -> Self {
|
||||
Self::ALL.get(i).copied().unwrap_or(Format::Mm35)
|
||||
}
|
||||
}
|
||||
|
||||
/// A 35 mm frame's width, in micrometres. The default format.
|
||||
pub const FRAME_WIDTH_UM: f32 = 36_000.0;
|
||||
|
||||
/// TRACES: FR-DEV-3f
|
||||
@@ -245,6 +297,41 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_larger_format_is_less_grainy_at_the_same_output_size() {
|
||||
// TRACES: FR-DEV-3f
|
||||
// The point of the whole control, and a fact about photography rather
|
||||
// than about this code: enlarge 35 mm and 4x5 to the same print and the
|
||||
// sheet is visibly smoother, because each of its pixels averages far
|
||||
// more crystals. Treating every frame as 35 mm made a large-format
|
||||
// photograph as grainy as a small one.
|
||||
let p = portra();
|
||||
let out_px = 5472.0;
|
||||
let small = Grain::for_pixel_size(&p, Format::Mm35.width_um() / out_px);
|
||||
let large = Grain::for_pixel_size(&p, Format::Sheet4x5.width_um() / out_px);
|
||||
|
||||
let d = small.density_max[1] * 0.5;
|
||||
let ratio = small.sigma(1, d) / large.sigma(1, d);
|
||||
// Linear magnification is 121/36, so the crystal count per pixel goes
|
||||
// as its square and sigma as its reciprocal: about 3.4x.
|
||||
assert!(
|
||||
(2.5..4.5).contains(&ratio),
|
||||
"35mm is {ratio:.2}x grainier than 4x5, which is not the enlargement"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_formats_are_ordered_smallest_first() {
|
||||
// Index zero has to be the neutral choice — 35 mm, which is what every
|
||||
// published granularity figure is calibrated against.
|
||||
let widths: Vec<f32> = Format::ALL.iter().map(|f| f.width_um()).collect();
|
||||
assert_eq!(widths[0], FRAME_WIDTH_UM);
|
||||
assert!(
|
||||
widths.windows(2).all(|w| w[0] <= w[1]),
|
||||
"formats are not ordered by size: {widths:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pixel_never_holds_less_than_one_grain() {
|
||||
// Past this the model describes a pixel smaller than a crystal, where
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
//! auditable. The sRGB reflectance basis is Mallett & Yuksel (2019).
|
||||
|
||||
pub mod bake;
|
||||
pub mod boolean_grain;
|
||||
mod built_in;
|
||||
pub mod grain;
|
||||
pub mod profile;
|
||||
@@ -45,7 +46,8 @@ pub mod spectrum;
|
||||
pub mod tables;
|
||||
|
||||
pub use bake::{bake, Baked, Recipe};
|
||||
pub use grain::Grain;
|
||||
pub use boolean_grain::BooleanGrain;
|
||||
pub use grain::{Format, Grain};
|
||||
pub use profile::{Kind, Profile, Stage, Support};
|
||||
|
||||
use built_in::BUILT_IN;
|
||||
|
||||
@@ -37,6 +37,23 @@ pub const ID: OpId = OpId("film_sim");
|
||||
pub const EXPOSURE: ParamId = ParamId("exposure");
|
||||
pub const PRINT_EXPOSURE: ParamId = ParamId("print_exposure");
|
||||
pub const PUSH: ParamId = ParamId("push");
|
||||
pub const FORMAT: ParamId = ParamId("format");
|
||||
|
||||
/// TRACES: FR-DEV-3f
|
||||
/// The frames a photograph can be simulated on, smallest first.
|
||||
///
|
||||
/// A genuinely fixed list, unlike the stocks: nobody invents a film format, so
|
||||
/// this is a declared `enum` parameter and gets its control, its place in the
|
||||
/// sidecar and its undo step for free. The *sizes* live in `dr_film::Format`;
|
||||
/// this crate carries only the names, in the same order.
|
||||
static FORMATS: [LocalizedKey; 6] = [
|
||||
LocalizedKey("param.film_sim.format.35mm"),
|
||||
LocalizedKey("param.film_sim.format.645"),
|
||||
LocalizedKey("param.film_sim.format.6x6"),
|
||||
LocalizedKey("param.film_sim.format.6x7"),
|
||||
LocalizedKey("param.film_sim.format.4x5"),
|
||||
LocalizedKey("param.film_sim.format.8x10"),
|
||||
];
|
||||
|
||||
/// How many samples a characteristic curve carries.
|
||||
///
|
||||
@@ -70,6 +87,13 @@ static DESCRIPTOR: OpDescriptor = OpDescriptor {
|
||||
// actually published: Double-X's measured axis spans about -1 to +2,
|
||||
// and beyond a range like that a curve would have to be invented.
|
||||
ParamDescriptor::stops("push", "param.film_sim.push", -1.0, 3.0),
|
||||
// TRACES: FR-DEV-3f
|
||||
// Which frame this was taken on — the half of the enlargement a
|
||||
// photograph cannot supply. A crystal is a fixed size in micrometres,
|
||||
// so how grainy a picture looks is film size against output size, and
|
||||
// the same emulsion on 4x5 renders about three times smoother than on
|
||||
// 35mm at the same print.
|
||||
ParamDescriptor::choice("format", "param.film_sim.format", &FORMATS),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -129,6 +153,8 @@ pub struct FilmSim {
|
||||
exposure: f32,
|
||||
print_exposure: f32,
|
||||
push: f32,
|
||||
/// Index into `FORMATS`. Zero is 35 mm, which is the neutral choice.
|
||||
format: f32,
|
||||
tables: Option<FilmTables>,
|
||||
}
|
||||
|
||||
@@ -173,6 +199,7 @@ impl Operation for FilmSim {
|
||||
EXPOSURE => self.exposure = value,
|
||||
PRINT_EXPOSURE => self.print_exposure = value,
|
||||
PUSH => self.push = value,
|
||||
FORMAT => self.format = value,
|
||||
_ => log::warn!("film_sim: unknown parameter {id}"),
|
||||
}
|
||||
}
|
||||
@@ -182,6 +209,7 @@ impl Operation for FilmSim {
|
||||
EXPOSURE => self.exposure,
|
||||
PRINT_EXPOSURE => self.print_exposure,
|
||||
PUSH => self.push,
|
||||
FORMAT => self.format,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
# Check that every command the workflows invoke exists in the image that will
|
||||
# run it.
|
||||
#
|
||||
# This exists because two CI failures in a row were the same shape: the image
|
||||
# was missing a command, and finding out took a 28-minute cross-compile each
|
||||
# time because the step that would fail ran last. git-lfs and file(1) were both
|
||||
# absent for as long as the build panicked before ever reaching them.
|
||||
#
|
||||
# Nothing here runs the workflow. It answers one question -- is the toolchain
|
||||
# the steps assume actually installed -- in about ten seconds.
|
||||
#
|
||||
# ./docker/ci-preflight.sh # every job
|
||||
# ./docker/ci-preflight.sh android # one job
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO="$(cd "${HERE}/.." && pwd)"
|
||||
WANT="${1:-}"
|
||||
FAILED=0
|
||||
|
||||
# Commands a `run:` block invokes that are worth asserting: the ones a minimal
|
||||
# image plausibly lacks. Shell builtins and coreutils are not interesting; a
|
||||
# missing `cd` is not the failure mode anybody has.
|
||||
readonly INTERESTING='^(git|git-lfs|file|zip|unzip|keytool|curl|cargo|rustup|apt-get|find|sed|awk|base64|shred|adb|python3|node|jq)$'
|
||||
|
||||
jobs_and_images() {
|
||||
python3 - "$REPO" <<'PY'
|
||||
import sys, pathlib, yaml
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
for wf in sorted((root / ".gitea/workflows").glob("*.yml")):
|
||||
doc = yaml.safe_load(wf.read_text()) or {}
|
||||
for job, spec in (doc.get("jobs") or {}).items():
|
||||
image = ((spec.get("container") or {}).get("image"))
|
||||
if not image:
|
||||
continue
|
||||
cmds = set()
|
||||
for step in (spec.get("steps") or []):
|
||||
run = step.get("run")
|
||||
if not run:
|
||||
continue
|
||||
for line in run.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
# first word of the line, and of anything after a pipe
|
||||
for part in line.split("|"):
|
||||
word = part.strip().split(" ")[0].strip()
|
||||
if word.isidentifier() or "-" in word:
|
||||
cmds.add(word)
|
||||
# `git lfs` is a subcommand, so the first word is `git` and the
|
||||
# thing that can actually be absent never appears. This is the
|
||||
# exact bug that shipped an image with no git-lfs in it.
|
||||
if "git lfs" in line:
|
||||
cmds.add("git-lfs")
|
||||
print(f"{job}\t{image}\t{' '.join(sorted(cmds))}")
|
||||
PY
|
||||
}
|
||||
|
||||
while IFS=$'\t' read -r job image cmds; do
|
||||
[[ -n "${WANT}" && "${job}" != "${WANT}" ]] && continue
|
||||
checked=()
|
||||
for c in ${cmds}; do
|
||||
[[ "${c}" =~ ${INTERESTING} ]] && checked+=("${c}")
|
||||
done
|
||||
# `git lfs` is a git subcommand, not a binary on PATH — ask git about it.
|
||||
printf '\n== %s (%s)\n' "${job}" "${image}"
|
||||
if [[ ${#checked[@]} -eq 0 ]]; then
|
||||
echo " nothing to check"
|
||||
continue
|
||||
fi
|
||||
docker image inspect "${image}" >/dev/null 2>&1 || {
|
||||
echo " image not present locally — docker pull ${image}"
|
||||
FAILED=1
|
||||
continue
|
||||
}
|
||||
out=$(docker run --rm --entrypoint bash "${image}" -c '
|
||||
for c in '"${checked[*]}"'; do
|
||||
if [ "$c" = git-lfs ]; then
|
||||
git lfs version >/dev/null 2>&1 && echo "ok git lfs" || echo "MISSING git lfs"
|
||||
elif command -v "$c" >/dev/null 2>&1; then
|
||||
echo "ok $c"
|
||||
else
|
||||
echo "MISSING $c"
|
||||
fi
|
||||
done' 2>&1)
|
||||
echo "${out}" | sed 's/^/ /'
|
||||
grep -q MISSING <<<"${out}" && FAILED=1
|
||||
done < <(jobs_and_images)
|
||||
|
||||
echo
|
||||
if [[ ${FAILED} -eq 0 ]]; then
|
||||
echo "preflight: every command the workflows invoke is present"
|
||||
else
|
||||
echo "preflight: something the workflows invoke is not in the image — fix the Dockerfile before pushing"
|
||||
fi
|
||||
exit ${FAILED}
|
||||
+15
-15
@@ -9,8 +9,8 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| Source files scanned | 234 |
|
||||
| TRACES tags found | 670 |
|
||||
| Source files scanned | 235 |
|
||||
| TRACES tags found | 677 |
|
||||
| Requirements defined | 177 |
|
||||
| Requirements covered | 91 |
|
||||
| **Coverage** | **51.4%** (91/177) |
|
||||
@@ -46,27 +46,27 @@ _None._
|
||||
| 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/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-catalog/src/schema.rs:840`](../core/dr-catalog/src/schema.rs#L840), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:138`](../core/dr-pipeline/src/sidecar.rs#L138), [`ui/dr-ui/src/collections_ui.rs:1578`](../ui/dr-ui/src/collections_ui.rs#L1578), [`ui/dr-ui/src/collections_ui.rs:1597`](../ui/dr-ui/src/collections_ui.rs#L1597), [`ui/dr-ui/src/collections_ui.rs:243`](../ui/dr-ui/src/collections_ui.rs#L243), [`ui/dr-ui/src/collections_ui.rs:340`](../ui/dr-ui/src/collections_ui.rs#L340), [`ui/dr-ui/src/collections_ui.rs:89`](../ui/dr-ui/src/collections_ui.rs#L89), [`ui/dr-ui/src/library.rs:3055`](../ui/dr-ui/src/library.rs#L3055), [`ui/dr-ui/src/library_ui.rs:6255`](../ui/dr-ui/src/library_ui.rs#L6255), [`ui/dr-ui/src/library_ui.rs:6266`](../ui/dr-ui/src/library_ui.rs#L6266), [`ui/dr-ui/src/library_ui.rs:6279`](../ui/dr-ui/src/library_ui.rs#L6279), [`ui/dr-ui/src/library_ui.rs:628`](../ui/dr-ui/src/library_ui.rs#L628), [`ui/dr-ui/src/library_ui.rs:6294`](../ui/dr-ui/src/library_ui.rs#L6294), [`ui/dr-ui/src/library_ui.rs:6303`](../ui/dr-ui/src/library_ui.rs#L6303), [`ui/dr-ui/ui/app.slint:461`](../ui/dr-ui/ui/app.slint#L461), [`ui/dr-ui/ui/app.slint:590`](../ui/dr-ui/ui/app.slint#L590), [`ui/dr-ui/ui/library.slint:1209`](../ui/dr-ui/ui/library.slint#L1209), [`ui/dr-ui/ui/library.slint:1212`](../ui/dr-ui/ui/library.slint#L1212), [`ui/dr-ui/ui/library.slint:18`](../ui/dr-ui/ui/library.slint#L18), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:901`](../ui/dr-ui/ui/library.slint#L901) |
|
||||
| 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/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:63`](../core/dr-types/src/settings.rs#L63), [`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.rs:203`](../ui/dr-ui/src/library.rs#L203), [`ui/dr-ui/src/library.rs:3301`](../ui/dr-ui/src/library.rs#L3301), [`ui/dr-ui/src/library_ui.rs:315`](../ui/dr-ui/src/library_ui.rs#L315), [`ui/dr-ui/src/library_ui.rs:387`](../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:5124`](../ui/dr-ui/src/library_ui.rs#L5124), [`ui/dr-ui/src/library_ui.rs:5177`](../ui/dr-ui/src/library_ui.rs#L5177), [`ui/dr-ui/src/library_ui.rs:6395`](../ui/dr-ui/src/library_ui.rs#L6395), [`ui/dr-ui/ui/app.slint:464`](../ui/dr-ui/ui/app.slint#L464), [`ui/dr-ui/ui/app.slint:590`](../ui/dr-ui/ui/app.slint#L590), [`ui/dr-ui/ui/app.slint:829`](../ui/dr-ui/ui/app.slint#L829), [`ui/dr-ui/ui/library.slint:109`](../ui/dr-ui/ui/library.slint#L109), [`ui/dr-ui/ui/library.slint:1278`](../ui/dr-ui/ui/library.slint#L1278), [`ui/dr-ui/ui/library.slint:901`](../ui/dr-ui/ui/library.slint#L901), [`ui/dr-ui/ui/settings.slint:115`](../ui/dr-ui/ui/settings.slint#L115) |
|
||||
| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.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.rs:1693`](../ui/dr-ui/src/collections_ui.rs#L1693), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library_ui.rs:3466`](../ui/dr-ui/src/library_ui.rs#L3466), [`ui/dr-ui/src/library_ui.rs:4164`](../ui/dr-ui/src/library_ui.rs#L4164), [`ui/dr-ui/ui/app.slint:585`](../ui/dr-ui/ui/app.slint#L585), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:2516`](../ui/dr-ui/ui/library.slint#L2516), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:891`](../ui/dr-ui/ui/library.slint#L891) |
|
||||
| FR-CAT-8 | [`core/dr-pipeline/src/ops/curve.rs:136`](../core/dr-pipeline/src/ops/curve.rs#L136), [`core/dr-pipeline/src/ops/curve.rs:652`](../core/dr-pipeline/src/ops/curve.rs#L652), [`core/dr-pipeline/src/sidecar.rs:1536`](../core/dr-pipeline/src/sidecar.rs#L1536), [`core/dr-pipeline/src/sidecar.rs:91`](../core/dr-pipeline/src/sidecar.rs#L91), [`core/dr-pipeline/tests/tone_curve.rs:33`](../core/dr-pipeline/tests/tone_curve.rs#L33), [`ui/dr-ui/src/develop.rs:3023`](../ui/dr-ui/src/develop.rs#L3023), [`ui/dr-ui/src/develop.rs:3051`](../ui/dr-ui/src/develop.rs#L3051), [`ui/dr-ui/src/export.rs:752`](../ui/dr-ui/src/export.rs#L752), [`ui/dr-ui/src/lib.rs:1285`](../ui/dr-ui/src/lib.rs#L1285), [`ui/dr-ui/src/lib.rs:1637`](../ui/dr-ui/src/lib.rs#L1637), [`ui/dr-ui/src/lib.rs:1742`](../ui/dr-ui/src/lib.rs#L1742), [`ui/dr-ui/src/lib.rs:470`](../ui/dr-ui/src/lib.rs#L470), [`ui/dr-ui/src/lib.rs:895`](../ui/dr-ui/src/lib.rs#L895), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library_ui.rs:4866`](../ui/dr-ui/src/library_ui.rs#L4866), [`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:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:704`](../core/dr-catalog/src/walk.rs#L704), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`ui/dr-ui/src/develop.rs:2586`](../ui/dr-ui/src/develop.rs#L2586), [`ui/dr-ui/src/library.rs:138`](../ui/dr-ui/src/library.rs#L138), [`ui/dr-ui/src/library.rs:1505`](../ui/dr-ui/src/library.rs#L1505), [`ui/dr-ui/src/library.rs:1582`](../ui/dr-ui/src/library.rs#L1582), [`ui/dr-ui/src/library.rs:220`](../ui/dr-ui/src/library.rs#L220), [`ui/dr-ui/src/library.rs:3408`](../ui/dr-ui/src/library.rs#L3408), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:680`](../ui/dr-ui/src/library.rs#L680), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library_ui.rs:1564`](../ui/dr-ui/src/library_ui.rs#L1564), [`ui/dr-ui/src/library_ui.rs:1590`](../ui/dr-ui/src/library_ui.rs#L1590), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:1700`](../ui/dr-ui/src/library_ui.rs#L1700), [`ui/dr-ui/src/library_ui.rs:226`](../ui/dr-ui/src/library_ui.rs#L226), [`ui/dr-ui/src/library_ui.rs:2316`](../ui/dr-ui/src/library_ui.rs#L2316), [`ui/dr-ui/src/library_ui.rs:259`](../ui/dr-ui/src/library_ui.rs#L259), [`ui/dr-ui/src/library_ui.rs:2754`](../ui/dr-ui/src/library_ui.rs#L2754), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:3257`](../ui/dr-ui/src/library_ui.rs#L3257), [`ui/dr-ui/src/library_ui.rs:3329`](../ui/dr-ui/src/library_ui.rs#L3329), [`ui/dr-ui/src/library_ui.rs:3517`](../ui/dr-ui/src/library_ui.rs#L3517), [`ui/dr-ui/src/library_ui.rs:3635`](../ui/dr-ui/src/library_ui.rs#L3635), [`ui/dr-ui/src/library_ui.rs:440`](../ui/dr-ui/src/library_ui.rs#L440), [`ui/dr-ui/src/library_ui.rs:498`](../ui/dr-ui/src/library_ui.rs#L498), [`ui/dr-ui/src/library_ui.rs:5222`](../ui/dr-ui/src/library_ui.rs#L5222), [`ui/dr-ui/src/library_ui.rs:5337`](../ui/dr-ui/src/library_ui.rs#L5337), [`ui/dr-ui/src/presets.rs:326`](../ui/dr-ui/src/presets.rs#L326), [`ui/dr-ui/src/presets.rs:338`](../ui/dr-ui/src/presets.rs#L338), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
||||
| FR-CAT-8 | [`core/dr-pipeline/src/ops/curve.rs:136`](../core/dr-pipeline/src/ops/curve.rs#L136), [`core/dr-pipeline/src/ops/curve.rs:652`](../core/dr-pipeline/src/ops/curve.rs#L652), [`core/dr-pipeline/src/sidecar.rs:1536`](../core/dr-pipeline/src/sidecar.rs#L1536), [`core/dr-pipeline/src/sidecar.rs:91`](../core/dr-pipeline/src/sidecar.rs#L91), [`core/dr-pipeline/tests/tone_curve.rs:33`](../core/dr-pipeline/tests/tone_curve.rs#L33), [`ui/dr-ui/src/develop.rs:3073`](../ui/dr-ui/src/develop.rs#L3073), [`ui/dr-ui/src/develop.rs:3101`](../ui/dr-ui/src/develop.rs#L3101), [`ui/dr-ui/src/export.rs:752`](../ui/dr-ui/src/export.rs#L752), [`ui/dr-ui/src/lib.rs:1285`](../ui/dr-ui/src/lib.rs#L1285), [`ui/dr-ui/src/lib.rs:1637`](../ui/dr-ui/src/lib.rs#L1637), [`ui/dr-ui/src/lib.rs:1742`](../ui/dr-ui/src/lib.rs#L1742), [`ui/dr-ui/src/lib.rs:470`](../ui/dr-ui/src/lib.rs#L470), [`ui/dr-ui/src/lib.rs:895`](../ui/dr-ui/src/lib.rs#L895), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library_ui.rs:4866`](../ui/dr-ui/src/library_ui.rs#L4866), [`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:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:704`](../core/dr-catalog/src/walk.rs#L704), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`ui/dr-ui/src/develop.rs:2621`](../ui/dr-ui/src/develop.rs#L2621), [`ui/dr-ui/src/library.rs:138`](../ui/dr-ui/src/library.rs#L138), [`ui/dr-ui/src/library.rs:1505`](../ui/dr-ui/src/library.rs#L1505), [`ui/dr-ui/src/library.rs:1582`](../ui/dr-ui/src/library.rs#L1582), [`ui/dr-ui/src/library.rs:220`](../ui/dr-ui/src/library.rs#L220), [`ui/dr-ui/src/library.rs:3408`](../ui/dr-ui/src/library.rs#L3408), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:680`](../ui/dr-ui/src/library.rs#L680), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library_ui.rs:1564`](../ui/dr-ui/src/library_ui.rs#L1564), [`ui/dr-ui/src/library_ui.rs:1590`](../ui/dr-ui/src/library_ui.rs#L1590), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:1700`](../ui/dr-ui/src/library_ui.rs#L1700), [`ui/dr-ui/src/library_ui.rs:226`](../ui/dr-ui/src/library_ui.rs#L226), [`ui/dr-ui/src/library_ui.rs:2316`](../ui/dr-ui/src/library_ui.rs#L2316), [`ui/dr-ui/src/library_ui.rs:259`](../ui/dr-ui/src/library_ui.rs#L259), [`ui/dr-ui/src/library_ui.rs:2754`](../ui/dr-ui/src/library_ui.rs#L2754), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:3257`](../ui/dr-ui/src/library_ui.rs#L3257), [`ui/dr-ui/src/library_ui.rs:3329`](../ui/dr-ui/src/library_ui.rs#L3329), [`ui/dr-ui/src/library_ui.rs:3517`](../ui/dr-ui/src/library_ui.rs#L3517), [`ui/dr-ui/src/library_ui.rs:3635`](../ui/dr-ui/src/library_ui.rs#L3635), [`ui/dr-ui/src/library_ui.rs:440`](../ui/dr-ui/src/library_ui.rs#L440), [`ui/dr-ui/src/library_ui.rs:498`](../ui/dr-ui/src/library_ui.rs#L498), [`ui/dr-ui/src/library_ui.rs:5222`](../ui/dr-ui/src/library_ui.rs#L5222), [`ui/dr-ui/src/library_ui.rs:5337`](../ui/dr-ui/src/library_ui.rs#L5337), [`ui/dr-ui/src/presets.rs:326`](../ui/dr-ui/src/presets.rs#L326), [`ui/dr-ui/src/presets.rs:338`](../ui/dr-ui/src/presets.rs#L338), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
||||
| FR-CULL-1 | [`core/dr-decode/src/preview.rs:134`](../core/dr-decode/src/preview.rs#L134) |
|
||||
| FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:161`](../core/dr-decode/src/preview.rs#L161), [`ui/dr-ui/src/import.rs:464`](../ui/dr-ui/src/import.rs#L464) |
|
||||
| FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:138`](../core/dr-pipeline/src/sidecar.rs#L138), [`ui/dr-ui/src/library.rs:203`](../ui/dr-ui/src/library.rs#L203), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364) |
|
||||
| FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:352`](../core/dr-pipeline/src/operation.rs#L352) |
|
||||
| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/framing.rs:188`](../core/dr-pipeline/src/framing.rs#L188), [`core/dr-pipeline/src/framing.rs:602`](../core/dr-pipeline/src/framing.rs#L602), [`core/dr-pipeline/src/graph.rs:165`](../core/dr-pipeline/src/graph.rs#L165), [`core/dr-pipeline/src/graph.rs:479`](../core/dr-pipeline/src/graph.rs#L479), [`core/dr-pipeline/src/mask.rs:120`](../core/dr-pipeline/src/mask.rs#L120), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299), [`core/dr-pipeline/src/operation.rs:479`](../core/dr-pipeline/src/operation.rs#L479), [`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:207`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L207), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:218`](../core/dr-pipeline/src/ops/curve.rs#L218), [`core/dr-pipeline/src/ops/curve.rs:631`](../core/dr-pipeline/src/ops/curve.rs#L631), [`core/dr-pipeline/src/ops/curve.rs:99`](../core/dr-pipeline/src/ops/curve.rs#L99), [`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:270`](../core/dr-pipeline/src/ops/noise_reduction.rs#L270), [`core/dr-pipeline/src/sidecar.rs:1536`](../core/dr-pipeline/src/sidecar.rs#L1536), [`core/dr-pipeline/src/sidecar.rs:1592`](../core/dr-pipeline/src/sidecar.rs#L1592), [`core/dr-pipeline/src/sidecar.rs:159`](../core/dr-pipeline/src/sidecar.rs#L159), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`ui/dr-ui/src/develop.rs:101`](../ui/dr-ui/src/develop.rs#L101), [`ui/dr-ui/src/develop.rs:1121`](../ui/dr-ui/src/develop.rs#L1121), [`ui/dr-ui/src/develop.rs:132`](../ui/dr-ui/src/develop.rs#L132), [`ui/dr-ui/src/develop.rs:1595`](../ui/dr-ui/src/develop.rs#L1595), [`ui/dr-ui/src/develop.rs:1610`](../ui/dr-ui/src/develop.rs#L1610), [`ui/dr-ui/src/develop.rs:1632`](../ui/dr-ui/src/develop.rs#L1632), [`ui/dr-ui/src/develop.rs:1764`](../ui/dr-ui/src/develop.rs#L1764), [`ui/dr-ui/src/develop.rs:1842`](../ui/dr-ui/src/develop.rs#L1842), [`ui/dr-ui/src/develop.rs:238`](../ui/dr-ui/src/develop.rs#L238), [`ui/dr-ui/src/develop.rs:270`](../ui/dr-ui/src/develop.rs#L270), [`ui/dr-ui/src/develop.rs:3023`](../ui/dr-ui/src/develop.rs#L3023), [`ui/dr-ui/src/develop.rs:3455`](../ui/dr-ui/src/develop.rs#L3455), [`ui/dr-ui/src/develop.rs:3509`](../ui/dr-ui/src/develop.rs#L3509), [`ui/dr-ui/src/develop.rs:3553`](../ui/dr-ui/src/develop.rs#L3553), [`ui/dr-ui/src/develop.rs:3603`](../ui/dr-ui/src/develop.rs#L3603), [`ui/dr-ui/src/develop.rs:506`](../ui/dr-ui/src/develop.rs#L506), [`ui/dr-ui/src/develop.rs:553`](../ui/dr-ui/src/develop.rs#L553), [`ui/dr-ui/src/lib.rs:1321`](../ui/dr-ui/src/lib.rs#L1321), [`ui/dr-ui/src/lib.rs:1992`](../ui/dr-ui/src/lib.rs#L1992), [`ui/dr-ui/src/lib.rs:291`](../ui/dr-ui/src/lib.rs#L291), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/segmentation.rs:218`](../ui/dr-ui/src/segmentation.rs#L218), [`ui/dr-ui/ui/app.slint:1987`](../ui/dr-ui/ui/app.slint#L1987), [`ui/dr-ui/ui/app.slint:916`](../ui/dr-ui/ui/app.slint#L916) |
|
||||
| FR-DEV-3a | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:117`](../core/dr-pipeline/src/descriptor.rs#L117), [`core/dr-pipeline/src/descriptor.rs:157`](../core/dr-pipeline/src/descriptor.rs#L157), [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/descriptor.rs:232`](../core/dr-pipeline/src/descriptor.rs#L232), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259), [`core/dr-pipeline/src/graph.rs:19`](../core/dr-pipeline/src/graph.rs#L19), [`core/dr-pipeline/src/graph.rs:239`](../core/dr-pipeline/src/graph.rs#L239), [`core/dr-pipeline/src/graph.rs:41`](../core/dr-pipeline/src/graph.rs#L41), [`core/dr-pipeline/src/graph.rs:54`](../core/dr-pipeline/src/graph.rs#L54), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`core/dr-pipeline/src/operation.rs:328`](../core/dr-pipeline/src/operation.rs#L328), [`core/dr-pipeline/src/ops/curve.rs:318`](../core/dr-pipeline/src/ops/curve.rs#L318), [`ui/dr-ui/src/develop.rs:1018`](../ui/dr-ui/src/develop.rs#L1018), [`ui/dr-ui/src/lib.rs:585`](../ui/dr-ui/src/lib.rs#L585) |
|
||||
| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/framing.rs:188`](../core/dr-pipeline/src/framing.rs#L188), [`core/dr-pipeline/src/framing.rs:602`](../core/dr-pipeline/src/framing.rs#L602), [`core/dr-pipeline/src/graph.rs:165`](../core/dr-pipeline/src/graph.rs#L165), [`core/dr-pipeline/src/graph.rs:479`](../core/dr-pipeline/src/graph.rs#L479), [`core/dr-pipeline/src/mask.rs:120`](../core/dr-pipeline/src/mask.rs#L120), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299), [`core/dr-pipeline/src/operation.rs:479`](../core/dr-pipeline/src/operation.rs#L479), [`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:207`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L207), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:218`](../core/dr-pipeline/src/ops/curve.rs#L218), [`core/dr-pipeline/src/ops/curve.rs:631`](../core/dr-pipeline/src/ops/curve.rs#L631), [`core/dr-pipeline/src/ops/curve.rs:99`](../core/dr-pipeline/src/ops/curve.rs#L99), [`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:270`](../core/dr-pipeline/src/ops/noise_reduction.rs#L270), [`core/dr-pipeline/src/sidecar.rs:1536`](../core/dr-pipeline/src/sidecar.rs#L1536), [`core/dr-pipeline/src/sidecar.rs:1592`](../core/dr-pipeline/src/sidecar.rs#L1592), [`core/dr-pipeline/src/sidecar.rs:159`](../core/dr-pipeline/src/sidecar.rs#L159), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`ui/dr-ui/src/develop.rs:101`](../ui/dr-ui/src/develop.rs#L101), [`ui/dr-ui/src/develop.rs:1156`](../ui/dr-ui/src/develop.rs#L1156), [`ui/dr-ui/src/develop.rs:132`](../ui/dr-ui/src/develop.rs#L132), [`ui/dr-ui/src/develop.rs:1630`](../ui/dr-ui/src/develop.rs#L1630), [`ui/dr-ui/src/develop.rs:1645`](../ui/dr-ui/src/develop.rs#L1645), [`ui/dr-ui/src/develop.rs:1667`](../ui/dr-ui/src/develop.rs#L1667), [`ui/dr-ui/src/develop.rs:1799`](../ui/dr-ui/src/develop.rs#L1799), [`ui/dr-ui/src/develop.rs:1877`](../ui/dr-ui/src/develop.rs#L1877), [`ui/dr-ui/src/develop.rs:238`](../ui/dr-ui/src/develop.rs#L238), [`ui/dr-ui/src/develop.rs:270`](../ui/dr-ui/src/develop.rs#L270), [`ui/dr-ui/src/develop.rs:3073`](../ui/dr-ui/src/develop.rs#L3073), [`ui/dr-ui/src/develop.rs:3505`](../ui/dr-ui/src/develop.rs#L3505), [`ui/dr-ui/src/develop.rs:3559`](../ui/dr-ui/src/develop.rs#L3559), [`ui/dr-ui/src/develop.rs:3603`](../ui/dr-ui/src/develop.rs#L3603), [`ui/dr-ui/src/develop.rs:3653`](../ui/dr-ui/src/develop.rs#L3653), [`ui/dr-ui/src/develop.rs:506`](../ui/dr-ui/src/develop.rs#L506), [`ui/dr-ui/src/develop.rs:553`](../ui/dr-ui/src/develop.rs#L553), [`ui/dr-ui/src/lib.rs:1321`](../ui/dr-ui/src/lib.rs#L1321), [`ui/dr-ui/src/lib.rs:1992`](../ui/dr-ui/src/lib.rs#L1992), [`ui/dr-ui/src/lib.rs:291`](../ui/dr-ui/src/lib.rs#L291), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/segmentation.rs:218`](../ui/dr-ui/src/segmentation.rs#L218), [`ui/dr-ui/ui/app.slint:1987`](../ui/dr-ui/ui/app.slint#L1987), [`ui/dr-ui/ui/app.slint:916`](../ui/dr-ui/ui/app.slint#L916) |
|
||||
| FR-DEV-3a | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:117`](../core/dr-pipeline/src/descriptor.rs#L117), [`core/dr-pipeline/src/descriptor.rs:157`](../core/dr-pipeline/src/descriptor.rs#L157), [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/descriptor.rs:232`](../core/dr-pipeline/src/descriptor.rs#L232), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259), [`core/dr-pipeline/src/graph.rs:19`](../core/dr-pipeline/src/graph.rs#L19), [`core/dr-pipeline/src/graph.rs:239`](../core/dr-pipeline/src/graph.rs#L239), [`core/dr-pipeline/src/graph.rs:41`](../core/dr-pipeline/src/graph.rs#L41), [`core/dr-pipeline/src/graph.rs:54`](../core/dr-pipeline/src/graph.rs#L54), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`core/dr-pipeline/src/operation.rs:328`](../core/dr-pipeline/src/operation.rs#L328), [`core/dr-pipeline/src/ops/curve.rs:318`](../core/dr-pipeline/src/ops/curve.rs#L318), [`ui/dr-ui/src/develop.rs:1053`](../ui/dr-ui/src/develop.rs#L1053), [`ui/dr-ui/src/lib.rs:585`](../ui/dr-ui/src/lib.rs#L585) |
|
||||
| FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259), [`core/dr-pipeline/src/graph.rs:54`](../core/dr-pipeline/src/graph.rs#L54), [`core/dr-pipeline/src/operation.rs:328`](../core/dr-pipeline/src/operation.rs#L328) |
|
||||
| FR-DEV-3c | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:239`](../core/dr-pipeline/src/graph.rs#L239), [`core/dr-pipeline/src/graph.rs:41`](../core/dr-pipeline/src/graph.rs#L41), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`ui/dr-ui/src/develop.rs:4182`](../ui/dr-ui/src/develop.rs#L4182) |
|
||||
| FR-DEV-3c | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:239`](../core/dr-pipeline/src/graph.rs#L239), [`core/dr-pipeline/src/graph.rs:41`](../core/dr-pipeline/src/graph.rs#L41), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`ui/dr-ui/src/develop.rs:4232`](../ui/dr-ui/src/develop.rs#L4232) |
|
||||
| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1041`](../core/dr-gpu/src/adjust.rs#L1041), [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`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:476`](../core/dr-gpu/tests/local_contrast.rs#L476), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:188`](../core/dr-pipeline/src/framing.rs#L188), [`core/dr-pipeline/src/graph.rs:518`](../core/dr-pipeline/src/graph.rs#L518), [`core/dr-pipeline/src/operation.rs:31`](../core/dr-pipeline/src/operation.rs#L31), [`core/dr-pipeline/src/operation.rs:352`](../core/dr-pipeline/src/operation.rs#L352), [`core/dr-pipeline/src/operation.rs:52`](../core/dr-pipeline/src/operation.rs#L52), [`core/dr-pipeline/src/operation.rs:70`](../core/dr-pipeline/src/operation.rs#L70) |
|
||||
| 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:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`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:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`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:1424`](../core/dr-pipeline/src/operation.rs#L1424), [`core/dr-pipeline/src/operation.rs:1505`](../core/dr-pipeline/src/operation.rs#L1505), [`core/dr-pipeline/src/operation.rs:1530`](../core/dr-pipeline/src/operation.rs#L1530), [`core/dr-pipeline/src/operation.rs:1545`](../core/dr-pipeline/src/operation.rs#L1545), [`core/dr-pipeline/src/operation.rs:1569`](../core/dr-pipeline/src/operation.rs#L1569), [`core/dr-pipeline/src/operation.rs:279`](../core/dr-pipeline/src/operation.rs#L279), [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:413`](../core/dr-pipeline/src/operation.rs#L413), [`core/dr-pipeline/src/operation.rs:563`](../core/dr-pipeline/src/operation.rs#L563) |
|
||||
| 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/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:88`](../core/dr-film/src/grain.rs#L88), [`core/dr-film/src/lib.rs:158`](../core/dr-film/src/lib.rs#L158), [`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:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:120`](../core/dr-pipeline/src/graph.rs#L120), [`core/dr-pipeline/src/graph.rs:313`](../core/dr-pipeline/src/graph.rs#L313), [`core/dr-pipeline/src/graph.rs:97`](../core/dr-pipeline/src/graph.rs#L97), [`core/dr-pipeline/src/operation.rs:1028`](../core/dr-pipeline/src/operation.rs#L1028), [`core/dr-pipeline/src/operation.rs:1057`](../core/dr-pipeline/src/operation.rs#L1057), [`core/dr-pipeline/src/operation.rs:1424`](../core/dr-pipeline/src/operation.rs#L1424), [`core/dr-pipeline/src/operation.rs:265`](../core/dr-pipeline/src/operation.rs#L265), [`core/dr-pipeline/src/operation.rs:279`](../core/dr-pipeline/src/operation.rs#L279), [`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:126`](../core/dr-pipeline/src/ops/film_sim.rs#L126), [`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:300`](../core/dr-pipeline/src/ops/film_sim.rs#L300), [`core/dr-pipeline/src/ops/film_sim.rs:68`](../core/dr-pipeline/src/ops/film_sim.rs#L68), [`core/dr-pipeline/src/sidecar.rs:110`](../core/dr-pipeline/src/sidecar.rs#L110), [`core/dr-pipeline/src/sidecar.rs:170`](../core/dr-pipeline/src/sidecar.rs#L170), [`core/dr-pipeline/src/sidecar.rs:1840`](../core/dr-pipeline/src/sidecar.rs#L1840), [`core/dr-pipeline/src/sidecar.rs:1916`](../core/dr-pipeline/src/sidecar.rs#L1916), [`core/dr-pipeline/src/sidecar.rs:511`](../core/dr-pipeline/src/sidecar.rs#L511), [`core/dr-pipeline/src/sidecar.rs:652`](../core/dr-pipeline/src/sidecar.rs#L652), [`core/dr-pipeline/src/sidecar.rs:784`](../core/dr-pipeline/src/sidecar.rs#L784), [`ui/dr-ui/src/develop.rs:2601`](../ui/dr-ui/src/develop.rs#L2601), [`ui/dr-ui/src/develop.rs:2618`](../ui/dr-ui/src/develop.rs#L2618), [`ui/dr-ui/src/develop.rs:2649`](../ui/dr-ui/src/develop.rs#L2649), [`ui/dr-ui/src/develop.rs:2746`](../ui/dr-ui/src/develop.rs#L2746), [`ui/dr-ui/src/develop.rs:3060`](../ui/dr-ui/src/develop.rs#L3060), [`ui/dr-ui/src/lib.rs:1966`](../ui/dr-ui/src/lib.rs#L1966), [`ui/dr-ui/src/lib.rs:518`](../ui/dr-ui/src/lib.rs#L518), [`ui/dr-ui/src/lib.rs:576`](../ui/dr-ui/src/lib.rs#L576), [`ui/dr-ui/src/library.rs:402`](../ui/dr-ui/src/library.rs#L402), [`ui/dr-ui/src/library.rs:651`](../ui/dr-ui/src/library.rs#L651), [`ui/dr-ui/src/presets.rs:275`](../ui/dr-ui/src/presets.rs#L275), [`ui/dr-ui/ui/adjust.slint:889`](../ui/dr-ui/ui/adjust.slint#L889), [`ui/dr-ui/ui/adjust.slint:959`](../ui/dr-ui/ui/adjust.slint#L959), [`ui/dr-ui/ui/app.slint:2599`](../ui/dr-ui/ui/app.slint#L2599), [`ui/dr-ui/ui/app.slint:710`](../ui/dr-ui/ui/app.slint#L710) |
|
||||
| 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:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:120`](../core/dr-pipeline/src/graph.rs#L120), [`core/dr-pipeline/src/graph.rs:313`](../core/dr-pipeline/src/graph.rs#L313), [`core/dr-pipeline/src/graph.rs:97`](../core/dr-pipeline/src/graph.rs#L97), [`core/dr-pipeline/src/operation.rs:1028`](../core/dr-pipeline/src/operation.rs#L1028), [`core/dr-pipeline/src/operation.rs:1057`](../core/dr-pipeline/src/operation.rs#L1057), [`core/dr-pipeline/src/operation.rs:1424`](../core/dr-pipeline/src/operation.rs#L1424), [`core/dr-pipeline/src/operation.rs:265`](../core/dr-pipeline/src/operation.rs#L265), [`core/dr-pipeline/src/operation.rs:279`](../core/dr-pipeline/src/operation.rs#L279), [`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:150`](../core/dr-pipeline/src/ops/film_sim.rs#L150), [`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:328`](../core/dr-pipeline/src/ops/film_sim.rs#L328), [`core/dr-pipeline/src/ops/film_sim.rs:42`](../core/dr-pipeline/src/ops/film_sim.rs#L42), [`core/dr-pipeline/src/ops/film_sim.rs:85`](../core/dr-pipeline/src/ops/film_sim.rs#L85), [`core/dr-pipeline/src/ops/film_sim.rs:90`](../core/dr-pipeline/src/ops/film_sim.rs#L90), [`core/dr-pipeline/src/sidecar.rs:110`](../core/dr-pipeline/src/sidecar.rs#L110), [`core/dr-pipeline/src/sidecar.rs:170`](../core/dr-pipeline/src/sidecar.rs#L170), [`core/dr-pipeline/src/sidecar.rs:1840`](../core/dr-pipeline/src/sidecar.rs#L1840), [`core/dr-pipeline/src/sidecar.rs:1916`](../core/dr-pipeline/src/sidecar.rs#L1916), [`core/dr-pipeline/src/sidecar.rs:511`](../core/dr-pipeline/src/sidecar.rs#L511), [`core/dr-pipeline/src/sidecar.rs:652`](../core/dr-pipeline/src/sidecar.rs#L652), [`core/dr-pipeline/src/sidecar.rs:784`](../core/dr-pipeline/src/sidecar.rs#L784), [`ui/dr-ui/src/develop.rs:2636`](../ui/dr-ui/src/develop.rs#L2636), [`ui/dr-ui/src/develop.rs:2653`](../ui/dr-ui/src/develop.rs#L2653), [`ui/dr-ui/src/develop.rs:2684`](../ui/dr-ui/src/develop.rs#L2684), [`ui/dr-ui/src/develop.rs:2693`](../ui/dr-ui/src/develop.rs#L2693), [`ui/dr-ui/src/develop.rs:2796`](../ui/dr-ui/src/develop.rs#L2796), [`ui/dr-ui/src/develop.rs:3110`](../ui/dr-ui/src/develop.rs#L3110), [`ui/dr-ui/src/lib.rs:1966`](../ui/dr-ui/src/lib.rs#L1966), [`ui/dr-ui/src/lib.rs:518`](../ui/dr-ui/src/lib.rs#L518), [`ui/dr-ui/src/lib.rs:576`](../ui/dr-ui/src/lib.rs#L576), [`ui/dr-ui/src/library.rs:402`](../ui/dr-ui/src/library.rs#L402), [`ui/dr-ui/src/library.rs:651`](../ui/dr-ui/src/library.rs#L651), [`ui/dr-ui/src/presets.rs:275`](../ui/dr-ui/src/presets.rs#L275), [`ui/dr-ui/ui/adjust.slint:889`](../ui/dr-ui/ui/adjust.slint#L889), [`ui/dr-ui/ui/adjust.slint:959`](../ui/dr-ui/ui/adjust.slint#L959), [`ui/dr-ui/ui/app.slint:2599`](../ui/dr-ui/ui/app.slint#L2599), [`ui/dr-ui/ui/app.slint:710`](../ui/dr-ui/ui/app.slint#L710) |
|
||||
| FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:202`](../core/dr-pipeline/src/framing.rs#L202), [`core/dr-types/src/lib.rs:336`](../core/dr-types/src/lib.rs#L336) |
|
||||
| FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:217`](../core/dr-gpu/src/lib.rs#L217) |
|
||||
| FR-DEV-5 | [`core/dr-pipeline/src/history.rs:124`](../core/dr-pipeline/src/history.rs#L124), [`core/dr-pipeline/src/history.rs:140`](../core/dr-pipeline/src/history.rs#L140), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:55`](../core/dr-pipeline/src/history.rs#L55), [`core/dr-pipeline/src/history.rs:71`](../core/dr-pipeline/src/history.rs#L71), [`core/dr-pipeline/src/history.rs:79`](../core/dr-pipeline/src/history.rs#L79), [`ui/dr-ui/src/develop.rs:3078`](../ui/dr-ui/src/develop.rs#L3078), [`ui/dr-ui/src/develop.rs:3088`](../ui/dr-ui/src/develop.rs#L3088), [`ui/dr-ui/src/develop.rs:488`](../ui/dr-ui/src/develop.rs#L488), [`ui/dr-ui/src/lib.rs:1313`](../ui/dr-ui/src/lib.rs#L1313) |
|
||||
| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-types/src/settings.rs:182`](../core/dr-types/src/settings.rs#L182), [`ui/dr-ui/src/develop.rs:3017`](../ui/dr-ui/src/develop.rs#L3017), [`ui/dr-ui/src/develop.rs:3038`](../ui/dr-ui/src/develop.rs#L3038), [`ui/dr-ui/src/lib.rs:1285`](../ui/dr-ui/src/lib.rs#L1285), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:392`](../ui/dr-ui/src/library.rs#L392), [`ui/dr-ui/src/library_ui.rs:2576`](../ui/dr-ui/src/library_ui.rs#L2576), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:466`](../ui/dr-ui/src/library_ui.rs#L466), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/settings_ui.rs:535`](../ui/dr-ui/src/settings_ui.rs#L535), [`ui/dr-ui/ui/adjust.slint:598`](../ui/dr-ui/ui/adjust.slint#L598), [`ui/dr-ui/ui/library.slint:1305`](../ui/dr-ui/ui/library.slint#L1305), [`ui/dr-ui/ui/library.slint:831`](../ui/dr-ui/ui/library.slint#L831), [`ui/dr-ui/ui/library.slint:912`](../ui/dr-ui/ui/library.slint#L912), [`ui/dr-ui/ui/settings.slint:87`](../ui/dr-ui/ui/settings.slint#L87) |
|
||||
| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:252`](../core/dr-gpu/src/detail.rs#L252), [`core/dr-gpu/src/detail.rs:434`](../core/dr-gpu/src/detail.rs#L434), [`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:363`](../core/dr-pipeline/src/detail.rs#L363), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:422`](../core/dr-pipeline/src/detail.rs#L422), [`core/dr-pipeline/src/detail.rs:496`](../core/dr-pipeline/src/detail.rs#L496), [`core/dr-pipeline/src/graph.rs:109`](../core/dr-pipeline/src/graph.rs#L109), [`core/dr-pipeline/src/graph.rs:187`](../core/dr-pipeline/src/graph.rs#L187), [`core/dr-pipeline/src/graph.rs:587`](../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/history.rs:140`](../core/dr-pipeline/src/history.rs#L140), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299), [`core/dr-pipeline/src/operation.rs:517`](../core/dr-pipeline/src/operation.rs#L517), [`core/dr-pipeline/src/sidecar.rs:186`](../core/dr-pipeline/src/sidecar.rs#L186), [`core/dr-pipeline/src/sidecar.rs:330`](../core/dr-pipeline/src/sidecar.rs#L330), [`core/dr-pipeline/src/sidecar.rs:664`](../core/dr-pipeline/src/sidecar.rs#L664), [`core/dr-pipeline/src/sidecar.rs:800`](../core/dr-pipeline/src/sidecar.rs#L800), [`core/dr-pipeline/src/sidecar.rs:854`](../core/dr-pipeline/src/sidecar.rs#L854), [`core/dr-pipeline/src/sidecar.rs:884`](../core/dr-pipeline/src/sidecar.rs#L884), [`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/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.rs:1912`](../ui/dr-ui/src/develop.rs#L1912), [`ui/dr-ui/src/develop.rs:1949`](../ui/dr-ui/src/develop.rs#L1949), [`ui/dr-ui/src/develop.rs:2014`](../ui/dr-ui/src/develop.rs#L2014), [`ui/dr-ui/src/develop.rs:2096`](../ui/dr-ui/src/develop.rs#L2096), [`ui/dr-ui/src/develop.rs:2109`](../ui/dr-ui/src/develop.rs#L2109), [`ui/dr-ui/src/develop.rs:536`](../ui/dr-ui/src/develop.rs#L536), [`ui/dr-ui/src/lib.rs:1328`](../ui/dr-ui/src/lib.rs#L1328), [`ui/dr-ui/src/lib.rs:2228`](../ui/dr-ui/src/lib.rs#L2228), [`ui/dr-ui/src/lib.rs:296`](../ui/dr-ui/src/lib.rs#L296), [`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/adjust.slint:676`](../ui/dr-ui/ui/adjust.slint#L676), [`ui/dr-ui/ui/app.slint:1722`](../ui/dr-ui/ui/app.slint#L1722), [`ui/dr-ui/ui/app.slint:2239`](../ui/dr-ui/ui/app.slint#L2239), [`ui/dr-ui/ui/app.slint:2505`](../ui/dr-ui/ui/app.slint#L2505), [`ui/dr-ui/ui/app.slint:317`](../ui/dr-ui/ui/app.slint#L317), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) |
|
||||
| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2088`](../core/dr-gpu/src/adjust.rs#L2088), [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:2250`](../core/dr-gpu/src/adjust.rs#L2250), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:54`](../core/dr-gpu/src/lib.rs#L54), [`core/dr-gpu/src/lib.rs:94`](../core/dr-gpu/src/lib.rs#L94), [`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:263`](../core/dr-gpu/tests/local_contrast.rs#L263), [`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:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/graph.rs:449`](../core/dr-pipeline/src/graph.rs#L449), [`core/dr-pipeline/src/graph.rs:479`](../core/dr-pipeline/src/graph.rs#L479), [`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:654`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L654), [`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:662`](../core/dr-pipeline/src/ops/local_contrast.rs#L662), [`core/dr-pipeline/src/ops/noise_reduction.rs:695`](../core/dr-pipeline/src/ops/noise_reduction.rs#L695), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:2401`](../ui/dr-ui/src/develop.rs#L2401), [`ui/dr-ui/src/develop.rs:3244`](../ui/dr-ui/src/develop.rs#L3244), [`ui/dr-ui/src/develop.rs:3727`](../ui/dr-ui/src/develop.rs#L3727), [`ui/dr-ui/src/develop.rs:3761`](../ui/dr-ui/src/develop.rs#L3761), [`ui/dr-ui/src/lib.rs:60`](../ui/dr-ui/src/lib.rs#L60), [`ui/dr-ui/src/lib.rs:726`](../ui/dr-ui/src/lib.rs#L726), [`ui/dr-ui/src/lib.rs:785`](../ui/dr-ui/src/lib.rs#L785) |
|
||||
| FR-DEV-5 | [`core/dr-pipeline/src/history.rs:124`](../core/dr-pipeline/src/history.rs#L124), [`core/dr-pipeline/src/history.rs:140`](../core/dr-pipeline/src/history.rs#L140), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:55`](../core/dr-pipeline/src/history.rs#L55), [`core/dr-pipeline/src/history.rs:71`](../core/dr-pipeline/src/history.rs#L71), [`core/dr-pipeline/src/history.rs:79`](../core/dr-pipeline/src/history.rs#L79), [`ui/dr-ui/src/develop.rs:3128`](../ui/dr-ui/src/develop.rs#L3128), [`ui/dr-ui/src/develop.rs:3138`](../ui/dr-ui/src/develop.rs#L3138), [`ui/dr-ui/src/develop.rs:488`](../ui/dr-ui/src/develop.rs#L488), [`ui/dr-ui/src/lib.rs:1313`](../ui/dr-ui/src/lib.rs#L1313) |
|
||||
| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-types/src/settings.rs:182`](../core/dr-types/src/settings.rs#L182), [`ui/dr-ui/src/develop.rs:3067`](../ui/dr-ui/src/develop.rs#L3067), [`ui/dr-ui/src/develop.rs:3088`](../ui/dr-ui/src/develop.rs#L3088), [`ui/dr-ui/src/lib.rs:1285`](../ui/dr-ui/src/lib.rs#L1285), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:392`](../ui/dr-ui/src/library.rs#L392), [`ui/dr-ui/src/library_ui.rs:2576`](../ui/dr-ui/src/library_ui.rs#L2576), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:466`](../ui/dr-ui/src/library_ui.rs#L466), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/settings_ui.rs:535`](../ui/dr-ui/src/settings_ui.rs#L535), [`ui/dr-ui/ui/adjust.slint:598`](../ui/dr-ui/ui/adjust.slint#L598), [`ui/dr-ui/ui/library.slint:1305`](../ui/dr-ui/ui/library.slint#L1305), [`ui/dr-ui/ui/library.slint:831`](../ui/dr-ui/ui/library.slint#L831), [`ui/dr-ui/ui/library.slint:912`](../ui/dr-ui/ui/library.slint#L912), [`ui/dr-ui/ui/settings.slint:87`](../ui/dr-ui/ui/settings.slint#L87) |
|
||||
| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:252`](../core/dr-gpu/src/detail.rs#L252), [`core/dr-gpu/src/detail.rs:434`](../core/dr-gpu/src/detail.rs#L434), [`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:363`](../core/dr-pipeline/src/detail.rs#L363), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:422`](../core/dr-pipeline/src/detail.rs#L422), [`core/dr-pipeline/src/detail.rs:496`](../core/dr-pipeline/src/detail.rs#L496), [`core/dr-pipeline/src/graph.rs:109`](../core/dr-pipeline/src/graph.rs#L109), [`core/dr-pipeline/src/graph.rs:187`](../core/dr-pipeline/src/graph.rs#L187), [`core/dr-pipeline/src/graph.rs:587`](../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/history.rs:140`](../core/dr-pipeline/src/history.rs#L140), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299), [`core/dr-pipeline/src/operation.rs:517`](../core/dr-pipeline/src/operation.rs#L517), [`core/dr-pipeline/src/sidecar.rs:186`](../core/dr-pipeline/src/sidecar.rs#L186), [`core/dr-pipeline/src/sidecar.rs:330`](../core/dr-pipeline/src/sidecar.rs#L330), [`core/dr-pipeline/src/sidecar.rs:664`](../core/dr-pipeline/src/sidecar.rs#L664), [`core/dr-pipeline/src/sidecar.rs:800`](../core/dr-pipeline/src/sidecar.rs#L800), [`core/dr-pipeline/src/sidecar.rs:854`](../core/dr-pipeline/src/sidecar.rs#L854), [`core/dr-pipeline/src/sidecar.rs:884`](../core/dr-pipeline/src/sidecar.rs#L884), [`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/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.rs:1947`](../ui/dr-ui/src/develop.rs#L1947), [`ui/dr-ui/src/develop.rs:1984`](../ui/dr-ui/src/develop.rs#L1984), [`ui/dr-ui/src/develop.rs:2049`](../ui/dr-ui/src/develop.rs#L2049), [`ui/dr-ui/src/develop.rs:2131`](../ui/dr-ui/src/develop.rs#L2131), [`ui/dr-ui/src/develop.rs:2144`](../ui/dr-ui/src/develop.rs#L2144), [`ui/dr-ui/src/develop.rs:536`](../ui/dr-ui/src/develop.rs#L536), [`ui/dr-ui/src/lib.rs:1328`](../ui/dr-ui/src/lib.rs#L1328), [`ui/dr-ui/src/lib.rs:2228`](../ui/dr-ui/src/lib.rs#L2228), [`ui/dr-ui/src/lib.rs:296`](../ui/dr-ui/src/lib.rs#L296), [`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/adjust.slint:676`](../ui/dr-ui/ui/adjust.slint#L676), [`ui/dr-ui/ui/app.slint:1722`](../ui/dr-ui/ui/app.slint#L1722), [`ui/dr-ui/ui/app.slint:2239`](../ui/dr-ui/ui/app.slint#L2239), [`ui/dr-ui/ui/app.slint:2505`](../ui/dr-ui/ui/app.slint#L2505), [`ui/dr-ui/ui/app.slint:317`](../ui/dr-ui/ui/app.slint#L317), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) |
|
||||
| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2088`](../core/dr-gpu/src/adjust.rs#L2088), [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:2250`](../core/dr-gpu/src/adjust.rs#L2250), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:54`](../core/dr-gpu/src/lib.rs#L54), [`core/dr-gpu/src/lib.rs:94`](../core/dr-gpu/src/lib.rs#L94), [`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:263`](../core/dr-gpu/tests/local_contrast.rs#L263), [`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:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/graph.rs:449`](../core/dr-pipeline/src/graph.rs#L449), [`core/dr-pipeline/src/graph.rs:479`](../core/dr-pipeline/src/graph.rs#L479), [`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:654`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L654), [`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:662`](../core/dr-pipeline/src/ops/local_contrast.rs#L662), [`core/dr-pipeline/src/ops/noise_reduction.rs:695`](../core/dr-pipeline/src/ops/noise_reduction.rs#L695), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:2436`](../ui/dr-ui/src/develop.rs#L2436), [`ui/dr-ui/src/develop.rs:3294`](../ui/dr-ui/src/develop.rs#L3294), [`ui/dr-ui/src/develop.rs:3777`](../ui/dr-ui/src/develop.rs#L3777), [`ui/dr-ui/src/develop.rs:3811`](../ui/dr-ui/src/develop.rs#L3811), [`ui/dr-ui/src/lib.rs:60`](../ui/dr-ui/src/lib.rs#L60), [`ui/dr-ui/src/lib.rs:726`](../ui/dr-ui/src/lib.rs#L726), [`ui/dr-ui/src/lib.rs:785`](../ui/dr-ui/src/lib.rs#L785) |
|
||||
| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:446`](../core/dr-pipeline/src/operation.rs#L446), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1) |
|
||||
| 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.rs:2467`](../ui/dr-ui/src/develop.rs#L2467), [`ui/dr-ui/src/develop.rs:4829`](../ui/dr-ui/src/develop.rs#L4829), [`ui/dr-ui/src/develop.rs:4861`](../ui/dr-ui/src/develop.rs#L4861), [`ui/dr-ui/src/develop.rs:499`](../ui/dr-ui/src/develop.rs#L499), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1384`](../ui/dr-ui/src/lib.rs#L1384), [`ui/dr-ui/src/lib.rs:286`](../ui/dr-ui/src/lib.rs#L286), [`ui/dr-ui/ui/app.slint:277`](../ui/dr-ui/ui/app.slint#L277), [`ui/dr-ui/ui/histogram.slint:122`](../ui/dr-ui/ui/histogram.slint#L122), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) |
|
||||
| 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.rs:2502`](../ui/dr-ui/src/develop.rs#L2502), [`ui/dr-ui/src/develop.rs:4879`](../ui/dr-ui/src/develop.rs#L4879), [`ui/dr-ui/src/develop.rs:4911`](../ui/dr-ui/src/develop.rs#L4911), [`ui/dr-ui/src/develop.rs:499`](../ui/dr-ui/src/develop.rs#L499), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1384`](../ui/dr-ui/src/lib.rs#L1384), [`ui/dr-ui/src/lib.rs:286`](../ui/dr-ui/src/lib.rs#L286), [`ui/dr-ui/ui/app.slint:277`](../ui/dr-ui/ui/app.slint#L277), [`ui/dr-ui/ui/histogram.slint:122`](../ui/dr-ui/ui/histogram.slint#L122), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) |
|
||||
| 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-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:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2398`](../core/dr-gpu/src/adjust.rs#L2398), [`core/dr-pipeline/src/graph.rs:439`](../core/dr-pipeline/src/graph.rs#L439), [`core/dr-pipeline/src/graph.rs:493`](../core/dr-pipeline/src/graph.rs#L493), [`core/dr-pipeline/src/operation.rs:446`](../core/dr-pipeline/src/operation.rs#L446), [`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:595`](../core/dr-types/src/settings.rs#L595), [`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:1`](../core/dr-export/src/lib.rs#L1), [`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-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) |
|
||||
@@ -75,7 +75,7 @@ _None._
|
||||
| FR-EXP-6 | [`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/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:334`](../ui/dr-ui/src/lib.rs#L334), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:48`](../ui/dr-ui/src/settings_ui.rs#L48), [`ui/dr-ui/src/settings_ui.rs:590`](../ui/dr-ui/src/settings_ui.rs#L590) |
|
||||
| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:944`](../ui/dr-ui/src/export.rs#L944), [`ui/dr-ui/src/lib.rs:176`](../ui/dr-ui/src/lib.rs#L176), [`ui/dr-ui/src/lib.rs:1908`](../ui/dr-ui/src/lib.rs#L1908), [`ui/dr-ui/src/lib.rs:334`](../ui/dr-ui/src/lib.rs#L334), [`ui/dr-ui/src/lib.rs:369`](../ui/dr-ui/src/lib.rs#L369), [`ui/dr-ui/src/lib.rs:396`](../ui/dr-ui/src/lib.rs#L396), [`ui/dr-ui/src/library_ui.rs:3346`](../ui/dr-ui/src/library_ui.rs#L3346), [`ui/dr-ui/src/library_ui.rs:557`](../ui/dr-ui/src/library_ui.rs#L557), [`ui/dr-ui/src/library_ui.rs:6237`](../ui/dr-ui/src/library_ui.rs#L6237), [`ui/dr-ui/src/library_ui.rs:6314`](../ui/dr-ui/src/library_ui.rs#L6314), [`ui/dr-ui/src/library_ui.rs:6326`](../ui/dr-ui/src/library_ui.rs#L6326), [`ui/dr-ui/src/library_ui.rs:660`](../ui/dr-ui/src/library_ui.rs#L660), [`ui/dr-ui/src/library_ui.rs:717`](../ui/dr-ui/src/library_ui.rs#L717), [`ui/dr-ui/ui/app.slint:1052`](../ui/dr-ui/ui/app.slint#L1052), [`ui/dr-ui/ui/app.slint:1432`](../ui/dr-ui/ui/app.slint#L1432), [`ui/dr-ui/ui/library.slint:1313`](../ui/dr-ui/ui/library.slint#L1313), [`ui/dr-ui/ui/library.slint:835`](../ui/dr-ui/ui/library.slint#L835), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927) |
|
||||
| FR-EXP-8 | [`core/dr-decode/src/lib.rs:326`](../core/dr-decode/src/lib.rs#L326), [`core/dr-decode/src/lib.rs:350`](../core/dr-decode/src/lib.rs#L350), [`core/dr-decode/src/lib.rs:364`](../core/dr-decode/src/lib.rs#L364), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`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:136`](../core/dr-export/src/lib.rs#L136), [`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:444`](../core/dr-types/src/lib.rs#L444), [`core/dr-types/src/settings.rs:313`](../core/dr-types/src/settings.rs#L313), [`ui/dr-ui/src/export.rs:620`](../ui/dr-ui/src/export.rs#L620), [`ui/dr-ui/src/export.rs:648`](../ui/dr-ui/src/export.rs#L648), [`ui/dr-ui/src/export.rs:779`](../ui/dr-ui/src/export.rs#L779), [`ui/dr-ui/src/export.rs:796`](../ui/dr-ui/src/export.rs#L796), [`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:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1068`](../core/dr-gpu/src/adjust.rs#L1068), [`ui/dr-ui/src/develop.rs:2545`](../ui/dr-ui/src/develop.rs#L2545), [`ui/dr-ui/src/lib.rs:334`](../ui/dr-ui/src/lib.rs#L334) |
|
||||
| FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1068`](../core/dr-gpu/src/adjust.rs#L1068), [`ui/dr-ui/src/develop.rs:2580`](../ui/dr-ui/src/develop.rs#L2580), [`ui/dr-ui/src/lib.rs:334`](../ui/dr-ui/src/lib.rs#L334) |
|
||||
| FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44), [`core/dr-sync-nextcloud/src/session.rs:128`](../core/dr-sync-nextcloud/src/session.rs#L128), [`ui/dr-ui/src/launch.rs:256`](../ui/dr-ui/src/launch.rs#L256), [`ui/dr-ui/src/launch.rs:49`](../ui/dr-ui/src/launch.rs#L49), [`ui/dr-ui/src/launch_ui.rs:344`](../ui/dr-ui/src/launch_ui.rs#L344) |
|
||||
| FR-NC-10 | [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:396`](../ui/dr-ui/src/lib.rs#L396), [`ui/dr-ui/src/library.rs:1582`](../ui/dr-ui/src/library.rs#L1582), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library.rs:947`](../ui/dr-ui/src/library.rs#L947), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:3346`](../ui/dr-ui/src/library_ui.rs#L3346), [`ui/dr-ui/src/library_ui.rs:498`](../ui/dr-ui/src/library_ui.rs#L498), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
||||
| FR-NC-12 | [`core/dr-sync-nextcloud/src/lib.rs:34`](../core/dr-sync-nextcloud/src/lib.rs#L34), [`core/dr-sync-nextcloud/src/lib.rs:892`](../core/dr-sync-nextcloud/src/lib.rs#L892), [`core/dr-sync/src/lib.rs:157`](../core/dr-sync/src/lib.rs#L157), [`core/dr-sync/src/lib.rs:40`](../core/dr-sync/src/lib.rs#L40), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1) |
|
||||
@@ -101,7 +101,7 @@ _None._
|
||||
| FR-RAW-5 | [`core/dr-decode/src/lib.rs:167`](../core/dr-decode/src/lib.rs#L167), [`core/dr-gpu/src/demosaic.rs:34`](../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:602`](../core/dr-gpu/src/demosaic.rs#L602), [`core/dr-gpu/src/demosaic.rs:681`](../core/dr-gpu/src/demosaic.rs#L681), [`core/dr-gpu/src/demosaic.rs:805`](../core/dr-gpu/src/demosaic.rs#L805) |
|
||||
| FR-UI-1 | [`ui/dr-ui/src/lib.rs:2566`](../ui/dr-ui/src/lib.rs#L2566), [`ui/dr-ui/src/lib.rs:68`](../ui/dr-ui/src/lib.rs#L68), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/ui/library.slint:1031`](../ui/dr-ui/ui/library.slint#L1031) |
|
||||
| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:1508`](../ui/dr-ui/src/collections_ui.rs#L1508), [`ui/dr-ui/src/collections_ui.rs:1522`](../ui/dr-ui/src/collections_ui.rs#L1522), [`ui/dr-ui/src/collections_ui.rs:1568`](../ui/dr-ui/src/collections_ui.rs#L1568), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1666`](../ui/dr-ui/src/collections_ui.rs#L1666), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/lib.rs:68`](../ui/dr-ui/src/lib.rs#L68), [`ui/dr-ui/src/library_ui.rs:286`](../ui/dr-ui/src/library_ui.rs#L286), [`ui/dr-ui/src/library_ui.rs:5124`](../ui/dr-ui/src/library_ui.rs#L5124), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/src/library_ui.rs:6345`](../ui/dr-ui/src/library_ui.rs#L6345), [`ui/dr-ui/ui/app.slint:2345`](../ui/dr-ui/ui/app.slint#L2345), [`ui/dr-ui/ui/app.slint:263`](../ui/dr-ui/ui/app.slint#L263), [`ui/dr-ui/ui/app.slint:609`](../ui/dr-ui/ui/app.slint#L609), [`ui/dr-ui/ui/app.slint:616`](../ui/dr-ui/ui/app.slint#L616), [`ui/dr-ui/ui/library.slint:1186`](../ui/dr-ui/ui/library.slint#L1186), [`ui/dr-ui/ui/library.slint:1193`](../ui/dr-ui/ui/library.slint#L1193), [`ui/dr-ui/ui/library.slint:1199`](../ui/dr-ui/ui/library.slint#L1199), [`ui/dr-ui/ui/library.slint:2266`](../ui/dr-ui/ui/library.slint#L2266), [`ui/dr-ui/ui/library.slint:823`](../ui/dr-ui/ui/library.slint#L823), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/settings.slint:93`](../ui/dr-ui/ui/settings.slint#L93) |
|
||||
| FR-UI-3 | [`ui/dr-ui/src/develop.rs:1842`](../ui/dr-ui/src/develop.rs#L1842), [`ui/dr-ui/src/develop.rs:1949`](../ui/dr-ui/src/develop.rs#L1949), [`ui/dr-ui/src/library_ui.rs:4369`](../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:1987`](../ui/dr-ui/ui/app.slint#L1987), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682) |
|
||||
| FR-UI-3 | [`ui/dr-ui/src/develop.rs:1877`](../ui/dr-ui/src/develop.rs#L1877), [`ui/dr-ui/src/develop.rs:1984`](../ui/dr-ui/src/develop.rs#L1984), [`ui/dr-ui/src/library_ui.rs:4369`](../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:1987`](../ui/dr-ui/ui/app.slint#L1987), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682) |
|
||||
| FR-UI-4 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:131`](../ui/dr-ui/src/collections_ui.rs#L131), [`ui/dr-ui/src/collections_ui.rs:1508`](../ui/dr-ui/src/collections_ui.rs#L1508), [`ui/dr-ui/src/collections_ui.rs:1522`](../ui/dr-ui/src/collections_ui.rs#L1522), [`ui/dr-ui/src/collections_ui.rs:1568`](../ui/dr-ui/src/collections_ui.rs#L1568), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1666`](../ui/dr-ui/src/collections_ui.rs#L1666), [`ui/dr-ui/src/collections_ui.rs:1693`](../ui/dr-ui/src/collections_ui.rs#L1693), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:582`](../ui/dr-ui/src/collections_ui.rs#L582), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/library_ui.rs:4369`](../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/library_ui.rs:4414`](../ui/dr-ui/src/library_ui.rs#L4414), [`ui/dr-ui/src/library_ui.rs:4517`](../ui/dr-ui/src/library_ui.rs#L4517), [`ui/dr-ui/src/library_ui.rs:4545`](../ui/dr-ui/src/library_ui.rs#L4545), [`ui/dr-ui/src/library_ui.rs:5257`](../ui/dr-ui/src/library_ui.rs#L5257), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/ui/app.slint:1659`](../ui/dr-ui/ui/app.slint#L1659), [`ui/dr-ui/ui/app.slint:585`](../ui/dr-ui/ui/app.slint#L585), [`ui/dr-ui/ui/app.slint:616`](../ui/dr-ui/ui/app.slint#L616), [`ui/dr-ui/ui/library.slint:1186`](../ui/dr-ui/ui/library.slint#L1186), [`ui/dr-ui/ui/library.slint:1193`](../ui/dr-ui/ui/library.slint#L1193), [`ui/dr-ui/ui/library.slint:1199`](../ui/dr-ui/ui/library.slint#L1199), [`ui/dr-ui/ui/library.slint:2266`](../ui/dr-ui/ui/library.slint#L2266), [`ui/dr-ui/ui/library.slint:823`](../ui/dr-ui/ui/library.slint#L823), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:891`](../ui/dr-ui/ui/library.slint#L891) |
|
||||
| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2193`](../ui/dr-ui/src/lib.rs#L2193), [`ui/dr-ui/src/lib.rs:2600`](../ui/dr-ui/src/lib.rs#L2600), [`ui/dr-ui/src/lib.rs:2785`](../ui/dr-ui/src/lib.rs#L2785), [`ui/dr-ui/src/masks_ui.rs:863`](../ui/dr-ui/src/masks_ui.rs#L863), [`ui/dr-ui/ui/app.slint:298`](../ui/dr-ui/ui/app.slint#L298), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) |
|
||||
| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:100`](../core/dr-pipeline/src/descriptor.rs#L100), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259) |
|
||||
|
||||
+52
-2
@@ -770,6 +770,41 @@ fn no_choices() -> slint::ModelRc<slint::SharedString> {
|
||||
EMPTY.with(Clone::clone)
|
||||
}
|
||||
|
||||
/// The choices model for one enum parameter, built once per variant list.
|
||||
///
|
||||
/// Memoised for exactly the reason [`no_choices`] is shared: `ModelRc` compares
|
||||
/// by *identity*, so building a fresh one each call makes the row differ from
|
||||
/// itself on every parameter event. `sync_rows` would then replace the row —
|
||||
/// destroying the elements built from it, including whichever `TouchArea` is
|
||||
/// holding the current gesture — and the enum's own control would fight every
|
||||
/// slider drag elsewhere in the panel.
|
||||
///
|
||||
/// Curve rows solve the same problem the other way, by writing new values
|
||||
/// through the existing model. That is not available here: a variant list is
|
||||
/// fixed at compile time, so the model never needs updating and can simply be
|
||||
/// the same one every time.
|
||||
///
|
||||
/// Keyed on the labels rather than the slice's address, because they are
|
||||
/// resolved through the UI's catalogue and two operations offering the same
|
||||
/// choices should share one model.
|
||||
fn choices_model(labels: &[slint::SharedString]) -> slint::ModelRc<slint::SharedString> {
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
thread_local! {
|
||||
static CACHE: RefCell<HashMap<String, slint::ModelRc<slint::SharedString>>> =
|
||||
RefCell::new(HashMap::new());
|
||||
}
|
||||
let key = labels.join("\u{1f}");
|
||||
CACHE.with(|cache| {
|
||||
cache
|
||||
.borrow_mut()
|
||||
.entry(key)
|
||||
.or_insert_with(|| slint::ModelRc::new(slint::VecModel::from(labels.to_vec())))
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this frontend has an implementation of `widget` **anywhere**.
|
||||
///
|
||||
/// "Anywhere" is doing real work: a widget may be drawn in the panel, as the
|
||||
@@ -989,7 +1024,7 @@ pub(crate) fn rows_filtered(
|
||||
choices: if choices.is_empty() {
|
||||
no_choices()
|
||||
} else {
|
||||
slint::ModelRc::new(slint::VecModel::from(choices))
|
||||
choices_model(&choices)
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -2655,8 +2690,23 @@ impl DevelopSession {
|
||||
// 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 = dr_film::grain::FRAME_WIDTH_UM / source_width.max(1) as f32;
|
||||
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);
|
||||
|
||||
let baked = dr_film::bake(&dr_film::Recipe {
|
||||
|
||||
Reference in New Issue
Block a user