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