phomo/metrics.rs
1extern crate image;
2use image::RgbImage;
3
4pub(crate) type MetricFn = fn(&RgbImage, &RgbImage) -> i64;
5
6/// L1 norm, the sum of the absolute differences of the pixels.
7///
8/// # Examples
9///
10/// ```rust
11/// use phomo::metrics::norm_l1;
12/// use image;
13///
14/// let img1 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([0, 0, 0]));
15/// let img2 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([255, 255, 255]));
16/// let norm = norm_l1(&img1, &img2);
17/// assert_eq!(norm, 255 * 3 * 2 * 2);
18/// ```
19pub fn norm_l1(img1: &RgbImage, img2: &RgbImage) -> i64 {
20 img1.pixels().zip(img2.pixels()).fold(0, |sum, (p1, p2)| {
21 sum + (p1[0].abs_diff(p2[0]) as i64)
22 + (p1[1].abs_diff(p2[1]) as i64)
23 + (p1[2].abs_diff(p2[2]) as i64)
24 })
25}
26
27/// L2 norm, the euclidean distance of the pixels.
28///
29/// Punishes mismatched image regions more than the L1 norm.
30/// Leading to less contrasty mosaics.
31///
32/// # Examples
33///
34/// ```rust
35/// use phomo::metrics::norm_l2;
36/// use image;
37///
38/// let img1 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([0, 0, 0]));
39/// let img2 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([255, 255, 255]));
40/// let norm = norm_l2(&img1, &img2);
41/// assert_eq!(norm, (255_i64.pow(2) * 3 * 2 * 2).isqrt());
42/// ```
43pub fn norm_l2(img1: &RgbImage, img2: &RgbImage) -> i64 {
44 img1.pixels()
45 .zip(img2.pixels())
46 .fold(0, |sum, (p1, p2)| {
47 sum + (p1[0].abs_diff(p2[0]) as i64).pow(2)
48 + (p1[1].abs_diff(p2[1]) as i64).pow(2)
49 + (p1[2].abs_diff(p2[2]) as i64).pow(2)
50 })
51 .isqrt()
52}
53
54#[inline]
55fn luminance(pixel: &[u8; 3]) -> i64 {
56 (0.299 * pixel[0] as f64 + 0.587 * pixel[1] as f64 + 0.114 * pixel[2] as f64) as i64
57}
58
59/// L1 norm of the luminance of the pixels.
60///
61/// # Examples
62///
63/// ```rust
64/// use phomo::metrics::luminance_l1;
65/// use image;
66///
67/// let img1 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([0, 0, 0]));
68/// let img2 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([255, 255, 255]));
69/// let norm = luminance_l1(&img1, &img2);
70/// assert_eq!(norm, 255 * 2 * 2);
71/// ```
72pub fn luminance_l1(img1: &RgbImage, img2: &RgbImage) -> i64 {
73 img1.pixels().zip(img2.pixels()).fold(0, |sum, (p1, p2)| {
74 let lum1 = luminance(&p1.0);
75 let lum2 = luminance(&p2.0);
76 sum + (lum1 - lum2).abs()
77 })
78}
79
80/// L2 norm of the luminance of the pixels.
81///
82/// # Examples
83///
84/// ```rust
85/// use phomo::metrics::luminance_l2;
86/// use image;
87///
88/// let img1 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([0, 0, 0]));
89/// let img2 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([255, 255, 255]));
90/// let norm = luminance_l2(&img1, &img2);
91/// assert_eq!(norm, (255_i64.pow(2) * 2 * 2).isqrt());
92/// ```
93pub fn luminance_l2(img1: &RgbImage, img2: &RgbImage) -> i64 {
94 img1.pixels()
95 .zip(img2.pixels())
96 .fold(0, |sum, (p1, p2)| {
97 let lum1 = luminance(&p1.0);
98 let lum2 = luminance(&p2.0);
99 sum + (lum1 - lum2).abs().pow(2)
100 })
101 .isqrt()
102}
103
104/// Difference of the average color of the images.
105///
106/// The distribution of the color is ignored, only the average color is used.
107/// Preserves colors better but loses a lot of the details.
108///
109/// # Examples
110///
111/// ```rust
112/// use phomo::metrics::avg_color;
113/// use image;
114///
115/// let img1 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([0, 0, 0]));
116/// let img2 = image::ImageBuffer::from_pixel(2, 2, image::Rgb([255, 255, 255]));
117/// let norm = avg_color(&img1, &img2);
118/// assert_eq!(norm, 255);
119/// ```
120pub fn avg_color(img1: &RgbImage, img2: &RgbImage) -> i64 {
121 let avg1 = img1.pixels().fold((0, 0, 0), |(r1, g1, b1), p1| {
122 (r1 + p1[0] as i64, g1 + p1[1] as i64, b1 + p1[2] as i64)
123 });
124 let avg2 = img2.pixels().fold((0, 0, 0), |(r2, g2, b2), p2| {
125 (r2 + p2[0] as i64, g2 + p2[1] as i64, b2 + p2[2] as i64)
126 });
127
128 (avg1.0.abs_diff(avg2.0) + avg1.1.abs_diff(avg2.1) + avg1.2.abs_diff(avg2.2)) as i64
129 / (3 * img1.width() as i64 * img1.height() as i64)
130}