1pub mod denoise;
22pub mod effect;
23pub mod grain;
24
25pub use denoise::{Denoise, Motion, Overrides, Preset, Spatial, Temporal};
26pub use grain::Grain;
27
28use std::fmt;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub enum Point {
33 Source,
36 Output,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum Footprint {
43 Pixel,
45 Spatial { radius: u32 },
47 Temporal { past: u32, future: u32, radius: u32 },
51}
52
53impl Footprint {
54 pub const fn gathers(self) -> bool {
57 !matches!(self, Self::Pixel)
58 }
59
60 pub const fn past(self) -> u32 {
62 match self {
63 Self::Temporal { past, .. } => past,
64 _ => 0,
65 }
66 }
67
68 pub const fn future(self) -> u32 {
70 match self {
71 Self::Temporal { future, .. } => future,
72 _ => 0,
73 }
74 }
75}
76
77pub trait Filter: fmt::Debug + Send + Sync {
79 fn name(&self) -> &'static str;
81 fn point(&self) -> Point;
82 fn footprint(&self) -> Footprint;
83 fn motion(&self) -> bool {
85 false
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct Pass {
92 pub point: Point,
93 pub filters: Vec<usize>,
95 pub past: u32,
97 pub future: u32,
99 pub motion: bool,
100}
101
102#[derive(Debug, Default)]
104pub struct FilterChain {
105 pub filters: Vec<Box<dyn Filter>>,
106}
107
108impl FilterChain {
109 #[must_use]
110 pub fn push(mut self, filter: impl Filter + 'static) -> Self {
111 self.filters.push(Box::new(filter));
112 self
113 }
114
115 pub fn plan(&self) -> Vec<Pass> {
117 let mut order: Vec<usize> = (0..self.filters.len()).collect();
118 order.sort_by_key(|&i| self.filters[i].point());
119 let mut passes: Vec<Pass> = Vec::new();
120 for i in order {
121 let f = &self.filters[i];
122 let fp = f.footprint();
123 match passes.last_mut() {
124 Some(p) if p.point == f.point() && !fp.gathers() => p.filters.push(i),
125 _ => passes.push(Pass {
126 point: f.point(),
127 filters: vec![i],
128 past: fp.past(),
129 future: fp.future(),
130 motion: f.motion(),
131 }),
132 }
133 }
134 passes
135 }
136}
137
138impl Filter for Temporal {
139 fn name(&self) -> &'static str {
140 "denoise.temporal"
141 }
142 fn point(&self) -> Point {
143 Point::Source
144 }
145 fn footprint(&self) -> Footprint {
146 Footprint::Temporal {
147 past: self.radius,
148 future: self.radius,
149 radius: 1,
150 }
151 }
152 fn motion(&self) -> bool {
153 true
154 }
155}
156
157impl Filter for Spatial {
158 fn name(&self) -> &'static str {
159 "denoise.spatial"
160 }
161 fn point(&self) -> Point {
162 Point::Source
163 }
164 fn footprint(&self) -> Footprint {
165 Footprint::Spatial {
166 radius: self.radius + 1,
167 }
168 }
169}
170
171impl Filter for Grain {
172 fn name(&self) -> &'static str {
173 "grain"
174 }
175 fn point(&self) -> Point {
176 Point::Output
177 }
178 fn footprint(&self) -> Footprint {
179 Footprint::Pixel
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[derive(Debug)]
188 struct Stub(&'static str, Point, Footprint);
189
190 impl Filter for Stub {
191 fn name(&self) -> &'static str {
192 self.0
193 }
194 fn point(&self) -> Point {
195 self.1
196 }
197 fn footprint(&self) -> Footprint {
198 self.2
199 }
200 fn motion(&self) -> bool {
201 self.2.past() > 0
202 }
203 }
204
205 fn filters(p: &Pass) -> Vec<usize> {
206 p.filters.clone()
207 }
208
209 #[test]
210 fn pointwise_filters_fuse_behind_a_gather() {
211 let chain = FilterChain::default()
212 .push(Stub(
213 "denoise",
214 Point::Source,
215 Footprint::Temporal {
216 past: 2,
217 future: 2,
218 radius: 3,
219 },
220 ))
221 .push(Stub("lift", Point::Source, Footprint::Pixel))
222 .push(Stub("grain", Point::Output, Footprint::Pixel));
223 let plan = chain.plan();
224 assert_eq!(plan.len(), 2);
225 assert_eq!(
226 (filters(&plan[0]), plan[0].past, plan[0].motion),
227 (vec![0, 1], 2, true)
228 );
229 assert_eq!((plan[1].point, filters(&plan[1])), (Point::Output, vec![2]));
230 }
231
232 #[test]
233 fn each_gather_starts_a_dispatch_and_points_sort() {
234 let chain = FilterChain::default()
235 .push(Stub(
236 "sharpen",
237 Point::Output,
238 Footprint::Spatial { radius: 2 },
239 ))
240 .push(Stub(
241 "deband",
242 Point::Source,
243 Footprint::Spatial { radius: 16 },
244 ))
245 .push(Stub(
246 "denoise",
247 Point::Source,
248 Footprint::Spatial { radius: 7 },
249 ));
250 let plan: Vec<_> = chain.plan().iter().map(filters).collect();
251 assert_eq!(plan, [vec![1], vec![2], vec![0]]);
252 }
253}