use std::io::Read; use crate::framebuffer::Framebuffer; use crate::proto::{self, read_exact, read_u8}; // Subencoding flag bits — ByteColorRFBRenderer.int(), line 192 const RAW: u8 = 1; const BACKGROUND_SPECIFIED: u8 = 2; const FOREGROUND_SPECIFIED: u8 = 4; const ANY_SUBRECTS: u8 = 8; const SUBRECTS_COLOURED: u8 = 16; /// Decode a Hextile-encoded rectangle into the framebuffer. /// /// The rectangle is divided into 16x16 tiles (edge tiles may be smaller). /// Background and foreground colors persist across tiles within one call. /// /// Reference: ByteColorRFBRenderer.int() line 169. pub fn decode_hextile( r: &mut impl Read, fb: &mut Framebuffer, rx: u16, ry: u16, rw: u16, rh: u16, ) -> proto::Result<()> { let mut bg: u8 = 0; let mut fg: u8 = 0; let mut ty = ry; while ty < ry + rh { let tile_h = (ry + rh - ty).min(16); let mut tx = rx; while tx < rx + rw { let tile_w = (rx + rw - tx).min(16); let flags = read_u8(r)?; if flags & RAW != 0 { // Raw tile: read tile_w * tile_h bytes let size = tile_w as usize * tile_h as usize; let data = read_exact(r, size)?; fb.apply_raw(tx, ty, tile_w, tile_h, &data); tx += 16; continue; } if flags & BACKGROUND_SPECIFIED != 0 { bg = read_u8(r)?; } // Fill tile with background fb.fill_rect(tx, ty, tile_w, tile_h, bg); if flags & FOREGROUND_SPECIFIED != 0 { fg = read_u8(r)?; } if flags & ANY_SUBRECTS != 0 { let num_subrects = read_u8(r)?; let coloured = flags & SUBRECTS_COLOURED != 0; for _ in 0..num_subrects { let color = if coloured { read_u8(r)? } else { fg }; let xy = read_u8(r)?; let wh = read_u8(r)?; let sx = (xy >> 4) as u16; let sy = (xy & 0x0F) as u16; let sw = ((wh >> 4) + 1) as u16; let sh = ((wh & 0x0F) + 1) as u16; fb.fill_rect(tx + sx, ty + sy, sw, sh, color); } } tx += 16; } ty += 16; } Ok(()) } #[cfg(test)] mod tests { use super::*; use std::io::Cursor; #[test] fn test_hextile_raw_tile() { let mut fb = Framebuffer::new(16, 16); // One 16x16 tile, Raw subencoding let mut data = vec![RAW]; // flags data.extend_from_slice(&[0x42u8; 256]); // 16*16 raw pixels let mut c = Cursor::new(data); decode_hextile(&mut c, &mut fb, 0, 0, 16, 16).unwrap(); assert_eq!(fb.pixels[0], 0x42); assert_eq!(fb.pixels[255], 0x42); } #[test] fn test_hextile_bg_fill() { let mut fb = Framebuffer::new(16, 16); // One tile: background=0x09, no subrects let data = vec![BACKGROUND_SPECIFIED, 0x09]; let mut c = Cursor::new(data); decode_hextile(&mut c, &mut fb, 0, 0, 16, 16).unwrap(); assert!(fb.pixels.iter().all(|&p| p == 0x09)); } #[test] fn test_hextile_subrects_coloured() { let mut fb = Framebuffer::new(16, 16); // Background=0x00, 1 coloured subrect at (2,3) size 4x5 color 0xFF let data = vec![ BACKGROUND_SPECIFIED | ANY_SUBRECTS | SUBRECTS_COLOURED, 0x00, // bg 1, // num_subrects 0xFF, // subrect color 0x23, // xy: x=2, y=3 0x34, // wh: w=3+1=4, h=4+1=5 ]; let mut c = Cursor::new(data); decode_hextile(&mut c, &mut fb, 0, 0, 16, 16).unwrap(); assert_eq!(fb.pixels[0], 0x00); // background assert_eq!(fb.pixels[3 * 16 + 2], 0xFF); // subrect at (2,3) assert_eq!(fb.pixels[7 * 16 + 5], 0xFF); // subrect at (5,7) assert_eq!(fb.pixels[8 * 16 + 2], 0x00); // below subrect } #[test] fn test_hextile_fg_subrects() { let mut fb = Framebuffer::new(16, 16); // Background=0x00, foreground=0xAA, 1 subrect at (0,0) size 2x2 let data = vec![ BACKGROUND_SPECIFIED | FOREGROUND_SPECIFIED | ANY_SUBRECTS, 0x00, // bg 0xAA, // fg 1, // num_subrects 0x00, // xy: x=0, y=0 0x11, // wh: w=1+1=2, h=1+1=2 ]; let mut c = Cursor::new(data); decode_hextile(&mut c, &mut fb, 0, 0, 16, 16).unwrap(); assert_eq!(fb.pixels[0], 0xAA); assert_eq!(fb.pixels[1], 0xAA); assert_eq!(fb.pixels[16], 0xAA); assert_eq!(fb.pixels[17], 0xAA); assert_eq!(fb.pixels[2], 0x00); } }