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