diff --git a/core/dr-pipeline/src/history.rs b/core/dr-pipeline/src/history.rs index cb5a9e3..5ca977f 100644 --- a/core/dr-pipeline/src/history.rs +++ b/core/dr-pipeline/src/history.rs @@ -129,7 +129,7 @@ impl Edit { /// where redo goes. `states` is never empty — the state the history was opened /// on is the floor, and undo stops there rather than at nothing. pub struct History { - states: Vec, + states: Vec, cursor: usize, /// What produced `states[cursor]`, and when — the pair that decides /// whether the next change amends it. `None` means the current step is @@ -137,11 +137,51 @@ pub struct History { last: Option<(Edit, Instant)>, } +/// TRACES: FR-DEV-5 | FR-DEV-8 +/// One remembered state of an edit. +/// +/// # Why this is not simply a `Preset` +/// +/// It was, and that was right while every edit in the graph was a parameter. +/// A [`Preset`] is a map of scalars, which is exactly what the module +/// documentation above says a snapshot should be — nothing to register, nothing +/// to fall behind the operation set. +/// +/// A repair is not a scalar (see [`crate::spot`]), and undo is the single most +/// expected thing to do with one: place a spot, dislike it, take it back. If +/// the snapshot could not carry the spot set, that press would step some +/// unrelated slider instead and leave the repair on the photograph — which is +/// worse than no undo at all, because it looks like undo is broken rather than +/// absent. +/// +/// So the state is the pair. The same door is what the mask stack will come +/// through — masks are outside undo today for precisely this reason, and +/// FR-DEV-5 is not finished until they are not. +#[derive(Debug, Clone, PartialEq)] +struct State { + params: Preset, + spots: crate::spot::SpotSet, +} + +impl State { + fn capture(graph: &EditGraph) -> Self { + Self { + params: Preset::capture(graph), + spots: graph.spots().clone(), + } + } + + fn apply(&self, graph: &mut EditGraph) { + self.params.apply(graph, Scope::Everything); + *graph.spots_mut() = self.spots.clone(); + } +} + impl History { /// Start from the state `graph` is in. pub fn new(graph: &EditGraph) -> Self { Self { - states: vec![Preset::capture(graph)], + states: vec![State::capture(graph)], cursor: 0, last: None, } @@ -168,7 +208,7 @@ impl History { /// [`Self::record`] with the clock supplied, so coalescing can be tested /// without sleeping. pub fn record_at(&mut self, graph: &EditGraph, edit: Edit, at: Instant) -> bool { - let state = Preset::capture(graph); + let state = State::capture(graph); if self.states.get(self.cursor) == Some(&state) { return false; } @@ -241,10 +281,10 @@ impl History { } fn restore(&mut self, graph: &mut EditGraph, target: usize) -> bool { - let Some(state) = self.states.get(target) else { + let Some(state) = self.states.get(target).cloned() else { return false; }; - state.apply(graph, Scope::Everything); + state.apply(graph); self.cursor = target; // Closes the current step. Without this, a slider moved immediately // after an undo would fold into the step it was just undone out of — diff --git a/core/dr-pipeline/tests/spots.rs b/core/dr-pipeline/tests/spots.rs index ba8806c..5bc61c8 100644 --- a/core/dr-pipeline/tests/spots.rs +++ b/core/dr-pipeline/tests/spots.rs @@ -239,3 +239,59 @@ fn modes_survive_their_own_names() { } assert_eq!(SpotMode::from_name("smudge"), None); } + +// --------------------------------------------------------------------------- +// Undo (FR-DEV-5) +// --------------------------------------------------------------------------- + +/// The press a photographer reaches for first: place a repair, dislike it, +/// take it back. Before the history carried the spot set this stepped some +/// unrelated slider and left the repair on the photograph, which reads as undo +/// being broken rather than absent. +#[test] +fn undo_takes_a_repair_back() { + use dr_pipeline::history::{Edit, History}; + + let mut graph = EditGraph::default_chain(); + let mut history = History::new(&graph); + + graph.spots_mut().place(spot_at((0.5, 0.5), (0.08, 0.0))); + assert!(history.record(&graph, Edit::Discrete)); + assert_eq!(graph.spots().len(), 1); + + assert!(history.undo(&mut graph)); + assert_eq!(graph.spots().len(), 0, "the repair is still on the frame"); + + assert!(history.redo(&mut graph)); + assert_eq!(graph.spots().len(), 1, "and redo could not put it back"); +} + +/// Moving a repair is undoable too, and separately from placing it: the two +/// are different decisions and a photographer who nudges a source expects one +/// press to return it, not to lose the spot entirely. +#[test] +fn undo_steps_back_through_a_moved_source() { + use dr_pipeline::history::{Edit, History}; + + let mut graph = EditGraph::default_chain(); + let id = graph + .spots_mut() + .place(spot_at((0.5, 0.5), (0.08, 0.0))) + .unwrap(); + let mut history = History::new(&graph); + + graph + .spots_mut() + .get_mut(&id) + .unwrap() + .set_offset((0.2, 0.1)); + history.record(&graph, Edit::Discrete); + + assert!(history.undo(&mut graph)); + assert_eq!( + graph.spots().get(&id).map(|s| s.offset), + Some((0.08, 0.0)), + "the source did not go back where it was" + ); + assert_eq!(graph.spots().len(), 1, "and the repair itself survived"); +} diff --git a/docs/traceability.md b/docs/traceability.md index 061fee9..017e5f8 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -10,7 +10,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| | Source files scanned | 232 | -| TRACES tags found | 648 | +| TRACES tags found | 649 | | Requirements defined | 177 | | Requirements covered | 91 | | **Coverage** | **51.4%** (91/177) | @@ -61,9 +61,9 @@ _None._ | 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:2341`](../ui/dr-ui/src/develop.rs#L2341), [`ui/dr-ui/src/develop.rs:2358`](../ui/dr-ui/src/develop.rs#L2358), [`ui/dr-ui/src/develop.rs:2389`](../ui/dr-ui/src/develop.rs#L2389), [`ui/dr-ui/src/develop.rs:2486`](../ui/dr-ui/src/develop.rs#L2486), [`ui/dr-ui/src/develop.rs:2800`](../ui/dr-ui/src/develop.rs#L2800), [`ui/dr-ui/src/lib.rs:1908`](../ui/dr-ui/src/lib.rs#L1908), [`ui/dr-ui/src/lib.rs:512`](../ui/dr-ui/src/lib.rs#L512), [`ui/dr-ui/src/lib.rs:570`](../ui/dr-ui/src/lib.rs#L570), [`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:879`](../ui/dr-ui/ui/adjust.slint#L879), [`ui/dr-ui/ui/adjust.slint:949`](../ui/dr-ui/ui/adjust.slint#L949), [`ui/dr-ui/ui/app.slint:2370`](../ui/dr-ui/ui/app.slint#L2370), [`ui/dr-ui/ui/app.slint:707`](../ui/dr-ui/ui/app.slint#L707) | | 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: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:2818`](../ui/dr-ui/src/develop.rs#L2818), [`ui/dr-ui/src/develop.rs:2828`](../ui/dr-ui/src/develop.rs#L2828), [`ui/dr-ui/src/develop.rs:488`](../ui/dr-ui/src/develop.rs#L488), [`ui/dr-ui/src/lib.rs:1267`](../ui/dr-ui/src/lib.rs#L1267) | +| 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:2818`](../ui/dr-ui/src/develop.rs#L2818), [`ui/dr-ui/src/develop.rs:2828`](../ui/dr-ui/src/develop.rs#L2828), [`ui/dr-ui/src/develop.rs:488`](../ui/dr-ui/src/develop.rs#L488), [`ui/dr-ui/src/lib.rs:1267`](../ui/dr-ui/src/lib.rs#L1267) | | 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:2757`](../ui/dr-ui/src/develop.rs#L2757), [`ui/dr-ui/src/develop.rs:2778`](../ui/dr-ui/src/develop.rs#L2778), [`ui/dr-ui/src/lib.rs:1239`](../ui/dr-ui/src/lib.rs#L1239), [`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/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:107`](../core/dr-pipeline/src/spot.rs#L107), [`core/dr-pipeline/src/spot.rs:143`](../core/dr-pipeline/src/spot.rs#L143), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:341`](../core/dr-pipeline/src/spot.rs#L341), [`core/dr-pipeline/src/spot.rs:426`](../core/dr-pipeline/src/spot.rs#L426), [`core/dr-pipeline/src/spot.rs:536`](../core/dr-pipeline/src/spot.rs#L536), [`core/dr-pipeline/src/spot.rs:627`](../core/dr-pipeline/src/spot.rs#L627), [`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) | +| 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:107`](../core/dr-pipeline/src/spot.rs#L107), [`core/dr-pipeline/src/spot.rs:143`](../core/dr-pipeline/src/spot.rs#L143), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:341`](../core/dr-pipeline/src/spot.rs#L341), [`core/dr-pipeline/src/spot.rs:426`](../core/dr-pipeline/src/spot.rs#L426), [`core/dr-pipeline/src/spot.rs:536`](../core/dr-pipeline/src/spot.rs#L536), [`core/dr-pipeline/src/spot.rs:627`](../core/dr-pipeline/src/spot.rs#L627), [`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) | | 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:627`](../core/dr-pipeline/src/spot.rs#L627), [`ui/dr-ui/src/develop.rs:2162`](../ui/dr-ui/src/develop.rs#L2162), [`ui/dr-ui/src/develop.rs:2984`](../ui/dr-ui/src/develop.rs#L2984), [`ui/dr-ui/src/develop.rs:3467`](../ui/dr-ui/src/develop.rs#L3467), [`ui/dr-ui/src/develop.rs:3501`](../ui/dr-ui/src/develop.rs#L3501), [`ui/dr-ui/src/lib.rs:59`](../ui/dr-ui/src/lib.rs#L59), [`ui/dr-ui/src/lib.rs:720`](../ui/dr-ui/src/lib.rs#L720) | | 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:2207`](../ui/dr-ui/src/develop.rs#L2207), [`ui/dr-ui/src/develop.rs:4569`](../ui/dr-ui/src/develop.rs#L4569), [`ui/dr-ui/src/develop.rs:4601`](../ui/dr-ui/src/develop.rs#L4601), [`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:1326`](../ui/dr-ui/src/lib.rs#L1326), [`ui/dr-ui/src/lib.rs:285`](../ui/dr-ui/src/lib.rs#L285), [`ui/dr-ui/ui/app.slint:276`](../ui/dr-ui/ui/app.slint#L276), [`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) |