use std::io::Read; use crate::framebuffer::Framebuffer; use crate::proto::{self, read_exact, read_i8}; /// Decode encoding 10 (Raw with tile interleave). /// /// Reads 1 flag byte. If bit 0 is clear, falls back to plain Raw. /// If bit 0 is set, reads w*h bytes of 16x16 tile-interleaved data /// and deinterleaves to row-major before blitting. /// /// Reference: ByteColorRFBRenderer.for() line 109. pub fn decode_raw_tile( r: &mut impl Read, fb: &mut Framebuffer, rx: u16, ry: u16, rw: u16, rh: u16, ) -> proto::Result<()> { let flag = read_i8(r)?; let w = rw as usize; let h = rh as usize; let size = w * h; if flag & 1 == 0 { // Plain raw — no interleave let data = read_exact(r, size)?; fb.apply_raw(rx, ry, rw, rh, &data); return Ok(()); } // Read tile-interleaved data let interleaved = read_exact(r, size)?; // Clamp to framebuffer bounds let w = w.min(fb.width as usize - rx as usize); let h = h.min(fb.height as usize - ry as usize); // Deinterleave 16x16 tiles to row-major. // Input is stored tile-by-tile: all pixels of tile (0,0), then tile (1,0), etc. // Each tile is row-major within itself. let tile = 16usize; let tiles_x = w.div_ceil(tile); let mut output = vec![0u8; w * h]; for row in 0..h { let tile_row = row / tile; let row_in_tile = row % tile; for col in 0..w { let tile_col = col / tile; let col_in_tile = col % tile; // Tile index in raster order let tile_idx = tile_row * tiles_x + tile_col; // Tile dimensions (edge tiles may be smaller) let tw = tile.min(w - tile_col * tile); // Offset within tile data: preceding full tiles + row offset + col offset let mut tile_data_start = 0usize; // Sum sizes of all preceding tiles for t in 0..tile_idx { let tr = t / tiles_x; let tc = t % tiles_x; let this_tw = tile.min(w - tc * tile); let this_th = tile.min(h - tr * tile); tile_data_start += this_tw * this_th; } let src = tile_data_start + row_in_tile * tw + col_in_tile; output[row * w + col] = interleaved[src]; } } fb.apply_raw(rx, ry, w as u16, h as u16, &output); Ok(()) } #[cfg(test)] mod tests { use super::*; use std::io::Cursor; #[test] fn test_raw_tile_plain_fallback() { let mut fb = Framebuffer::new(4, 4); // Flag byte with bit 0 clear → plain raw let mut data = vec![0x00i8 as u8]; // flag data.extend_from_slice(&[0x42; 16]); // 4x4 pixels let mut c = Cursor::new(data); decode_raw_tile(&mut c, &mut fb, 0, 0, 4, 4).unwrap(); assert!(fb.pixels.iter().all(|&p| p == 0x42)); } #[test] fn test_raw_tile_small_no_tile_boundary() { let mut fb = Framebuffer::new(4, 4); // Flag byte with bit 0 set, but 4x4 < 16x16 so no tile wrap occurs let mut data = vec![0x01u8]; // flag with interleave data.extend_from_slice(&[0xAA; 16]); // 4x4 pixels (all same, no deinterleave effect) let mut c = Cursor::new(data); decode_raw_tile(&mut c, &mut fb, 0, 0, 4, 4).unwrap(); assert!(fb.pixels.iter().all(|&p| p == 0xAA)); } }