1use num_traits::NumCast;
4
5use crate::color::{FromColor, IntoColor, Luma, LumaA};
6use crate::metadata::{CicpColorPrimaries, CicpTransferCharacteristics};
7use crate::traits::{Pixel, Primitive};
8use crate::utils::clamp;
9use crate::{GenericImage, GenericImageView, 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 out.copy_color_space_from(&image.buffer_with_dimensions(0, 0));
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 out.copy_color_space_from(&image.buffer_with_dimensions(0, 0));
58
59 for (x, y, pixel) in image.pixels() {
60 let grayscale = pixel.to_luma_alpha();
61 let new_pixel = grayscale.into_color(); out.put_pixel(x, y, new_pixel);
64 }
65
66 out
67}
68
69pub fn invert<I: GenericImage>(image: &mut I) {
72 let (width, height) = image.dimensions();
74
75 for y in 0..height {
76 for x in 0..width {
77 let mut p = image.get_pixel(x, y);
78 p.invert();
79
80 image.put_pixel(x, y, p);
81 }
82 }
83}
84
85pub fn contrast<I, P, S>(image: &I, contrast: f32) -> ImageBuffer<P, Vec<S>>
91where
92 I: GenericImageView<Pixel = P>,
93 P: Pixel<Subpixel = S> + 'static,
94 S: Primitive + 'static,
95{
96 let mut out = image.buffer_like();
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 mut out = image.buffer_like();
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 mut out = image.buffer_like();
228
229 let angle: f64 = NumCast::from(value).unwrap();
230
231 let cosv = angle.to_radians().cos();
232 let sinv = angle.to_radians().sin();
233 let matrix: [f64; 9] = [
234 0.213 + cosv * 0.787 - sinv * 0.213,
236 0.715 - cosv * 0.715 - sinv * 0.715,
237 0.072 - cosv * 0.072 + sinv * 0.928,
238 0.213 - cosv * 0.213 + sinv * 0.143,
240 0.715 + cosv * 0.285 + sinv * 0.140,
241 0.072 - cosv * 0.072 - sinv * 0.283,
242 0.213 - cosv * 0.213 - sinv * 0.787,
244 0.715 - cosv * 0.715 + sinv * 0.715,
245 0.072 + cosv * 0.928 + sinv * 0.072,
246 ];
247 for (x, y, pixel) in out.enumerate_pixels_mut() {
248 let p = image.get_pixel(x, y);
249
250 #[allow(deprecated)]
251 let (k1, k2, k3, k4) = p.channels4();
252 let vec: (f64, f64, f64, f64) = (
253 NumCast::from(k1).unwrap(),
254 NumCast::from(k2).unwrap(),
255 NumCast::from(k3).unwrap(),
256 NumCast::from(k4).unwrap(),
257 );
258
259 let r = vec.0;
260 let g = vec.1;
261 let b = vec.2;
262
263 let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b;
264 let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b;
265 let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b;
266 let max = 255f64;
267
268 #[allow(deprecated)]
269 let outpixel = Pixel::from_channels(
270 NumCast::from(clamp(new_r, 0.0, max)).unwrap(),
271 NumCast::from(clamp(new_g, 0.0, max)).unwrap(),
272 NumCast::from(clamp(new_b, 0.0, max)).unwrap(),
273 NumCast::from(clamp(vec.3, 0.0, max)).unwrap(),
274 );
275 *pixel = outpixel;
276 }
277 out
278}
279
280pub fn huerotate_in_place<I>(image: &mut I, value: i32)
288where
289 I: GenericImage,
290{
291 let (width, height) = image.dimensions();
292
293 let angle: f64 = NumCast::from(value).unwrap();
294
295 let cosv = angle.to_radians().cos();
296 let sinv = angle.to_radians().sin();
297 let matrix: [f64; 9] = [
298 0.213 + cosv * 0.787 - sinv * 0.213,
300 0.715 - cosv * 0.715 - sinv * 0.715,
301 0.072 - cosv * 0.072 + sinv * 0.928,
302 0.213 - cosv * 0.213 + sinv * 0.143,
304 0.715 + cosv * 0.285 + sinv * 0.140,
305 0.072 - cosv * 0.072 - sinv * 0.283,
306 0.213 - cosv * 0.213 - sinv * 0.787,
308 0.715 - cosv * 0.715 + sinv * 0.715,
309 0.072 + cosv * 0.928 + sinv * 0.072,
310 ];
311
312 for y in 0..height {
314 for x in 0..width {
315 let pixel = image.get_pixel(x, y);
316
317 #[allow(deprecated)]
318 let (k1, k2, k3, k4) = pixel.channels4();
319
320 let vec: (f64, f64, f64, f64) = (
321 NumCast::from(k1).unwrap(),
322 NumCast::from(k2).unwrap(),
323 NumCast::from(k3).unwrap(),
324 NumCast::from(k4).unwrap(),
325 );
326
327 let r = vec.0;
328 let g = vec.1;
329 let b = vec.2;
330
331 let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b;
332 let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b;
333 let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b;
334 let max = 255f64;
335
336 #[allow(deprecated)]
337 let outpixel = Pixel::from_channels(
338 NumCast::from(clamp(new_r, 0.0, max)).unwrap(),
339 NumCast::from(clamp(new_g, 0.0, max)).unwrap(),
340 NumCast::from(clamp(new_b, 0.0, max)).unwrap(),
341 NumCast::from(clamp(vec.3, 0.0, max)).unwrap(),
342 );
343
344 image.put_pixel(x, y, outpixel);
345 }
346 }
347}
348
349pub trait ColorMap {
351 type Color;
353 fn index_of(&self, color: &Self::Color) -> usize;
356 fn lookup(&self, index: usize) -> Option<Self::Color> {
359 let _ = index;
360 None
361 }
362 fn has_lookup(&self) -> bool {
364 false
365 }
366 fn map_color(&self, color: &mut Self::Color);
368}
369
370#[derive(Clone, Copy)]
400pub struct BiLevel;
401
402impl ColorMap for BiLevel {
403 type Color = Luma<u8>;
404
405 #[inline(always)]
406 fn index_of(&self, color: &Luma<u8>) -> usize {
407 let luma = color.0;
408 if luma[0] > 127 {
409 1
410 } else {
411 0
412 }
413 }
414
415 #[inline(always)]
416 fn lookup(&self, idx: usize) -> Option<Self::Color> {
417 match idx {
418 0 => Some([0].into()),
419 1 => Some([255].into()),
420 _ => None,
421 }
422 }
423
424 fn has_lookup(&self) -> bool {
426 true
427 }
428
429 #[inline(always)]
430 fn map_color(&self, color: &mut Luma<u8>) {
431 let new_color = 0xFF * self.index_of(color) as u8;
432 let luma = &mut color.0;
433 luma[0] = new_color;
434 }
435}
436
437#[cfg(feature = "color_quant")]
438impl ColorMap for color_quant::NeuQuant {
439 type Color = crate::color::Rgba<u8>;
440
441 #[inline(always)]
442 fn index_of(&self, color: &Self::Color) -> usize {
443 self.index_of(color.channels())
444 }
445
446 #[inline(always)]
447 fn lookup(&self, idx: usize) -> Option<Self::Color> {
448 self.lookup(idx).map(|p| p.into())
449 }
450
451 fn has_lookup(&self) -> bool {
453 true
454 }
455
456 #[inline(always)]
457 fn map_color(&self, color: &mut Self::Color) {
458 self.map_pixel(color.channels_mut());
459 }
460}
461
462fn diffuse_err<P: Pixel<Subpixel = u8>>(pixel: &mut P, error: [i16; 3], factor: i16) {
464 for (e, c) in error.iter().zip(pixel.channels_mut().iter_mut()) {
465 *c = match <i16 as From<_>>::from(*c) + e * factor / 16 {
466 val if val < 0 => 0,
467 val if val > 0xFF => 0xFF,
468 val => val as u8,
469 }
470 }
471}
472
473macro_rules! do_dithering(
474 ($map:expr, $image:expr, $err:expr, $x:expr, $y:expr) => (
475 {
476 let old_pixel = $image[($x, $y)];
477 let new_pixel = $image.get_pixel_mut($x, $y);
478 $map.map_color(new_pixel);
479 for ((e, &old), &new) in $err.iter_mut()
480 .zip(old_pixel.channels().iter())
481 .zip(new_pixel.channels().iter())
482 {
483 *e = <i16 as From<_>>::from(old) - <i16 as From<_>>::from(new)
484 }
485 }
486 )
487);
488
489pub fn dither<Pix, Map>(image: &mut ImageBuffer<Pix, Vec<u8>>, color_map: &Map)
492where
493 Map: ColorMap<Color = Pix> + ?Sized,
494 Pix: Pixel<Subpixel = u8> + 'static,
495{
496 let (width, height) = image.dimensions();
497 let mut err: [i16; 3] = [0; 3];
498 for y in 0..height - 1 {
499 let x = 0;
500 do_dithering!(color_map, image, err, x, y);
501 diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
502 diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
503 diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
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 diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
508 diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
509 diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1);
510 }
511 let x = width - 1;
512 do_dithering!(color_map, image, err, x, y);
513 diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3);
514 diffuse_err(image.get_pixel_mut(x, y + 1), err, 5);
515 }
516 let y = height - 1;
517 let x = 0;
518 do_dithering!(color_map, image, err, x, y);
519 diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
520 for x in 1..width - 1 {
521 do_dithering!(color_map, image, err, x, y);
522 diffuse_err(image.get_pixel_mut(x + 1, y), err, 7);
523 }
524 let x = width - 1;
525 do_dithering!(color_map, image, err, x, y);
526}
527
528pub fn index_colors<Pix, Map>(
530 image: &ImageBuffer<Pix, Vec<u8>>,
531 color_map: &Map,
532) -> ImageBuffer<Luma<u8>, Vec<u8>>
533where
534 Map: ColorMap<Color = Pix> + ?Sized,
535 Pix: Pixel<Subpixel = u8> + 'static,
536{
537 let mut indices = ImageBuffer::new(image.width(), image.height());
539 indices.set_rgb_primaries(CicpColorPrimaries::Unspecified);
540 indices.set_transfer_function(CicpTransferCharacteristics::Unspecified);
541 for (pixel, idx) in image.pixels().zip(indices.pixels_mut()) {
542 *idx = Luma([color_map.index_of(pixel) as u8]);
543 }
544 indices
545}
546
547#[cfg(test)]
548mod test {
549
550 use super::*;
551 use crate::GrayImage;
552
553 macro_rules! assert_pixels_eq {
554 ($actual:expr, $expected:expr) => {{
555 let actual_dim = $actual.dimensions();
556 let expected_dim = $expected.dimensions();
557
558 if actual_dim != expected_dim {
559 panic!(
560 "dimensions do not match. \
561 actual: {:?}, expected: {:?}",
562 actual_dim, expected_dim
563 )
564 }
565
566 let diffs = pixel_diffs($actual, $expected);
567
568 if !diffs.is_empty() {
569 let mut err = "".to_string();
570
571 let diff_messages = diffs
572 .iter()
573 .take(5)
574 .map(|d| format!("\nactual: {:?}, expected {:?} ", d.0, d.1))
575 .collect::<Vec<_>>()
576 .join("");
577
578 err.push_str(&diff_messages);
579 panic!("pixels do not match. {:?}", err)
580 }
581 }};
582 }
583
584 #[test]
585 fn test_dither() {
586 let mut image = ImageBuffer::from_raw(2, 2, vec![127, 127, 127, 127]).unwrap();
587 let cmap = BiLevel;
588 dither(&mut image, &cmap);
589 assert_eq!(&*image, &[0, 0xFF, 0xFF, 0]);
590 assert_eq!(index_colors(&image, &cmap).into_raw(), vec![0, 1, 1, 0]);
591 }
592
593 #[test]
594 fn test_grayscale() {
595 let image: GrayImage =
596 ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
597
598 let expected: GrayImage =
599 ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
600
601 assert_pixels_eq!(&grayscale(&image), &expected);
602 }
603
604 #[test]
605 fn test_invert() {
606 let mut image: GrayImage =
607 ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
608
609 let expected: GrayImage =
610 ImageBuffer::from_raw(3, 2, vec![255u8, 254u8, 253u8, 245u8, 244u8, 243u8]).unwrap();
611
612 invert(&mut image);
613 assert_pixels_eq!(&image, &expected);
614 }
615 #[test]
616 fn test_brighten() {
617 let image: GrayImage =
618 ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
619
620 let expected: GrayImage =
621 ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap();
622
623 assert_pixels_eq!(&brighten(&image, 10), &expected);
624 }
625
626 #[test]
627 fn test_brighten_place() {
628 let mut image: GrayImage =
629 ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
630
631 let expected: GrayImage =
632 ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap();
633
634 brighten_in_place(&mut image, 10);
635 assert_pixels_eq!(&image, &expected);
636 }
637
638 #[allow(clippy::type_complexity)]
639 fn pixel_diffs<I, J, P>(left: &I, right: &J) -> Vec<((u32, u32, P), (u32, u32, P))>
640 where
641 I: GenericImage<Pixel = P>,
642 J: GenericImage<Pixel = P>,
643 P: Pixel + Eq,
644 {
645 left.pixels()
646 .zip(right.pixels())
647 .filter(|&(p, q)| p != q)
648 .collect::<Vec<_>>()
649 }
650}