1#[derive(Debug, PartialEq, Eq, Clone, Hash)]
2pub struct Rect {
3 pub x: usize,
4 pub y: usize,
5 pub width: usize,
6 pub height: usize,
7}
8
9impl Rect {
10 pub fn new(x: usize, y: usize, width: usize, height: usize) -> Self {
11 Self { x, y, width, height }
12 }
13
14 #[must_use]
15 pub fn add_xy(mut self, x: usize, y: usize) -> Self {
16 self.x += x;
17 self.y += y;
18 self
19 }
20
21 fn intersect(&self, other: &Rect) -> Option<Rect> {
22 let x = self.x.max(other.x);
23 let y = self.y.max(other.y);
24 let width = (self.x + self.width).min(other.x + other.width);
25 if width <= x {
26 return None;
27 }
28 let width = width - x;
29 let height = (self.y + self.height).min(other.y + other.height);
30 if height <= y {
31 return None;
32 }
33 let height = height - y;
34
35 Some(Rect::new(x, y, width, height))
36 }
37}
38
39const TILE_SIZE: usize = 64;
40
41fn find_different_tiles<const BPP: usize>(
42 image1: &[u8],
43 stride1: usize,
44 image2: &[u8],
45 stride2: usize,
46 width: usize,
47 height: usize,
48) -> Vec<bool> {
49 assert!(stride1 >= width * BPP);
50 assert!(stride2 >= width * BPP);
51 assert!(image1.len() >= (height - 1) * stride1 + width * BPP);
52 assert!(image2.len() >= (height - 1) * stride2 + width * BPP);
53
54 let tiles_x = width.div_ceil(TILE_SIZE);
55 let tiles_y = height.div_ceil(TILE_SIZE);
56 let mut tile_differences = vec![false; tiles_x * tiles_y];
57
58 tile_differences.iter_mut().enumerate().for_each(|(idx, diff)| {
59 let tile_start_x = (idx % tiles_x) * TILE_SIZE;
60 let tile_end_x = (tile_start_x + TILE_SIZE).min(width);
61 let tile_start_y = (idx / tiles_x) * TILE_SIZE;
62 let tile_end_y = (tile_start_y + TILE_SIZE).min(height);
63
64 let has_diff = (tile_start_y..tile_end_y).any(|y| {
66 let row_start1 = y * stride1;
67 let row_start2 = y * stride2;
68 let tile_row_start1 = row_start1 + tile_start_x * BPP;
69 let tile_row_end1 = row_start1 + tile_end_x * BPP;
70 let tile_row_start2 = row_start2 + tile_start_x * BPP;
71 let tile_row_end2 = row_start2 + tile_end_x * BPP;
72
73 image1[tile_row_start1..tile_row_end1] != image2[tile_row_start2..tile_row_end2]
74 });
75
76 *diff = has_diff;
77 });
78
79 tile_differences
80}
81
82fn find_different_rects<const BPP: usize>(
83 image1: &[u8],
84 stride1: usize,
85 image2: &[u8],
86 stride2: usize,
87 width: usize,
88 height: usize,
89) -> Vec<Rect> {
90 let mut tile_differences = find_different_tiles::<BPP>(image1, stride1, image2, stride2, width, height);
91
92 let mod_width = width % TILE_SIZE;
93 let mod_height = height % TILE_SIZE;
94 let tiles_x = width.div_ceil(TILE_SIZE);
95 let tiles_y = height.div_ceil(TILE_SIZE);
96
97 let mut rectangles = Vec::new();
98 let mut current_idx = 0;
99 let total_tiles = tiles_x * tiles_y;
100
101 while current_idx < total_tiles {
103 if !tile_differences[current_idx] {
104 current_idx += 1;
105 continue;
106 }
107
108 let start_y = current_idx / tiles_x;
109 let start_x = current_idx % tiles_x;
110
111 let mut max_width = 1;
113 while start_x + max_width < tiles_x && tile_differences[current_idx + max_width] {
114 max_width += 1;
115 }
116
117 let mut max_height = 1;
119 'vertical: while start_y + max_height < tiles_y {
120 for x in 0..max_width {
121 let check_idx = (start_y + max_height) * tiles_x + start_x + x;
122 if !tile_differences[check_idx] {
123 break 'vertical;
124 }
125 }
126 max_height += 1;
127 }
128
129 let pixel_x = start_x * TILE_SIZE;
131 let pixel_y = start_y * TILE_SIZE;
132
133 let pixel_width = if start_x + max_width == tiles_x && mod_width > 0 {
134 (max_width - 1) * TILE_SIZE + mod_width
135 } else {
136 max_width * TILE_SIZE
137 };
138
139 let pixel_height = if start_y + max_height == tiles_y && mod_height > 0 {
140 (max_height - 1) * TILE_SIZE + mod_height
141 } else {
142 max_height * TILE_SIZE
143 };
144
145 rectangles.push(Rect {
146 x: pixel_x,
147 y: pixel_y,
148 width: pixel_width,
149 height: pixel_height,
150 });
151
152 for y in 0..max_height {
154 for x in 0..max_width {
155 let idx = (start_y + y) * tiles_x + start_x + x;
156 tile_differences[idx] = false;
157 }
158 }
159
160 current_idx += max_width;
161 }
162
163 rectangles
164}
165
166#[expect(clippy::too_many_arguments)]
188pub fn find_different_rects_sub<const BPP: usize>(
189 image1: &[u8],
190 stride1: usize,
191 width1: usize,
192 height1: usize,
193 image2: &[u8],
194 stride2: usize,
195 width2: usize,
196 height2: usize,
197 x: usize,
198 y: usize,
199) -> Vec<Rect> {
200 let rect1 = Rect::new(0, 0, width1, height1);
201 let rect2 = Rect::new(x, y, width2, height2);
202 let Some(inter) = rect1.intersect(&rect2) else {
203 return vec![];
204 };
205
206 let image1 = &image1[y * stride1 + x * BPP..];
207 find_different_rects::<BPP>(image1, stride1, image2, stride2, inter.width, inter.height)
208}
209
210#[cfg(test)]
211mod tests {
212 use bytemuck::cast_slice;
213
214 use super::*;
215
216 #[test]
217 fn test_intersect() {
218 let r1 = Rect::new(0, 0, 640, 480);
219 let r2 = Rect::new(10, 10, 10, 10);
220 let r3 = Rect::new(630, 470, 20, 20);
221
222 assert_eq!(r1.intersect(&r1).as_ref(), Some(&r1));
223 assert_eq!(r1.intersect(&r2).as_ref(), Some(&r2));
224 assert_eq!(r1.intersect(&r3), Some(Rect::new(630, 470, 10, 10)));
225 assert_eq!(r2.intersect(&r3), None);
226 }
227
228 #[test]
229 fn test_single_tile() {
230 const SIZE: usize = 128;
231 let image1 = vec![0u32; SIZE * SIZE];
232 let mut image2 = vec![0u32; SIZE * SIZE];
233 image2[65 * 128 + 65] = 1;
234 let result =
235 find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
236 assert_eq!(
237 result,
238 vec![Rect {
239 x: 64,
240 y: 64,
241 width: 64,
242 height: 64
243 }]
244 );
245 }
246
247 #[test]
248 fn test_adjacent_tiles() {
249 const SIZE: usize = 256;
250 let image1 = vec![0u32; SIZE * SIZE];
251 let mut image2 = vec![0u32; SIZE * SIZE];
252 image2[65 * SIZE + 65] = 1;
254 image2[65 * SIZE + 129] = 1;
255 let result =
256 find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
257 assert_eq!(
258 result,
259 vec![Rect {
260 x: 64,
261 y: 64,
262 width: 128,
263 height: 64
264 }]
265 );
266 }
267
268 #[test]
269 fn test_edge_tiles() {
270 const SIZE: usize = 100;
271 let image1 = vec![0u32; SIZE * SIZE];
272 let mut image2 = vec![0u32; SIZE * SIZE];
273 image2[65 * SIZE + 65] = 1;
274 let result =
275 find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
276 assert_eq!(
277 result,
278 vec![Rect {
279 x: 64,
280 y: 64,
281 width: 36,
282 height: 36
283 }]
284 );
285 }
286
287 #[test]
288 fn test_large() {
289 const SIZE: usize = 4096;
290 let image1 = vec![0u32; SIZE * SIZE];
291 let mut image2 = vec![0u32; SIZE * SIZE];
292 image2[95 * 100 + 95] = 1;
293 let _result =
294 find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
295 }
296
297 #[test]
298 fn test_sub_diff() {
299 let image1 = vec![0u32; 2048 * 2048];
300 let mut image2 = vec![0u32; 1024 * 1024];
301 image2[0] = 1;
302 image2[1024 * 65 + 512 - 1] = 1;
303
304 let res = find_different_rects_sub::<4>(
305 cast_slice(&image1),
306 2048 * 4,
307 2048,
308 2048,
309 cast_slice(&image2),
310 1024 * 4,
311 512,
312 512,
313 1024,
314 1024,
315 );
316 assert_eq!(
317 res,
318 vec![
319 Rect {
320 x: 0,
321 y: 0,
322 width: 64,
323 height: 64
324 },
325 Rect {
326 x: 448,
327 y: 64,
328 width: 64,
329 height: 64
330 }
331 ]
332 )
333 }
334}