1use num_traits::NumCast;
4
5use crate::color::{FromColor, IntoColor, Luma, LumaA};
6use crate::image::{GenericImage, GenericImageView};
7use crate::traits::{Pixel, Primitive};
8use crate::utils::clamp;
9use crate::ImageBuffer;
10
11type Subpixel<I> = <<I as GenericImageView>::Pixel as Pixel>::Subpixel;
12
13pub fn grayscale<I: GenericImageView>(
15    image: &I,
16) -> ImageBuffer<Luma<Subpixel<I>>, Vec<Subpixel<I>>> {
17    grayscale_with_type(image)
18}
19
20pub fn grayscale_alpha<I: GenericImageView>(
22    image: &I,
23) -> ImageBuffer<LumaA<Subpixel<I>>, Vec<Subpixel<I>>> {
24    grayscale_with_type_alpha(image)
25}
26
27pub fn grayscale_with_type<NewPixel, I: GenericImageView>(
29    image: &I,
30) -> ImageBuffer<NewPixel, Vec<NewPixel::Subpixel>>
31where
32    NewPixel: Pixel + FromColor<Luma<Subpixel<I>>>,
33{
34    let (width, height) = image.dimensions();
35    let mut out = ImageBuffer::new(width, height);
36
37    for (x, y, pixel) in image.pixels() {
38        let grayscale = pixel.to_luma();
39        let new_pixel = grayscale.into_color(); out.put_pixel(x, y, new_pixel);
42    }
43
44    out
45}
46
47pub fn grayscale_with_type_alpha<NewPixel, I: GenericImageView>(
49    image: &I,
50) -> ImageBuffer<NewPixel, Vec<NewPixel::Subpixel>>
51where
52    NewPixel: Pixel + FromColor<LumaA<Subpixel<I>>>,
53{
54    let (width, height) = image.dimensions();
55    let mut out = ImageBuffer::new(width, height);
56
57    for (x, y, pixel) in image.pixels() {
58        let grayscale = pixel.to_luma_alpha();
59        let new_pixel = grayscale.into_color(); out.put_pixel(x, y, new_pixel);
62    }
63
64    out
65}
66
67pub fn invert<I: GenericImage>(image: &mut I) {
70    let (width, height) = image.dimensions();
72
73    for y in 0..height {
74        for x in 0..width {
75            let mut p = image.get_pixel(x, y);
76            p.invert();
77
78            image.put_pixel(x, y, p);
79        }
80    }
81}
82
83pub fn contrast<I, P, S>(image: &I, contrast: f32) -> ImageBuffer<P, Vec<S>>
89where
90    I: GenericImageView<Pixel = P>,
91    P: Pixel<Subpixel = S> + 'static,
92    S: Primitive + 'static,
93{
94    let (width, height) = image.dimensions();
95    let mut out = ImageBuffer::new(width, height);
96
97    let max = S::DEFAULT_MAX_VALUE;
98    let max: f32 = NumCast::from(max).unwrap();
99
100    let percent = ((100.0 + contrast) / 100.0).powi(2);
101
102    for (x, y, pixel) in image.pixels() {
103        let f = pixel.map(|b| {
104            let c: f32 = NumCast::from(b).unwrap();
105
106            let d = ((c / max - 0.5) * percent + 0.5) * max;
107            let e = clamp(d, 0.0, max);
108
109            NumCast::from(e).unwrap()
110        });
111        out.put_pixel(x, y, f);
112    }
113
114    out
115}
116
117pub fn contrast_in_place<I>(image: &mut I, contrast: f32)
123where
124    I: GenericImage,
125{
126    let (width, height) = image.dimensions();
127
128    let max = <I::Pixel as Pixel>::Subpixel::DEFAULT_MAX_VALUE;
129    let max: f32 = NumCast::from(max).unwrap();
130
131    let percent = ((100.0 + contrast) / 100.0).powi(2);
132
133    for y in 0..height {
135        for x in 0..width {
136            let f = image.get_pixel(x, y).map(|b| {
137                let c: f32 = NumCast::from(b).unwrap();
138
139                let d = ((c / max - 0.5) * percent + 0.5) * max;
140                let e = clamp(d, 0.0, max);
141
142                NumCast::from(e).unwrap()
143            });
144
145            image.put_pixel(x, y, f);
146        }
147    }
148}
149
150pub fn brighten<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>>
156where
157    I: GenericImageView<Pixel = P>,
158    P: Pixel<Subpixel = S> + 'static,
159    S: Primitive + 'static,
160{
161    let (width, height) = image.dimensions();
162    let mut out = ImageBuffer::new(width, height);
163
164    let max = S::DEFAULT_MAX_VALUE;
165    let max: i32 = NumCast::from(max).unwrap();
166
167    for (x, y, pixel) in image.pixels() {
168        let e = pixel.map_with_alpha(
169            |b| {
170                let c: i32 = NumCast::from(b).unwrap();
171                let d = clamp(c + value, 0, max);
172
173                NumCast::from(d).unwrap()
174            },
175            |alpha| alpha,
176        );
177        out.put_pixel(x, y, e);
178    }
179
180    out
181}
182
183pub fn brighten_in_place<I>(image: &mut I, value: i32)
189where
190    I: GenericImage,
191{
192    let (width, height) = image.dimensions();
193
194    let max = <I::Pixel as Pixel>::Subpixel::DEFAULT_MAX_VALUE;
195    let max: i32 = NumCast::from(max).unwrap(); for y in 0..height {
199        for x in 0..width {
200            let e = image.get_pixel(x, y).map_with_alpha(
201                |b| {
202                    let c: i32 = NumCast::from(b).unwrap();
203                    let d = clamp(c + value, 0, max);
204
205                    NumCast::from(d).unwrap()
206                },
207                |alpha| alpha,
208            );
209
210            image.put_pixel(x, y, e);
211        }
212    }
213}
214
215pub fn huerotate<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>>
222where
223    I: GenericImageView<Pixel = P>,
224    P: Pixel<Subpixel = S> + 'static,
225    S: Primitive + 'static,
226{
227    let (width, height) = image.dimensions();
228    let mut out = ImageBuffer::new(width, height);
229
230    let angle: f64 = NumCast::from(value).unwrap();
231
232    let cosv = angle.to_radians().cos();
233    let sinv = angle.to_radians().sin();
234    let matrix: [f64; 9] = [
235        0.213 + cosv * 0.787 - sinv * 0.213,
237        0.715 - cosv * 0.715 - sinv * 0.715,
238        0.072 - cosv * 0.072 + sinv * 0.928,
239        0.213 - cosv * 0.213 + sinv * 0.143,
241        0.715 + cosv * 0.285 + sinv * 0.140,
242        0.072 - cosv * 0.072 - sinv * 0.283,
243        0.213 - cosv * 0.213 - sinv * 0.787,
245        0.715 - cosv * 0.715 + sinv * 0.715,
246        0.072 + cosv * 0.928 + sinv * 0.072,
247    ];
248    for (x, y, pixel) in out.enumerate_pixels_mut() {
249        let p = image.get_pixel(x, y);
250
251        #[allow(deprecated)]
252        let (k1, k2, k3, k4) = p.channels4();
253        let vec: (f64, f64, f64, f64) = (
254            NumCast::from(k1).unwrap(),
255            NumCast::from(k2).unwrap(),
256            NumCast::from(k3).unwrap(),
257            NumCast::from(k4).unwrap(),
258        );
259
260        let r = vec.0;
261        let g = vec.1;
262        let b = vec.2;
263
264        let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b;
265        let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b;
266        let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b;
267        let max = 255f64;
268
269        #[allow(deprecated)]
270        let outpixel = Pixel::from_channels(
271            NumCast::from(clamp(new_r, 0.0, max)).unwrap(),
272            NumCast::from(clamp(new_g, 0.0, max)).unwrap(),
273            NumCast::from(clamp(new_b, 0.0, max)).unwrap(),
274            NumCast::from(clamp(vec.3, 0.0, max)).unwrap(),
275        );
276        *pixel = outpixel;
277    }
278    out
279}
280
281pub fn huerotate_in_place<I>(image: &mut I, value: i32)
289where
290    I: GenericImage,
291{
292    let (width, height) = image.dimensions();
293
294    let angle: f64 = NumCast::from(value).unwrap();
295
296    let cosv = angle.to_radians().cos();
297    let sinv = angle.to_radians().sin();
298    let matrix: [f64; 9] = [
299        0.213 + cosv * 0.787 - sinv * 0.213,
301        0.715 - cosv * 0.715 - sinv * 0.715,
302        0.072 - cosv * 0.072 + sinv * 0.928,
303        0.213 - cosv * 0.213 + sinv * 0.143,
305        0.715 + cosv * 0.285 + sinv * 0.140,
306        0.072 - cosv * 0.072 - sinv * 0.283,
307        0.213 - cosv * 0.213 - sinv * 0.787,
309        0.715 - cosv * 0.715 + sinv * 0.715,
310        0.072 + cosv * 0.928 + sinv * 0.072,
311    ];
312
313    for y in 0..height {
315        for x in 0..width {
316            let pixel = image.get_pixel(x, y);
317
318            #[allow(deprecated)]
319            let (k1, k2, k3, k4) = pixel.channels4();
320
321            let vec: (f64, f64, f64, f64) = (
322                NumCast::from(k1).unwrap(),
323                NumCast::from(k2).unwrap(),
324                NumCast::from(k3).unwrap(),
325                NumCast::from(k4).unwrap(),
326            );
327
328            let r = vec.0;
329            let g = vec.1;
330            let b = vec.2;
331
332            let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b;
333            let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b;
334            let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b;
335            let max = 255f64;
336
337            #[allow(deprecated)]
338            let outpixel = Pixel::from_channels(
339                NumCast::from(clamp(new_r, 0.0, max)).unwrap(),
340                NumCast::from(clamp(new_g, 0.0, max)).unwrap(),
341                NumCast::from(clamp(new_b, 0.0, max)).unwrap(),
342                NumCast::from(clamp(vec.3, 0.0, max)).unwrap(),
343            );
344
345            image.put_pixel(x, y, outpixel);
346        }
347    }
348}
349
350pub trait ColorMap {
352    type Color;
354    fn index_of(&self, color: &Self::Color) -> usize;
357    fn lookup(&self, index: usize) -> Option<Self::Color> {
360        let _ = index;
361        None
362    }
363    fn has_lookup(&self) -> bool {
365        false
366    }
367    fn map_color(&self, color: &mut Self::Color);
369}
370
371#[derive(Clone, Copy)]
401pub struct BiLevel;
402
403impl ColorMap for BiLevel {
404    type Color = Luma<u8>;
405
406    #[inline(always)]
407    fn index_of(&self, color: &Luma<u8>) -> usize {
408        let luma = color.0;
409        if luma[0] > 127 {
410            1
411        } else {
412            0
413        }
414    }
415
416    #[inline(always)]
417    fn lookup(&self, idx: usize) -> Option<Self::Color> {
418        match idx {
419            0 => Some([0].into()),
420            1 => Some([255].into()),
421            _ => None,
422        }
423    }
424
425    fn has_lookup(&self) -> bool {
427        true
428    }
429
430    #[inline(always)]
431    fn map_color(&self, color: &mut Luma<u8>) {
432        let new_color = 0xFF * self.index_of(color) as u8;
433        let luma = &mut color.0;
434        luma[0] = new_color;
435    }
436}
437
438#[cfg(feature = "color_quant")]
439impl ColorMap for color_quant::NeuQuant {
440    type Color = crate::color::Rgba<u8>;
441
442    #[inline(always)]
443    fn index_of(&self, color: &Self::Color) -> usize {
444        self.index_of(color.channels())
445    }
446
447    #[inline(always)]
448    fn lookup(&self, idx: usize) -> Option<Self::Color> {
449        self.lookup(idx).map(|p| p.into())
450    }
451
452    fn has_lookup(&self) -> bool {
454        true
455    }
456
457    #[inline(always)]
458    fn map_color(&self, color: &mut Self::Color) {
459        self.map_pixel(color.channels_mut())
460    }
461}
462
463fn diffuse_err<P: Pixel<Subpixel = u8>>(pixel: &mut P, error: [i16; 3], factor: i16) {
465    for (e, c) in error.iter().zip(pixel.channels_mut().iter_mut()) {
466        *c = match <i16 as From<_>>::from(*c) + e * factor / 16 {
467            val if val < 0 => 0,
468            val if val > 0xFF => 0xFF,
469            val => val as u8,
470        }
471    }
472}
473
474macro_rules! do_dithering(
475    ($map:expr, $image:expr, $err:expr, $x:expr, $y:expr) => (
476        {
477            let old_pixel = $image[($x, $y)];
478            let new_pixel = $image.get_pixel_mut($x, $y);
479            $map.map_color(new_pixel);
480            for ((e, &old), &new) in $err.iter_mut()
481                                        .zip(old_pixel.channels().iter())
482                                        .zip(new_pixel.channels().iter())
483            {
484                *e = <i16 as From<_>>::from(old) - <i16 as From<_>>::from(new)
485            }
486        }
487    )
488);
489
490pub fn dither<Pix, Map>(image: &mut ImageBuffer<Pix, Vec<u8>>, color_map: &Map)
493where
494    Map: ColorMap<Color = Pix> + ?Sized,
495    Pix: Pixel<Subpixel = u8> + 'static,
496{
497    let (width, height) = image.dimensions();
498    let mut err: [i16; 3] = [0; 3];
499    for y in 0..height - 1 {
500        let x = 0;
501        do_dithering!(color_map, image, err, x, y);
502        diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
503        diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
504        diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
505        for x in 1..width - 1 {
506            do_dithering!(color_map, image, err, x, y);
507            diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
508            diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
509            diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
510            diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
511        }
512        let x = width - 1;
513        do_dithering!(color_map, image, err, x, y);
514        diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
515        diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
516    }
517    let y = height - 1;
518    let x = 0;
519    do_dithering!(color_map, image, err, x, y);
520    diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
521    for x in 1..width - 1 {
522        do_dithering!(color_map, image, err, x, y);
523        diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
524    }
525    let x = width - 1;
526    do_dithering!(color_map, image, err, x, y);
527}
528
529pub fn index_colors<Pix, Map>(
531    image: &ImageBuffer<Pix, Vec<u8>>,
532    color_map: &Map,
533) -> ImageBuffer<Luma<u8>, Vec<u8>>
534where
535    Map: ColorMap<Color = Pix> + ?Sized,
536    Pix: Pixel<Subpixel = u8> + 'static,
537{
538    let mut indices = ImageBuffer::new(image.width(), image.height());
539    for (pixel, idx) in image.pixels().zip(indices.pixels_mut()) {
540        *idx = Luma([color_map.index_of(pixel) as u8]);
541    }
542    indices
543}
544
545#[cfg(test)]
546mod test {
547
548    use super::*;
549    use crate::GrayImage;
550
551    macro_rules! assert_pixels_eq {
552        ($actual:expr, $expected:expr) => {{
553            let actual_dim = $actual.dimensions();
554            let expected_dim = $expected.dimensions();
555
556            if actual_dim != expected_dim {
557                panic!(
558                    "dimensions do not match. \
559                     actual: {:?}, expected: {:?}",
560                    actual_dim, expected_dim
561                )
562            }
563
564            let diffs = pixel_diffs($actual, $expected);
565
566            if !diffs.is_empty() {
567                let mut err = "".to_string();
568
569                let diff_messages = diffs
570                    .iter()
571                    .take(5)
572                    .map(|d| format!("\nactual: {:?}, expected {:?} ", d.0, d.1))
573                    .collect::<Vec<_>>()
574                    .join("");
575
576                err.push_str(&diff_messages);
577                panic!("pixels do not match. {:?}", err)
578            }
579        }};
580    }
581
582    #[test]
583    fn test_dither() {
584        let mut image = ImageBuffer::from_raw(2, 2, vec![127, 127, 127, 127]).unwrap();
585        let cmap = BiLevel;
586        dither(&mut image, &cmap);
587        assert_eq!(&*image, &[0, 0xFF, 0xFF, 0]);
588        assert_eq!(index_colors(&image, &cmap).into_raw(), vec![0, 1, 1, 0]);
589    }
590
591    #[test]
592    fn test_grayscale() {
593        let image: GrayImage =
594            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
595
596        let expected: GrayImage =
597            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
598
599        assert_pixels_eq!(&grayscale(&image), &expected);
600    }
601
602    #[test]
603    fn test_invert() {
604        let mut image: GrayImage =
605            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
606
607        let expected: GrayImage =
608            ImageBuffer::from_raw(3, 2, vec![255u8, 254u8, 253u8, 245u8, 244u8, 243u8]).unwrap();
609
610        invert(&mut image);
611        assert_pixels_eq!(&image, &expected);
612    }
613    #[test]
614    fn test_brighten() {
615        let image: GrayImage =
616            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
617
618        let expected: GrayImage =
619            ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap();
620
621        assert_pixels_eq!(&brighten(&image, 10), &expected);
622    }
623
624    #[test]
625    fn test_brighten_place() {
626        let mut image: GrayImage =
627            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
628
629        let expected: GrayImage =
630            ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap();
631
632        brighten_in_place(&mut image, 10);
633        assert_pixels_eq!(&image, &expected);
634    }
635
636    #[allow(clippy::type_complexity)]
637    fn pixel_diffs<I, J, P>(left: &I, right: &J) -> Vec<((u32, u32, P), (u32, u32, P))>
638    where
639        I: GenericImage<Pixel = P>,
640        J: GenericImage<Pixel = P>,
641        P: Pixel + Eq,
642    {
643        left.pixels()
644            .zip(right.pixels())
645            .filter(|&(p, q)| p != q)
646            .collect::<Vec<_>>()
647    }
648}