1 //! Functions for altering and converting the color of pixelbufs
2 
3 use num_traits::{Bounded, Num, NumCast};
4 use std::f64::consts::PI;
5 
6 use crate::color::{Luma, Rgba};
7 use crate::image::{GenericImage, GenericImageView};
8 #[allow(deprecated)]
9 use crate::math::nq;
10 use crate::traits::{Pixel, Primitive};
11 use crate::utils::clamp;
12 use crate::ImageBuffer;
13 
14 type Subpixel<I> = <<I as GenericImageView>::Pixel as Pixel>::Subpixel;
15 
16 /// Convert the supplied image to grayscale
grayscale<I: GenericImageView>(image: &I) -> ImageBuffer<Luma<Subpixel<I>>, Vec<Subpixel<I>>> where Subpixel<I>: 'static, <Subpixel<I> as Num>::FromStrRadixErr: 'static,17 pub fn grayscale<I: GenericImageView>(image: &I) -> ImageBuffer<Luma<Subpixel<I>>, Vec<Subpixel<I>>>
18 where
19     Subpixel<I>: 'static,
20     <Subpixel<I> as Num>::FromStrRadixErr: 'static,
21 {
22     let (width, height) = image.dimensions();
23     let mut out = ImageBuffer::new(width, height);
24 
25     for y in 0..height {
26         for x in 0..width {
27             let p = image.get_pixel(x, y).to_luma();
28             out.put_pixel(x, y, p);
29         }
30     }
31 
32     out
33 }
34 
35 /// Invert each pixel within the supplied image.
36 /// This function operates in place.
invert<I: GenericImage>(image: &mut I)37 pub fn invert<I: GenericImage>(image: &mut I) {
38     let (width, height) = image.dimensions();
39 
40     for y in 0..height {
41         for x in 0..width {
42             let mut p = image.get_pixel(x, y);
43             p.invert();
44 
45             image.put_pixel(x, y, p);
46         }
47     }
48 }
49 
50 /// Adjust the contrast of the supplied image.
51 /// ```contrast``` is the amount to adjust the contrast by.
52 /// Negative values decrease the contrast and positive values increase the contrast.
53 ///
54 /// *[See also `contrast_in_place`.][contrast_in_place]*
contrast<I, P, S>(image: &I, contrast: f32) -> ImageBuffer<P, Vec<S>> where I: GenericImageView<Pixel = P>, P: Pixel<Subpixel = S> + 'static, S: Primitive + 'static,55 pub fn contrast<I, P, S>(image: &I, contrast: f32) -> ImageBuffer<P, Vec<S>>
56 where
57     I: GenericImageView<Pixel = P>,
58     P: Pixel<Subpixel = S> + 'static,
59     S: Primitive + 'static,
60 {
61     let (width, height) = image.dimensions();
62     let mut out = ImageBuffer::new(width, height);
63 
64     let max = S::max_value();
65     let max: f32 = NumCast::from(max).unwrap();
66 
67     let percent = ((100.0 + contrast) / 100.0).powi(2);
68 
69     for y in 0..height {
70         for x in 0..width {
71             let f = image.get_pixel(x, y).map(|b| {
72                 let c: f32 = NumCast::from(b).unwrap();
73 
74                 let d = ((c / max - 0.5) * percent + 0.5) * max;
75                 let e = clamp(d, 0.0, max);
76 
77                 NumCast::from(e).unwrap()
78             });
79 
80             out.put_pixel(x, y, f);
81         }
82     }
83 
84     out
85 }
86 
87 /// Adjust the contrast of the supplied image in place.
88 /// ```contrast``` is the amount to adjust the contrast by.
89 /// Negative values decrease the contrast and positive values increase the contrast.
90 ///
91 /// *[See also `contrast`.][contrast]*
contrast_in_place<I>(image: &mut I, contrast: f32) where I: GenericImage,92 pub fn contrast_in_place<I>(image: &mut I, contrast: f32)
93 where
94     I: GenericImage,
95 {
96     let (width, height) = image.dimensions();
97 
98     let max = <<I::Pixel as Pixel>::Subpixel as Bounded>::max_value();
99     let max: f32 = NumCast::from(max).unwrap();
100 
101     let percent = ((100.0 + contrast) / 100.0).powi(2);
102 
103     for y in 0..height {
104         for x in 0..width {
105             let f = image.get_pixel(x, y).map(|b| {
106                 let c: f32 = NumCast::from(b).unwrap();
107 
108                 let d = ((c / max - 0.5) * percent + 0.5) * max;
109                 let e = clamp(d, 0.0, max);
110 
111                 NumCast::from(e).unwrap()
112             });
113 
114             image.put_pixel(x, y, f);
115         }
116     }
117 }
118 
119 /// Brighten the supplied image.
120 /// ```value``` is the amount to brighten each pixel by.
121 /// Negative values decrease the brightness and positive values increase it.
122 ///
123 /// *[See also `brighten_in_place`.][brighten_in_place]*
brighten<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>> where I: GenericImageView<Pixel = P>, P: Pixel<Subpixel = S> + 'static, S: Primitive + 'static,124 pub fn brighten<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>>
125 where
126     I: GenericImageView<Pixel = P>,
127     P: Pixel<Subpixel = S> + 'static,
128     S: Primitive + 'static,
129 {
130     let (width, height) = image.dimensions();
131     let mut out = ImageBuffer::new(width, height);
132 
133     let max = S::max_value();
134     let max: i32 = NumCast::from(max).unwrap();
135 
136     for y in 0..height {
137         for x in 0..width {
138             let e = image.get_pixel(x, y).map_with_alpha(
139                 |b| {
140                     let c: i32 = NumCast::from(b).unwrap();
141                     let d = clamp(c + value, 0, max);
142 
143                     NumCast::from(d).unwrap()
144                 },
145                 |alpha| alpha,
146             );
147 
148             out.put_pixel(x, y, e);
149         }
150     }
151 
152     out
153 }
154 
155 /// Brighten the supplied image in place.
156 /// ```value``` is the amount to brighten each pixel by.
157 /// Negative values decrease the brightness and positive values increase it.
158 ///
159 /// *[See also `brighten`.][brighten]*
brighten_in_place<I>(image: &mut I, value: i32) where I: GenericImage,160 pub fn brighten_in_place<I>(image: &mut I, value: i32)
161 where
162     I: GenericImage,
163 {
164     let (width, height) = image.dimensions();
165 
166     let max = <<I::Pixel as Pixel>::Subpixel as Bounded>::max_value();
167     let max: i32 = NumCast::from(max).unwrap();
168 
169     for y in 0..height {
170         for x in 0..width {
171             let e = image.get_pixel(x, y).map_with_alpha(
172                 |b| {
173                     let c: i32 = NumCast::from(b).unwrap();
174                     let d = clamp(c + value, 0, max);
175 
176                     NumCast::from(d).unwrap()
177                 },
178                 |alpha| alpha,
179             );
180 
181             image.put_pixel(x, y, e);
182         }
183     }
184 }
185 
186 /// Hue rotate the supplied image.
187 /// `value` is the degrees to rotate each pixel by.
188 /// 0 and 360 do nothing, the rest rotates by the given degree value.
189 /// just like the css webkit filter hue-rotate(180)
190 ///
191 /// *[See also `huerotate_in_place`.][huerotate_in_place]*
huerotate<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>> where I: GenericImageView<Pixel = P>, P: Pixel<Subpixel = S> + 'static, S: Primitive + 'static,192 pub fn huerotate<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>>
193 where
194     I: GenericImageView<Pixel = P>,
195     P: Pixel<Subpixel = S> + 'static,
196     S: Primitive + 'static,
197 {
198     let (width, height) = image.dimensions();
199     let mut out = ImageBuffer::new(width, height);
200 
201     let angle: f64 = NumCast::from(value).unwrap();
202 
203     let cosv = (angle * PI / 180.0).cos();
204     let sinv = (angle * PI / 180.0).sin();
205     let matrix: [f64; 9] = [
206         // Reds
207         0.213 + cosv * 0.787 - sinv * 0.213,
208         0.715 - cosv * 0.715 - sinv * 0.715,
209         0.072 - cosv * 0.072 + sinv * 0.928,
210         // Greens
211         0.213 - cosv * 0.213 + sinv * 0.143,
212         0.715 + cosv * 0.285 + sinv * 0.140,
213         0.072 - cosv * 0.072 - sinv * 0.283,
214         // Blues
215         0.213 - cosv * 0.213 - sinv * 0.787,
216         0.715 - cosv * 0.715 + sinv * 0.715,
217         0.072 + cosv * 0.928 + sinv * 0.072,
218     ];
219     for (x, y, pixel) in out.enumerate_pixels_mut() {
220         let p = image.get_pixel(x, y);
221         let (k1, k2, k3, k4) = p.channels4();
222         let vec: (f64, f64, f64, f64) = (
223             NumCast::from(k1).unwrap(),
224             NumCast::from(k2).unwrap(),
225             NumCast::from(k3).unwrap(),
226             NumCast::from(k4).unwrap(),
227         );
228 
229         let r = vec.0;
230         let g = vec.1;
231         let b = vec.2;
232 
233         let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b;
234         let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b;
235         let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b;
236         let max = 255f64;
237         let outpixel = Pixel::from_channels(
238             NumCast::from(clamp(new_r, 0.0, max)).unwrap(),
239             NumCast::from(clamp(new_g, 0.0, max)).unwrap(),
240             NumCast::from(clamp(new_b, 0.0, max)).unwrap(),
241             NumCast::from(clamp(vec.3, 0.0, max)).unwrap(),
242         );
243         *pixel = outpixel;
244     }
245     out
246 }
247 
248 /// Hue rotate the supplied image in place.
249 /// `value` is the degrees to rotate each pixel by.
250 /// 0 and 360 do nothing, the rest rotates by the given degree value.
251 /// just like the css webkit filter hue-rotate(180)
252 ///
253 /// *[See also `huerotate`.][huerotate]*
huerotate_in_place<I>(image: &mut I, value: i32) where I: GenericImage,254 pub fn huerotate_in_place<I>(image: &mut I, value: i32)
255 where
256     I: GenericImage,
257 {
258     let (width, height) = image.dimensions();
259 
260     let angle: f64 = NumCast::from(value).unwrap();
261 
262     let cosv = (angle * PI / 180.0).cos();
263     let sinv = (angle * PI / 180.0).sin();
264     let matrix: [f64; 9] = [
265         // Reds
266         0.213 + cosv * 0.787 - sinv * 0.213,
267         0.715 - cosv * 0.715 - sinv * 0.715,
268         0.072 - cosv * 0.072 + sinv * 0.928,
269         // Greens
270         0.213 - cosv * 0.213 + sinv * 0.143,
271         0.715 + cosv * 0.285 + sinv * 0.140,
272         0.072 - cosv * 0.072 - sinv * 0.283,
273         // Blues
274         0.213 - cosv * 0.213 - sinv * 0.787,
275         0.715 - cosv * 0.715 + sinv * 0.715,
276         0.072 + cosv * 0.928 + sinv * 0.072,
277     ];
278     for y in 0..height {
279         for x in 0..width {
280             let pixel = image.get_pixel(x, y);
281             let (k1, k2, k3, k4) = pixel.channels4();
282             let vec: (f64, f64, f64, f64) = (
283                 NumCast::from(k1).unwrap(),
284                 NumCast::from(k2).unwrap(),
285                 NumCast::from(k3).unwrap(),
286                 NumCast::from(k4).unwrap(),
287             );
288 
289             let r = vec.0;
290             let g = vec.1;
291             let b = vec.2;
292 
293             let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b;
294             let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b;
295             let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b;
296             let max = 255f64;
297             let outpixel = Pixel::from_channels(
298                 NumCast::from(clamp(new_r, 0.0, max)).unwrap(),
299                 NumCast::from(clamp(new_g, 0.0, max)).unwrap(),
300                 NumCast::from(clamp(new_b, 0.0, max)).unwrap(),
301                 NumCast::from(clamp(vec.3, 0.0, max)).unwrap(),
302             );
303 
304             image.put_pixel(x, y, outpixel);
305         }
306     }
307 }
308 
309 /// A color map
310 pub trait ColorMap {
311     /// The color type on which the map operates on
312     type Color;
313     /// Returns the index of the closest match of `color`
314     /// in the color map.
index_of(&self, color: &Self::Color) -> usize315     fn index_of(&self, color: &Self::Color) -> usize;
316     /// Looks up color by index in the color map.  If `idx` is out of range for the color map, or
317     /// ColorMap doesn't implement `lookup` `None` is returned.
lookup(&self, index: usize) -> Option<Self::Color>318     fn lookup(&self, index: usize) -> Option<Self::Color> {
319         let _ = index;
320         None
321     }
322     /// Determine if this implementation of ColorMap overrides the default `lookup`.
has_lookup(&self) -> bool323     fn has_lookup(&self) -> bool {
324         false
325     }
326     /// Maps `color` to the closest color in the color map.
map_color(&self, color: &mut Self::Color)327     fn map_color(&self, color: &mut Self::Color);
328 }
329 
330 /// A bi-level color map
331 ///
332 /// # Examples
333 /// ```
334 /// use image::imageops::colorops::{index_colors, BiLevel, ColorMap};
335 /// use image::{ImageBuffer, Luma};
336 ///
337 /// let (w, h) = (16, 16);
338 /// // Create an image with a smooth horizontal gradient from black (0) to white (255).
339 /// let gray = ImageBuffer::from_fn(w, h, |x, y| -> Luma<u8> { [(255 * x / w) as u8].into() });
340 /// // Mapping the gray image through the `BiLevel` filter should map gray pixels less than half
341 /// // intensity (127) to black (0), and anything greater to white (255).
342 /// let cmap = BiLevel;
343 /// let palletized = index_colors(&gray, &cmap);
344 /// let mapped = ImageBuffer::from_fn(w, h, |x, y| {
345 ///     let p = palletized.get_pixel(x, y);
346 ///     cmap.lookup(p.0[0] as usize)
347 ///         .expect("indexed color out-of-range")
348 /// });
349 /// // Create an black and white image of expected output.
350 /// let bw = ImageBuffer::from_fn(w, h, |x, y| -> Luma<u8> {
351 ///     if x <= (w / 2) {
352 ///         [0].into()
353 ///     } else {
354 ///         [255].into()
355 ///     }
356 /// });
357 /// assert_eq!(mapped, bw);
358 /// ```
359 #[derive(Clone, Copy)]
360 pub struct BiLevel;
361 
362 impl ColorMap for BiLevel {
363     type Color = Luma<u8>;
364 
365     #[inline(always)]
index_of(&self, color: &Luma<u8>) -> usize366     fn index_of(&self, color: &Luma<u8>) -> usize {
367         let luma = color.0;
368         if luma[0] > 127 {
369             1
370         } else {
371             0
372         }
373     }
374 
375     #[inline(always)]
lookup(&self, idx: usize) -> Option<Self::Color>376     fn lookup(&self, idx: usize) -> Option<Self::Color> {
377         match idx {
378             0 => Some([0].into()),
379             1 => Some([255].into()),
380             _ => None,
381         }
382     }
383 
384     /// Indicate NeuQuant implements `lookup`.
has_lookup(&self) -> bool385     fn has_lookup(&self) -> bool {
386         true
387     }
388 
389     #[inline(always)]
map_color(&self, color: &mut Luma<u8>)390     fn map_color(&self, color: &mut Luma<u8>) {
391         let new_color = 0xFF * self.index_of(color) as u8;
392         let luma = &mut color.0;
393         luma[0] = new_color;
394     }
395 }
396 
397 #[allow(deprecated)]
398 impl ColorMap for nq::NeuQuant {
399     type Color = Rgba<u8>;
400 
401     #[inline(always)]
index_of(&self, color: &Rgba<u8>) -> usize402     fn index_of(&self, color: &Rgba<u8>) -> usize {
403         self.index_of(color.channels())
404     }
405 
406     #[inline(always)]
lookup(&self, idx: usize) -> Option<Self::Color>407     fn lookup(&self, idx: usize) -> Option<Self::Color> {
408         self.lookup(idx).map(|p| p.into())
409     }
410 
411     /// Indicate NeuQuant implements `lookup`.
has_lookup(&self) -> bool412     fn has_lookup(&self) -> bool {
413         true
414     }
415 
416     #[inline(always)]
map_color(&self, color: &mut Rgba<u8>)417     fn map_color(&self, color: &mut Rgba<u8>) {
418         self.map_pixel(color.channels_mut())
419     }
420 }
421 
422 impl ColorMap for color_quant::NeuQuant {
423     type Color = Rgba<u8>;
424 
425     #[inline(always)]
index_of(&self, color: &Rgba<u8>) -> usize426     fn index_of(&self, color: &Rgba<u8>) -> usize {
427         self.index_of(color.channels())
428     }
429 
430     #[inline(always)]
lookup(&self, idx: usize) -> Option<Self::Color>431     fn lookup(&self, idx: usize) -> Option<Self::Color> {
432         self.lookup(idx).map(|p| p.into())
433     }
434 
435     /// Indicate NeuQuant implements `lookup`.
has_lookup(&self) -> bool436     fn has_lookup(&self) -> bool {
437         true
438     }
439 
440     #[inline(always)]
map_color(&self, color: &mut Rgba<u8>)441     fn map_color(&self, color: &mut Rgba<u8>) {
442         self.map_pixel(color.channels_mut())
443     }
444 }
445 
446 /// Floyd-Steinberg error diffusion
diffuse_err<P: Pixel<Subpixel = u8>>(pixel: &mut P, error: [i16; 3], factor: i16)447 fn diffuse_err<P: Pixel<Subpixel = u8>>(pixel: &mut P, error: [i16; 3], factor: i16) {
448     for (e, c) in error.iter().zip(pixel.channels_mut().iter_mut()) {
449         *c = match <i16 as From<_>>::from(*c) + e * factor / 16 {
450             val if val < 0 => 0,
451             val if val > 0xFF => 0xFF,
452             val => val as u8,
453         }
454     }
455 }
456 
457 macro_rules! do_dithering(
458     ($map:expr, $image:expr, $err:expr, $x:expr, $y:expr) => (
459         {
460             let old_pixel = $image[($x, $y)];
461             let new_pixel = $image.get_pixel_mut($x, $y);
462             $map.map_color(new_pixel);
463             for ((e, &old), &new) in $err.iter_mut()
464                                         .zip(old_pixel.channels().iter())
465                                         .zip(new_pixel.channels().iter())
466             {
467                 *e = <i16 as From<_>>::from(old) - <i16 as From<_>>::from(new)
468             }
469         }
470     )
471 );
472 
473 /// Reduces the colors of the image using the supplied `color_map` while applying
474 /// Floyd-Steinberg dithering to improve the visual conception
dither<Pix, Map>(image: &mut ImageBuffer<Pix, Vec<u8>>, color_map: &Map) where Map: ColorMap<Color = Pix> + ?Sized, Pix: Pixel<Subpixel = u8> + 'static,475 pub fn dither<Pix, Map>(image: &mut ImageBuffer<Pix, Vec<u8>>, color_map: &Map)
476 where
477     Map: ColorMap<Color = Pix> + ?Sized,
478     Pix: Pixel<Subpixel = u8> + 'static,
479 {
480     let (width, height) = image.dimensions();
481     let mut err: [i16; 3] = [0; 3];
482     for y in 0..height - 1 {
483         let x = 0;
484         do_dithering!(color_map, image, err, x, y);
485         diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
486         diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
487         diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
488         for x in 1..width - 1 {
489             do_dithering!(color_map, image, err, x, y);
490             diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
491             diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
492             diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
493             diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
494         }
495         let x = width - 1;
496         do_dithering!(color_map, image, err, x, y);
497         diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
498         diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
499     }
500     let y = height - 1;
501     let x = 0;
502     do_dithering!(color_map, image, err, x, y);
503     diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
504     for x in 1..width - 1 {
505         do_dithering!(color_map, image, err, x, y);
506         diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
507     }
508     let x = width - 1;
509     do_dithering!(color_map, image, err, x, y);
510 }
511 
512 /// Reduces the colors using the supplied `color_map` and returns an image of the indices
index_colors<Pix, Map>( image: &ImageBuffer<Pix, Vec<u8>>, color_map: &Map, ) -> ImageBuffer<Luma<u8>, Vec<u8>> where Map: ColorMap<Color = Pix> + ?Sized, Pix: Pixel<Subpixel = u8> + 'static,513 pub fn index_colors<Pix, Map>(
514     image: &ImageBuffer<Pix, Vec<u8>>,
515     color_map: &Map,
516 ) -> ImageBuffer<Luma<u8>, Vec<u8>>
517 where
518     Map: ColorMap<Color = Pix> + ?Sized,
519     Pix: Pixel<Subpixel = u8> + 'static,
520 {
521     let mut indices = ImageBuffer::new(image.width(), image.height());
522     for (pixel, idx) in image.pixels().zip(indices.pixels_mut()) {
523         *idx = Luma([color_map.index_of(pixel) as u8])
524     }
525     indices
526 }
527 
528 #[cfg(test)]
529 mod test {
530 
531     use super::*;
532     use crate::ImageBuffer;
533 
534     #[test]
test_dither()535     fn test_dither() {
536         let mut image = ImageBuffer::from_raw(2, 2, vec![127, 127, 127, 127]).unwrap();
537         let cmap = BiLevel;
538         dither(&mut image, &cmap);
539         assert_eq!(&*image, &[0, 0xFF, 0xFF, 0]);
540         assert_eq!(index_colors(&image, &cmap).into_raw(), vec![0, 1, 1, 0])
541     }
542 }
543