ironrdp_graphics/
diff.rs

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        // Check for any difference in tile using slice comparisons
65        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    // Process tiles in linear fashion to find rectangular regions
102    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        // Expand horizontally as much as possible
112        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        // Expand vertically as much as possible
118        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        // Calculate pixel coordinates
130        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        // Mark tiles as processed
153        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/// Helper function to find different regions in two images.
167///
168/// This function takes two images as input and returns a list of rectangles
169/// representing the different regions between the two images, in image2 coordinates.
170///
171/// ```text
172///     ┌───────────────────────────────────────────┐
173///     │ image1                                    │
174///     │                                           │
175///     │                    (x,y)                  │
176///     │                     ┌───────────────┐     │
177///     │                     │ image2        │     │
178///     │                     │               │     │
179///     │                     │               │     │
180///     │                     │               │     │
181///     │                     │               │     │
182///     │                     │               │     │
183///     │                     └───────────────┘     │
184///     │                                           │
185///     └───────────────────────────────────────────┘
186/// ```
187#[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        // Modify two adjacent tiles
253        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}