Skip to content

feat(server): find & send the damaged tiles #771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/ironrdp-graphics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ yuvutils-rs = { version = "0.8", features = ["rdp"] }

[dev-dependencies]
bmp = "0.5"
bytemuck = "1.21"
expect-test.workspace = true

[lints]
Expand Down
333 changes: 333 additions & 0 deletions crates/ironrdp-graphics/src/diff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct Rect {
pub x: usize,
pub y: usize,
pub width: usize,
pub height: usize,
}

impl Rect {
pub fn new(x: usize, y: usize, width: usize, height: usize) -> Self {
Self { x, y, width, height }
}

#[must_use]
pub fn add_xy(mut self, x: usize, y: usize) -> Self {
self.x += x;
self.y += y;
self
}

fn intersect(&self, other: &Rect) -> Option<Rect> {
let x = self.x.max(other.x);
let y = self.y.max(other.y);
let width = (self.x + self.width).min(other.x + other.width);
if width <= x {
return None;
}
let width = width - x;
let height = (self.y + self.height).min(other.y + other.height);
if height <= y {
return None;
}
let height = height - y;

Some(Rect::new(x, y, width, height))
}
}

const TILE_SIZE: usize = 64;

fn find_different_tiles<const BPP: usize>(
image1: &[u8],
stride1: usize,
image2: &[u8],
stride2: usize,
width: usize,
height: usize,
) -> Vec<bool> {
assert!(stride1 >= width * BPP);
assert!(stride2 >= width * BPP);
assert!(image1.len() >= (height - 1) * stride1 + width * BPP);
assert!(image2.len() >= (height - 1) * stride2 + width * BPP);

let tiles_x = width.div_ceil(TILE_SIZE);
let tiles_y = height.div_ceil(TILE_SIZE);
let mut tile_differences = vec![false; tiles_x * tiles_y];

tile_differences.iter_mut().enumerate().for_each(|(idx, diff)| {
let tile_start_x = (idx % tiles_x) * TILE_SIZE;
let tile_end_x = (tile_start_x + TILE_SIZE).min(width);
let tile_start_y = (idx / tiles_x) * TILE_SIZE;
let tile_end_y = (tile_start_y + TILE_SIZE).min(height);

// Check for any difference in tile using slice comparisons
let has_diff = (tile_start_y..tile_end_y).any(|y| {
let row_start1 = y * stride1;
let row_start2 = y * stride2;
let tile_row_start1 = row_start1 + tile_start_x * BPP;
let tile_row_end1 = row_start1 + tile_end_x * BPP;
let tile_row_start2 = row_start2 + tile_start_x * BPP;
let tile_row_end2 = row_start2 + tile_end_x * BPP;

image1[tile_row_start1..tile_row_end1] != image2[tile_row_start2..tile_row_end2]
});

*diff = has_diff;
});

tile_differences
}

fn find_different_rects<const BPP: usize>(
image1: &[u8],
stride1: usize,
image2: &[u8],
stride2: usize,
width: usize,
height: usize,
) -> Vec<Rect> {
let mut tile_differences = find_different_tiles::<BPP>(image1, stride1, image2, stride2, width, height);

let mod_width = width % TILE_SIZE;
let mod_height = height % TILE_SIZE;
let tiles_x = width.div_ceil(TILE_SIZE);
let tiles_y = height.div_ceil(TILE_SIZE);

let mut rectangles = Vec::new();
let mut current_idx = 0;
let total_tiles = tiles_x * tiles_y;

// Process tiles in linear fashion to find rectangular regions
while current_idx < total_tiles {
if !tile_differences[current_idx] {
current_idx += 1;
continue;
}

let start_y = current_idx / tiles_x;
let start_x = current_idx % tiles_x;

// Expand horizontally as much as possible
let mut max_width = 1;
while start_x + max_width < tiles_x && tile_differences[current_idx + max_width] {
max_width += 1;
}

// Expand vertically as much as possible
let mut max_height = 1;
'vertical: while start_y + max_height < tiles_y {
for x in 0..max_width {
let check_idx = (start_y + max_height) * tiles_x + start_x + x;
if !tile_differences[check_idx] {
break 'vertical;
}
}
max_height += 1;
}

// Calculate pixel coordinates
let pixel_x = start_x * TILE_SIZE;
let pixel_y = start_y * TILE_SIZE;

let pixel_width = if start_x + max_width == tiles_x && mod_width > 0 {
(max_width - 1) * TILE_SIZE + mod_width
} else {
max_width * TILE_SIZE
};

let pixel_height = if start_y + max_height == tiles_y && mod_height > 0 {
(max_height - 1) * TILE_SIZE + mod_height
} else {
max_height * TILE_SIZE
};

rectangles.push(Rect {
x: pixel_x,
y: pixel_y,
width: pixel_width,
height: pixel_height,
});

// Mark tiles as processed
for y in 0..max_height {
for x in 0..max_width {
let idx = (start_y + y) * tiles_x + start_x + x;
tile_differences[idx] = false;
}
}

current_idx += max_width;
}

rectangles
}

/// Helper function to find different regions in two images.
///
/// This function takes two images as input and returns a list of rectangles
/// representing the different regions between the two images, in image2 coordinates.
///
/// ```text
/// ┌───────────────────────────────────────────┐
/// │ image1 │
/// │ │
/// │ (x,y) │
/// │ ┌───────────────┐ │
/// │ │ image2 │ │
/// │ │ │ │
/// │ │ │ │
/// │ │ │ │
/// │ │ │ │
/// │ │ │ │
/// │ └───────────────┘ │
/// │ │
/// └───────────────────────────────────────────┘
/// ```
#[allow(clippy::too_many_arguments)]
pub fn find_different_rects_sub<const BPP: usize>(
image1: &[u8],
stride1: usize,
width1: usize,
height1: usize,
image2: &[u8],
stride2: usize,
width2: usize,
height2: usize,
x: usize,
y: usize,
) -> Vec<Rect> {
let rect1 = Rect::new(0, 0, width1, height1);
let rect2 = Rect::new(x, y, width2, height2);
let Some(inter) = rect1.intersect(&rect2) else {
return vec![];
};

let image1 = &image1[y * stride1 + x * BPP..];
find_different_rects::<BPP>(image1, stride1, image2, stride2, inter.width, inter.height)
}

#[cfg(test)]
mod tests {
use super::*;
use bytemuck::cast_slice;

#[test]
fn test_intersect() {
let r1 = Rect::new(0, 0, 640, 480);
let r2 = Rect::new(10, 10, 10, 10);
let r3 = Rect::new(630, 470, 20, 20);

assert_eq!(r1.intersect(&r1).as_ref(), Some(&r1));
assert_eq!(r1.intersect(&r2).as_ref(), Some(&r2));
assert_eq!(r1.intersect(&r3), Some(Rect::new(630, 470, 10, 10)));
assert_eq!(r2.intersect(&r3), None);
}

#[test]
fn test_single_tile() {
const SIZE: usize = 128;
let image1 = vec![0u32; SIZE * SIZE];
let mut image2 = vec![0u32; SIZE * SIZE];
image2[65 * 128 + 65] = 1;
let result =
find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
assert_eq!(
result,
vec![Rect {
x: 64,
y: 64,
width: 64,
height: 64
}]
);
}

#[test]
fn test_adjacent_tiles() {
const SIZE: usize = 256;
let image1 = vec![0u32; SIZE * SIZE];
let mut image2 = vec![0u32; SIZE * SIZE];
// Modify two adjacent tiles
image2[65 * SIZE + 65] = 1;
image2[65 * SIZE + 129] = 1;
let result =
find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
assert_eq!(
result,
vec![Rect {
x: 64,
y: 64,
width: 128,
height: 64
}]
);
}

#[test]
fn test_edge_tiles() {
const SIZE: usize = 100;
let image1 = vec![0u32; SIZE * SIZE];
let mut image2 = vec![0u32; SIZE * SIZE];
image2[65 * SIZE + 65] = 1;
let result =
find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
assert_eq!(
result,
vec![Rect {
x: 64,
y: 64,
width: 36,
height: 36
}]
);
}

#[test]
fn test_large() {
const SIZE: usize = 4096;
let image1 = vec![0u32; SIZE * SIZE];
let mut image2 = vec![0u32; SIZE * SIZE];
image2[95 * 100 + 95] = 1;
let _result =
find_different_rects::<4>(cast_slice(&image1), SIZE * 4, cast_slice(&image2), SIZE * 4, SIZE, SIZE);
}

#[test]
fn test_sub_diff() {
let image1 = vec![0u32; 2048 * 2048];
let mut image2 = vec![0u32; 1024 * 1024];
image2[0] = 1;
image2[1024 * 65 + 512 - 1] = 1;

let res = find_different_rects_sub::<4>(
cast_slice(&image1),
2048 * 4,
2048,
2048,
cast_slice(&image2),
1024 * 4,
512,
512,
1024,
1024,
);
assert_eq!(
res,
vec![
Rect {
x: 0,
y: 0,
width: 64,
height: 64
},
Rect {
x: 448,
y: 64,
width: 64,
height: 64
}
]
)
}
}
1 change: 1 addition & 0 deletions crates/ironrdp-graphics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#![allow(clippy::cast_sign_loss)] // FIXME: remove

pub mod color_conversion;
pub mod diff;
pub mod dwt;
pub mod image_processing;
pub mod pointer;
Expand Down
Loading
Loading