1 //! Functions for altering and converting the color of pixelbufs
2 
3 use buffer::{ImageBuffer, Pixel};
4 use color::{Luma, Rgba};
5 use image::{GenericImage, GenericImageView};
6 use math::nq;
7 use math::utils::clamp;
8 use num_traits::{Num, NumCast};
9 use std::f64::consts::PI;
10 use traits::Primitive;
11 
12 type Subpixel<I> = <<I as GenericImageView>::Pixel as Pixel>::Subpixel;
13 
14 /// 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,15 pub fn grayscale<I: GenericImageView>(
16     image: &I,
17 ) -> 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.
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,53 pub fn contrast<I, P, S>(image: &I, contrast: f32) -> ImageBuffer<P, Vec<S>>
54 where
55     I: GenericImageView<Pixel = P>,
56     P: Pixel<Subpixel = S> + 'static,
57     S: Primitive + 'static,
58 {
59     let (width, height) = image.dimensions();
60     let mut out = ImageBuffer::new(width, height);
61 
62     let max = S::max_value();
63     let max: f32 = NumCast::from(max).unwrap();
64 
65     let percent = ((100.0 + contrast) / 100.0).powi(2);
66 
67     for y in 0..height {
68         for x in 0..width {
69             let f = image.get_pixel(x, y).map(|b| {
70                 let c: f32 = NumCast::from(b).unwrap();
71 
72                 let d = ((c / max - 0.5) * percent + 0.5) * max;
73                 let e = clamp(d, 0.0, max);
74 
75                 NumCast::from(e).unwrap()
76             });
77 
78             out.put_pixel(x, y, f);
79         }
80     }
81 
82     out
83 }
84 
85 /// Brighten the supplied image.
86 /// ```value``` is the amount to brighten each pixel by.
87 /// Negative values decrease the brightness and positive values increase it.
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,88 pub fn brighten<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>>
89 where
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::max_value();
98     let max: i32 = NumCast::from(max).unwrap();
99 
100     for y in 0..height {
101         for x in 0..width {
102             let e = image.get_pixel(x, y).map_with_alpha(
103                 |b| {
104                     let c: i32 = NumCast::from(b).unwrap();
105                     let d = clamp(c + value, 0, max);
106 
107                     NumCast::from(d).unwrap()
108                 },
109                 |alpha| alpha,
110             );
111 
112             out.put_pixel(x, y, e);
113         }
114     }
115 
116     out
117 }
118 
119 /// Hue rotate the supplied image.
120 /// `value` is the degrees to rotate each pixel by.
121 /// 0 and 360 do nothing, the rest rotates by the given degree value.
122 /// just like the css webkit filter hue-rotate(180)
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,123 pub fn huerotate<I, P, S>(image: &I, value: i32) -> ImageBuffer<P, Vec<S>>
124 where
125     I: GenericImageView<Pixel = P>,
126     P: Pixel<Subpixel = S> + 'static,
127     S: Primitive + 'static,
128 {
129     let (width, height) = image.dimensions();
130     let mut out = ImageBuffer::new(width, height);
131 
132     let angle: f64 = NumCast::from(value).unwrap();
133 
134     let cosv = (angle * PI / 180.0).cos();
135     let sinv = (angle * PI / 180.0).sin();
136     let matrix: [f64; 9] = [
137         // Reds
138         0.213 + cosv * 0.787 - sinv * 0.213,
139         0.715 - cosv * 0.715 - sinv * 0.715,
140         0.072 - cosv * 0.072 + sinv * 0.928,
141         // Greens
142         0.213 - cosv * 0.213 + sinv * 0.143,
143         0.715 + cosv * 0.285 + sinv * 0.140,
144         0.072 - cosv * 0.072 - sinv * 0.283,
145         // Blues
146         0.213 - cosv * 0.213 - sinv * 0.787,
147         0.715 - cosv * 0.715 + sinv * 0.715,
148         0.072 + cosv * 0.928 + sinv * 0.072,
149     ];
150     for (x, y, pixel) in out.enumerate_pixels_mut() {
151         let p = image.get_pixel(x, y);
152         let (k1, k2, k3, k4) = p.channels4();
153         let vec: (f64, f64, f64, f64) = (
154             NumCast::from(k1).unwrap(),
155             NumCast::from(k2).unwrap(),
156             NumCast::from(k3).unwrap(),
157             NumCast::from(k4).unwrap(),
158         );
159 
160         let r = vec.0;
161         let g = vec.1;
162         let b = vec.2;
163 
164         let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b;
165         let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b;
166         let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b;
167         let max = 255f64;
168         let outpixel = Pixel::from_channels(
169             NumCast::from(clamp(new_r, 0.0, max)).unwrap(),
170             NumCast::from(clamp(new_g, 0.0, max)).unwrap(),
171             NumCast::from(clamp(new_b, 0.0, max)).unwrap(),
172             NumCast::from(clamp(vec.3, 0.0, max)).unwrap(),
173         );
174         *pixel = outpixel;
175     }
176     out
177 }
178 
179 /// A color map
180 pub trait ColorMap {
181     /// The color type on which the map operates on
182     type Color;
183     /// Returns the index of the closed match of `color`
184     /// in the color map.
index_of(&self, color: &Self::Color) -> usize185     fn index_of(&self, color: &Self::Color) -> usize;
186     /// Maps `color` to the closest color in the color map.
map_color(&self, color: &mut Self::Color)187     fn map_color(&self, color: &mut Self::Color);
188 }
189 
190 /// A bi-level color map
191 #[derive(Clone, Copy)]
192 pub struct BiLevel;
193 
194 impl ColorMap for BiLevel {
195     type Color = Luma<u8>;
196 
197     #[inline(always)]
index_of(&self, color: &Luma<u8>) -> usize198     fn index_of(&self, color: &Luma<u8>) -> usize {
199         let luma = color.data;
200         if luma[0] > 127 {
201             1
202         } else {
203             0
204         }
205     }
206 
207     #[inline(always)]
map_color(&self, color: &mut Luma<u8>)208     fn map_color(&self, color: &mut Luma<u8>) {
209         let new_color = 0xFF * self.index_of(color) as u8;
210         let luma = &mut color.data;
211         luma[0] = new_color;
212     }
213 }
214 
215 impl ColorMap for nq::NeuQuant {
216     type Color = Rgba<u8>;
217 
218     #[inline(always)]
index_of(&self, color: &Rgba<u8>) -> usize219     fn index_of(&self, color: &Rgba<u8>) -> usize {
220         self.index_of(color.channels())
221     }
222 
223     #[inline(always)]
map_color(&self, color: &mut Rgba<u8>)224     fn map_color(&self, color: &mut Rgba<u8>) {
225         self.map_pixel(color.channels_mut())
226     }
227 }
228 
229 /// Floyd-Steinberg error diffusion
diffuse_err<P: Pixel<Subpixel = u8>>(pixel: &mut P, error: [i16; 3], factor: i16)230 fn diffuse_err<P: Pixel<Subpixel = u8>>(pixel: &mut P, error: [i16; 3], factor: i16) {
231     for (e, c) in error.iter().zip(pixel.channels_mut().iter_mut()) {
232         *c = match <i16 as From<_>>::from(*c) + e * factor / 16 {
233             val if val < 0 => 0,
234             val if val > 0xFF => 0xFF,
235             val => val as u8,
236         }
237     }
238 }
239 
240 macro_rules! do_dithering(
241     ($map:expr, $image:expr, $err:expr, $x:expr, $y:expr) => (
242         {
243             let old_pixel = $image[($x, $y)];
244             let new_pixel = $image.get_pixel_mut($x, $y);
245             $map.map_color(new_pixel);
246             for ((e, &old), &new) in $err.iter_mut()
247                                         .zip(old_pixel.channels().iter())
248                                         .zip(new_pixel.channels().iter())
249             {
250                 *e = <i16 as From<_>>::from(old) - <i16 as From<_>>::from(new)
251             }
252         }
253     )
254 );
255 
256 /// Reduces the colors of the image using the supplied `color_map` while applying
257 /// 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>, Pix: Pixel<Subpixel = u8> + 'static,258 pub fn dither<Pix, Map>(image: &mut ImageBuffer<Pix, Vec<u8>>, color_map: &Map)
259 where
260     Map: ColorMap<Color = Pix>,
261     Pix: Pixel<Subpixel = u8> + 'static,
262 {
263     let (width, height) = image.dimensions();
264     let mut err: [i16; 3] = [0; 3];
265     for y in 0..height - 1 {
266         let x = 0;
267         do_dithering!(color_map, image, err, x, y);
268         diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
269         diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
270         diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
271         for x in 1..width - 1 {
272             do_dithering!(color_map, image, err, x, y);
273             diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
274             diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
275             diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
276             diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
277         }
278         let x = width - 1;
279         do_dithering!(color_map, image, err, x, y);
280         diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
281         diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
282     }
283     let y = height - 1;
284     let x = 0;
285     do_dithering!(color_map, image, err, x, y);
286     diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
287     for x in 1..width - 1 {
288         do_dithering!(color_map, image, err, x, y);
289         diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
290     }
291     let x = width - 1;
292     do_dithering!(color_map, image, err, x, y);
293 }
294 
295 /// 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>, Pix: Pixel<Subpixel = u8> + 'static,296 pub fn index_colors<Pix, Map>(
297     image: &ImageBuffer<Pix, Vec<u8>>,
298     color_map: &Map,
299 ) -> ImageBuffer<Luma<u8>, Vec<u8>>
300 where
301     Map: ColorMap<Color = Pix>,
302     Pix: Pixel<Subpixel = u8> + 'static,
303 {
304     let mut indices = ImageBuffer::new(image.width(), image.height());
305     for (pixel, idx) in image.pixels().zip(indices.pixels_mut()) {
306         *idx = Luma([color_map.index_of(pixel) as u8])
307     }
308     indices
309 }
310 
311 #[cfg(test)]
312 mod test {
313 
314     use super::*;
315     use ImageBuffer;
316 
317     #[test]
test_dither()318     fn test_dither() {
319         let mut image = ImageBuffer::from_raw(2, 2, vec![127, 127, 127, 127]).unwrap();
320         let cmap = BiLevel;
321         dither(&mut image, &cmap);
322         assert_eq!(&*image, &[0, 0xFF, 0xFF, 0]);
323         assert_eq!(index_colors(&image, &cmap).into_raw(), vec![0, 1, 1, 0])
324     }
325 }
326