Skip to main content

icu_lib/endecoder/utils/
diff.rs

1use crate::midata::MiData;
2use image::{Pixel, Rgba, RgbaImage};
3
4#[derive(Clone)]
5pub struct ImageDiffPixel {
6    pub pos: (u32, u32),
7    pub color_lhs: Rgba<u8>,
8    pub color_rhs: Rgba<u8>,
9    pub diff: [f32; 4],
10}
11
12impl ImageDiffPixel {
13    pub fn new(pos: (u32, u32), color_lhs: Rgba<u8>, color_rhs: Rgba<u8>) -> Self {
14        let mut s = Self {
15            pos,
16            color_lhs,
17            color_rhs,
18            diff: [0.; 4],
19        };
20
21        s.diff = s.calc_diff();
22        s
23    }
24
25    fn calc_diff(&self) -> [f32; 4] {
26        [
27            self.color_lhs[0] as f32 - self.color_rhs[0] as f32,
28            self.color_lhs[1] as f32 - self.color_rhs[1] as f32,
29            self.color_lhs[2] as f32 - self.color_rhs[2] as f32,
30            self.color_lhs[3] as f32 - self.color_rhs[3] as f32,
31        ]
32    }
33}
34
35#[derive(Clone)]
36pub struct ImageDiffResult {
37    size: (u32, u32),
38    diffs: Vec<ImageDiffPixel>,
39    min_diff: f32,
40    max_diff: f32,
41}
42
43impl Default for ImageDiffResult {
44    fn default() -> Self {
45        Self::new((0, 0), Vec::new(), f32::MIN, f32::MAX)
46    }
47}
48
49impl ImageDiffResult {
50    pub fn new(size: (u32, u32), diffs: Vec<ImageDiffPixel>, min_diff: f32, max_diff: f32) -> Self {
51        Self {
52            size,
53            diffs,
54            min_diff,
55            max_diff,
56        }
57    }
58    pub fn size(&self) -> (u32, u32) {
59        self.size
60    }
61
62    pub fn diffs(&self) -> &Vec<ImageDiffPixel> {
63        &self.diffs
64    }
65
66    pub fn diff_filter(&self, tolerance: f32) -> impl Iterator<Item = &ImageDiffPixel> + use<'_> {
67        self.diffs
68            .iter()
69            .filter(move |x| x.diff.iter().any(|&d| d.abs() >= tolerance))
70    }
71
72    pub fn max_diff(&self) -> f32 {
73        self.max_diff
74    }
75
76    pub fn min_diff(&self) -> f32 {
77        self.min_diff
78    }
79
80    pub fn render_diff_mask(&self, tolerance: f32, color: Rgba<u8>) -> RgbaImage {
81        let (width, height) = self.size();
82        let mut img = RgbaImage::new(width, height);
83        for pixel in img.pixels_mut() {
84            *pixel = Rgba([0, 0, 0, 0]);
85        }
86        for diff_pixel in self.diff_filter(tolerance) {
87            let (x, y) = diff_pixel.pos;
88            img.put_pixel(x, y, color);
89        }
90        img
91    }
92}
93
94pub fn blend_color32(
95    c1: &impl Pixel<Subpixel = u8>,
96    c2: &impl Pixel<Subpixel = u8>,
97    t: f32,
98) -> impl Pixel<Subpixel = u8> {
99    let t = t.clamp(0., 1.0);
100    let c1 = c1.to_rgba();
101    let c2 = c2.to_rgba();
102    Rgba::from([
103        (c1[0] as f32 * (1.0 - t) + c2[0] as f32 * t) as u8,
104        (c1[1] as f32 * (1.0 - t) + c2[1] as f32 * t) as u8,
105        (c1[2] as f32 * (1.0 - t) + c2[2] as f32 * t) as u8,
106        (c1[3] as f32 * (1.0 - t) + c2[3] as f32 * t) as u8,
107    ])
108}
109
110// fn color_diff_f32(c1: Color32, c2: Color32) -> f32 {
111//     // Simple Euclidean distance in RGBA space, normalized to [0,1]
112//     let dr = c1.r() as f32 - c2.r() as f32;
113//     let dg = c1.g() as f32 - c2.g() as f32;
114//     let db = c1.b() as f32 - c2.b() as f32;
115//     let da = c1.a() as f32 - c2.a() as f32;
116//
117//     (dr * dr + dg * dg + db * db + da * da).sqrt() / (4.0 * 255.0)
118// }
119
120pub fn color_diff_f32(c1: &impl Pixel<Subpixel = u8>, c2: &impl Pixel<Subpixel = u8>) -> f32 {
121    let a32 = c1.to_rgba();
122    let b32 = c2.to_rgba();
123
124    let dr = a32[0].abs_diff(b32[0]);
125    let dg = a32[1].abs_diff(b32[1]);
126    let db = a32[2].abs_diff(b32[2]);
127    let da = a32[3].abs_diff(b32[3]);
128
129    dr.max(dg).min(db).max(da) as f32 / (4.0 * 255.0)
130}
131
132pub fn diff_image(img1: &MiData, img2: &MiData) -> Option<ImageDiffResult> {
133    match (img1, img2) {
134        (MiData::RGBA(img1), MiData::RGBA(img2)) => {
135            if img1 != img2 && img1.width() == img2.width() && img1.height() == img2.height() {
136                // Only diff same size
137                let mut diff_data = Vec::with_capacity(img1.pixels().len());
138                let mut min_diff = f32::MAX;
139                let mut max_diff = f32::MIN;
140                // First pass: find min/max diff (in absolute pixel diff)
141                for ((p1_i, p1), p2) in img1.pixels().enumerate().zip(img2.pixels()) {
142                    let diff_pixel = ImageDiffPixel::new(
143                        (p1_i as u32 % img1.width(), p1_i as u32 / img1.width()),
144                        *p1,
145                        *p2,
146                    );
147                    let diff = diff_pixel.diff;
148                    let d = diff
149                        .into_iter()
150                        .map(f32::abs)
151                        .reduce(f32::max)
152                        .unwrap_or(0.);
153                    if d < min_diff {
154                        min_diff = d;
155                    }
156                    if d > max_diff {
157                        max_diff = d;
158                    }
159                    diff_data.push(diff_pixel);
160                }
161                Some(ImageDiffResult::new(
162                    (img1.width(), img1.height()),
163                    diff_data,
164                    min_diff,
165                    max_diff,
166                ))
167            } else {
168                None
169            }
170        }
171        _ => None,
172    }
173}