image2/filters/
filter.rs

1use crate::*;
2
3/// Convert between colors
4#[derive(Clone, Copy, Default, Debug)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6struct Convert<T: Color>(std::marker::PhantomData<T>);
7
8/// Create new color conversion filter
9pub fn convert<T: Type, C: Color, U: Type, D: Color>() -> impl Filter<T, C, U, D> {
10    Convert(std::marker::PhantomData)
11}
12
13impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Convert<D> {
14    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
15        input.get_pixel(pt, None).convert_to_data(dest);
16    }
17}
18
19#[derive(Debug, Default)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21struct Saturation(pub f64);
22
23/// Adjust saturation
24pub fn saturation<T: Type, C: Color, U: Type, D: Color>(amt: f64) -> impl Filter<T, C, U, D> {
25    Saturation(amt)
26}
27
28impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Saturation {
29    fn compute_at(&self, pt: Point, input: &Input<T, C>, data: &mut DataMut<U, D>) {
30        let px = input.get_pixel(pt, None);
31        let mut tmp: Pixel<Hsv> = px.convert();
32        tmp[1] *= self.0;
33        tmp.convert_to_data(data);
34    }
35}
36
37#[derive(Debug)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39struct Brightness(f64);
40
41/// Adjust image brightness
42pub fn brightness<T: Type, C: Color, U: Type, D: Color>(amt: f64) -> impl Filter<T, C, U, D> {
43    Brightness(amt)
44}
45
46impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Brightness {
47    fn compute_at(&self, pt: Point, input: &Input<T, C>, data: &mut DataMut<U, D>) {
48        let mut px = input.get_pixel(pt, None);
49        px *= self.0;
50        px.convert_to_data(data);
51    }
52}
53
54#[derive(Debug)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56struct Exposure(f64);
57
58/// Adjust image exposure, the argument is the number of stops to increase or decrease exposure by
59pub fn exposure<T: Type, C: Color, U: Type, D: Color>(stops: f64) -> impl Filter<T, C, U, D> {
60    Exposure(stops)
61}
62
63impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Exposure {
64    fn compute_at(&self, pt: Point, input: &Input<T, C>, data: &mut DataMut<U, D>) {
65        let mut px = input.get_pixel(pt, None);
66        px *= 2f64.powf(self.0);
67        px.convert_to_data(data);
68    }
69}
70
71#[derive(Debug)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73struct Contrast(pub f64);
74
75/// Adjust image contrast
76pub fn contrast<T: Type, C: Color, U: Type, D: Color>(amt: f64) -> impl Filter<T, C, U, D> {
77    Contrast(amt)
78}
79
80impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Contrast {
81    fn compute_at(&self, pt: Point, input: &Input<T, C>, data: &mut DataMut<U, D>) {
82        let mut px = input.get_pixel(pt, None);
83        px.map(|x| (self.0 * (x - 0.5)) + 0.5);
84        px.convert_to_data(data);
85    }
86}
87
88#[derive(Debug)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90struct Crop(Region);
91
92/// Crop an image
93pub fn crop<T: Type, C: Color, U: Type, D: Color>(r: Region) -> impl Filter<T, C, U, D> {
94    Crop(r)
95}
96
97impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Crop {
98    fn output_size(&self, _input: &Input<T, C>, _dest: &mut Image<U, D>) -> Size {
99        self.0.size
100    }
101
102    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
103        if pt.x > self.0.origin.x + self.0.size.width || pt.y > self.0.origin.y + self.0.size.height
104        {
105            return;
106        }
107
108        let x = pt.x + self.0.origin.x;
109        let y = pt.y + self.0.origin.y;
110        let px = input.get_pixel((x, y), None);
111        px.copy_to_slice(dest);
112    }
113}
114
115#[derive(Debug)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117struct Invert;
118
119/// Invert an image
120pub fn invert<T: Type, C: Color, U: Type, D: Color>() -> impl Filter<T, C, U, D> {
121    Invert
122}
123
124impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Invert {
125    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
126        let mut px = input.get_pixel(pt, None);
127        px.map(|x| 1.0 - x);
128        px.copy_to_slice(dest);
129    }
130}
131
132#[derive(Debug)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134struct Blend;
135
136/// Average two images
137pub fn blend<T: Type, C: Color, U: Type, D: Color>() -> impl Filter<T, C, U, D> {
138    Blend
139}
140
141impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Blend {
142    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
143        let a = input.get_pixel(pt, None);
144        let b = input.get_pixel(pt, Some(1));
145        ((a + &b) / 2.).copy_to_slice(dest);
146    }
147}
148
149#[derive(Debug)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
151struct GammaLog(f64);
152
153/// Convert to log gamma
154pub fn gamma_log<T: Type, C: Color, U: Type, D: Color>(
155    gamma: Option<f64>,
156) -> impl Filter<T, C, U, D> {
157    GammaLog(gamma.unwrap_or(2.2))
158}
159
160impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for GammaLog {
161    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
162        let mut px = input.get_pixel(pt, None);
163        px.map(|x| x.powf(1.0 / self.0));
164        px.copy_to_slice(dest);
165    }
166}
167
168#[derive(Debug)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170struct GammaLin(f64);
171
172/// Convert to linear gamma
173pub fn gamma_lin<T: Type, C: Color, U: Type, D: Color>(
174    gamma: Option<f64>,
175) -> impl Filter<T, C, U, D> {
176    GammaLin(gamma.unwrap_or(2.2))
177}
178
179impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for GammaLin {
180    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
181        let mut px = input.get_pixel(pt, None);
182        px.map(|x| x.powf(self.0));
183        px.copy_to_slice(dest);
184    }
185}
186
187/// Conditional filter
188struct If<
189    F: Fn(Point, &Input<T, C>) -> bool,
190    G: Filter<T, C, U, D>,
191    H: Filter<T, C, U, D>,
192    T: Type,
193    C: Color,
194    U: Type,
195    D: Color,
196> {
197    cond: F,
198    then: G,
199    else_: H,
200    _t: std::marker::PhantomData<(T, C, U, D)>,
201}
202
203/// Create new conditional filter
204pub fn if_then_else<
205    F: Sync + Fn(Point, &Input<T, C>) -> bool,
206    G: Filter<T, C, U, D>,
207    H: Filter<T, C, U, D>,
208    T: Type,
209    C: Color,
210    U: Type,
211    D: Color,
212>(
213    cond: F,
214    then: G,
215    else_: H,
216) -> impl Filter<T, C, U, D> {
217    If {
218        cond,
219        then,
220        else_,
221        _t: std::marker::PhantomData,
222    }
223}
224
225impl<
226        F: Fn(Point, &Input<T, C>) -> bool,
227        G: Filter<T, C, U, D>,
228        H: Filter<T, C, U, D>,
229        T: Type,
230        C: Color,
231        U: Type,
232        D: Color,
233    > std::fmt::Debug for If<F, G, H, T, C, U, D>
234{
235    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
236        fmt.debug_struct("If")
237            .field("cond", &"Function")
238            .field("then", &self.then)
239            .field("else", &self.else_)
240            .finish()
241    }
242}
243
244impl<
245        F: Sync + Fn(Point, &Input<T, C>) -> bool,
246        G: Filter<T, C, U, D>,
247        H: Filter<T, C, U, D>,
248        T: Type,
249        C: Color,
250        U: Type,
251        D: Color,
252    > Filter<T, C, U, D> for If<F, G, H, T, C, U, D>
253{
254    fn schedule(&self) -> Schedule {
255        if self.then.schedule() == Schedule::Image || self.else_.schedule() == Schedule::Image {
256            return Schedule::Image;
257        }
258
259        Schedule::Pixel
260    }
261
262    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
263        if (self.cond)(pt, input) {
264            self.then.compute_at(pt, input, dest)
265        } else {
266            self.else_.compute_at(pt, input, dest)
267        }
268    }
269}
270
271#[derive(Debug)]
272#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
273struct Clamp;
274
275/// Clamp pixel values
276pub fn clamp<T: Type, C: Color, U: Type, D: Color>() -> impl Filter<T, C, U, D> {
277    Clamp
278}
279
280impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Clamp {
281    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
282        input.get_pixel(pt, None).clamped().copy_to_slice(dest)
283    }
284}
285
286#[derive(Debug)]
287#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
288struct Normalize {
289    min: f64,
290    max: f64,
291    new_min: f64,
292    new_max: f64,
293}
294
295/// Normalize image data
296pub fn normalize<T: Type, C: Color, U: Type, D: Color>(
297    min: f64,
298    max: f64,
299    new_min: f64,
300    new_max: f64,
301) -> impl Filter<T, C, U, D> {
302    Normalize {
303        min,
304        max,
305        new_min,
306        new_max,
307    }
308}
309
310impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Normalize {
311    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
312        input
313            .get_pixel(pt, None)
314            .map(|x| {
315                (x - self.min) * ((self.new_max - self.new_min) / (self.max - self.min))
316                    + self.new_min
317            })
318            .copy_to_slice(dest)
319    }
320}
321
322#[derive(Debug)]
323#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
324struct Noop;
325
326/// Filter that does nothing
327pub fn noop<T: Type, C: Color, U: Type, D: Color>() -> impl Filter<T, C, U, D> {
328    Noop
329}
330
331impl<T: Type, C: Color, U: Type, D: Color> Filter<T, C, U, D> for Noop {
332    fn compute_at(&self, pt: Point, input: &Input<T, C>, dest: &mut DataMut<U, D>) {
333        input.get_pixel(pt, None).copy_to_slice(dest)
334    }
335}
336
337#[inline]
338/// Build rotation `Transform` using the specified degrees and center point
339pub fn rotate<T: Type, C: Color, U: Type, D: Color>(
340    deg: f64,
341    center: Point,
342) -> impl Filter<T, C, U, D> {
343    let center = center.to_tuple();
344    Transform::rotation(euclid::Angle::degrees(-deg))
345        .pre_translate(euclid::Vector2D::new(
346            -(center.0 as f64),
347            -(center.1 as f64),
348        ))
349        .then_translate(euclid::Vector2D::new(center.0 as f64, center.1 as f64))
350}
351
352#[inline]
353/// Build scale `Transform`
354pub fn scale<T: Type, C: Color, U: Type, D: Color>(x: f64, y: f64) -> impl Filter<T, C, U, D> {
355    Transform::scale(1.0 / x, 1.0 / y)
356}
357
358#[inline]
359/// Build resize transform
360pub fn resize<T: Type, C: Color, U: Type, D: Color>(
361    from: Size,
362    to: Size,
363) -> impl Filter<T, C, U, D> {
364    Transform::scale(
365        from.width as f64 / to.width as f64,
366        from.height as f64 / to.height as f64,
367    )
368}
369
370/// 90 degree rotation
371pub fn rotate90<T: Type, C: Color, U: Type, D: Color>(
372    from: Size,
373    to: Size,
374) -> impl Filter<T, C, U, D> {
375    let dwidth = to.width as f64;
376    let height = from.height as f64;
377    rotate(
378        90.,
379        Point::new((dwidth / 2.) as usize, (height / 2.) as usize),
380    )
381}
382
383/// 180 degree rotation
384pub fn rotate180<T: Type, C: Color, U: Type, D: Color>(src: Size) -> impl Filter<T, C, U, D> {
385    let dwidth = src.width as f64;
386    let height = src.height as f64;
387    rotate(
388        180.,
389        Point::new((dwidth / 2.) as usize, (height / 2.) as usize),
390    )
391}
392
393/// 270 degree rotation
394pub fn rotate270<T: Type, C: Color, U: Type, D: Color>(
395    from: Size,
396    to: Size,
397) -> impl Filter<T, C, U, D> {
398    let width = to.height as f64;
399    let dheight = from.width as f64;
400    rotate(
401        270.,
402        Point::new((width / 2.) as usize, (dheight / 2.) as usize),
403    )
404}