use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Frame {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
impl Frame {
pub fn new(width: u32, height: u32, data: Vec<u8>) -> Self {
debug_assert_eq!(data.len(), (width as usize) * (height as usize) * 4);
Self { width, height, data }
}
pub fn zeroed(width: u32, height: u32) -> Self {
Self {
width,
height,
data: vec![0u8; (width as usize) * (height as usize) * 4],
}
}
pub fn tile_count(&self, tile_size: u32) -> (u32, u32) {
let cols = (self.width + tile_size - 1) / tile_size;
let rows = (self.height + tile_size - 1) / tile_size;
(cols, rows)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TileHash {
pub col: u16,
pub row: u16,
pub hash: u64,
}
#[derive(Debug, Clone)]
pub struct DeltaResult {
pub tile_size: u32,
pub cols: u32,
pub rows: u32,
pub changed_indices: Vec<u32>,
pub current_hashes: Vec<u64>,
}
impl DeltaResult {
pub fn change_ratio(&self) -> f32 {
let total = self.cols * self.rows;
if total == 0 { return 0.0; }
self.changed_indices.len() as f32 / total as f32
}
pub fn changed_tiles(&self) -> Vec<(u16, u16)> {
self.changed_indices.iter().map(|&idx| {
let col = (idx % self.cols) as u16;
let row = (idx / self.cols) as u16;
(col, row)
}).collect()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompressedFrame {
pub width: u32,
pub height: u32,
pub tile_size: u32,
pub total_tiles: u32,
pub changed_tiles: u32,
pub tiles: Vec<TileDescriptor>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TileDescriptor {
pub col: u16,
pub row: u16,
pub mean_color: [u8; 4],
pub edge_density: f32,
pub likely_text: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use proptest::collection::vec;
fn arb_tile_descriptor() -> impl Strategy<Value = TileDescriptor> {
(
any::<u16>(),
any::<u16>(),
any::<[u8; 4]>(),
(0.0f32..=1.0f32),
any::<bool>(),
)
.prop_map(|(col, row, mean_color, edge_density, likely_text)| TileDescriptor {
col,
row,
mean_color,
edge_density,
likely_text,
})
}
fn arb_compressed_frame() -> impl Strategy<Value = CompressedFrame> {
(
any::<u32>(),
any::<u32>(),
any::<u32>(),
any::<u32>(),
any::<u32>(),
vec(arb_tile_descriptor(), 0..16),
)
.prop_map(
|(width, height, tile_size, total_tiles, changed_tiles, tiles)| CompressedFrame {
width,
height,
tile_size,
total_tiles,
changed_tiles,
tiles,
},
)
}
fn arb_frame() -> impl Strategy<Value = Frame> {
(1u32..=64, 1u32..=64).prop_flat_map(|(w, h)| {
let size = (w as usize) * (h as usize) * 4;
vec(any::<u8>(), size).prop_map(move |data| Frame {
width: w,
height: h,
data,
})
})
}
proptest! {
#[test]
fn prop_compressed_frame_serde_roundtrip(cf in arb_compressed_frame()) {
let json = serde_json::to_string(&cf).unwrap();
let decoded: CompressedFrame = serde_json::from_str(&json).unwrap();
prop_assert_eq!(cf, decoded);
}
#[test]
fn prop_frame_serde_roundtrip(frame in arb_frame()) {
let json = serde_json::to_string(&frame).unwrap();
let decoded: Frame = serde_json::from_str(&json).unwrap();
prop_assert_eq!(frame, decoded);
}
#[test]
fn prop_tile_descriptor_serde_roundtrip(td in arb_tile_descriptor()) {
let json = serde_json::to_string(&td).unwrap();
let decoded: TileDescriptor = serde_json::from_str(&json).unwrap();
prop_assert_eq!(td, decoded);
}
}
}