use crate::render_submission::{FrameId, RenderOwnership}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct OwnedRgba8888Frame { pub frame_id: FrameId, pub ownership: RenderOwnership, pub width: usize, pub height: usize, pub stride_pixels: usize, pub pixels: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OwnedRgba8888FrameError { EmptyDimensions, StrideTooSmall, PixelBufferTooSmall, } impl OwnedRgba8888Frame { pub fn new( frame_id: FrameId, ownership: RenderOwnership, width: usize, height: usize, stride_pixels: usize, pixels: Vec, ) -> Result { if width == 0 || height == 0 { return Err(OwnedRgba8888FrameError::EmptyDimensions); } if stride_pixels < width { return Err(OwnedRgba8888FrameError::StrideTooSmall); } let required_len = stride_pixels .checked_mul(height) .ok_or(OwnedRgba8888FrameError::PixelBufferTooSmall)?; if pixels.len() < required_len { return Err(OwnedRgba8888FrameError::PixelBufferTooSmall); } Ok(Self { frame_id, ownership, width, height, stride_pixels, pixels }) } pub fn packed( frame_id: FrameId, ownership: RenderOwnership, width: usize, height: usize, pixels: Vec, ) -> Result { Self::new(frame_id, ownership, width, height, width, pixels) } pub fn required_pixel_len(&self) -> usize { self.stride_pixels * self.height } pub fn row(&self, y: usize) -> Option<&[u32]> { if y >= self.height { return None; } let start = y * self.stride_pixels; Some(&self.pixels[start..start + self.width]) } } #[cfg(test)] mod tests { use super::*; use crate::app_mode::AppMode; fn ownership() -> RenderOwnership { RenderOwnership::new(11, AppMode::Game, 42) } #[test] fn owned_rgba8888_frame_preserves_metadata_and_pixels() { let pixels = vec![0x11223344, 0x55667788, 0x99AABBCC, 0xDDEEFF00]; let frame = OwnedRgba8888Frame::packed(FrameId::new(7), ownership(), 2, 2, pixels.clone()) .expect("valid packed RGBA8888 frame"); assert_eq!(frame.frame_id, FrameId::new(7)); assert_eq!(frame.ownership, ownership()); assert_eq!(frame.width, 2); assert_eq!(frame.height, 2); assert_eq!(frame.stride_pixels, 2); assert_eq!(frame.pixels, pixels); assert_eq!(frame.required_pixel_len(), 4); } #[test] fn checked_constructor_rejects_empty_dimensions() { let err = OwnedRgba8888Frame::new(FrameId::ZERO, ownership(), 0, 1, 1, vec![0]) .expect_err("zero width should be rejected"); assert_eq!(err, OwnedRgba8888FrameError::EmptyDimensions); } #[test] fn checked_constructor_rejects_stride_smaller_than_width() { let err = OwnedRgba8888Frame::new(FrameId::ZERO, ownership(), 3, 1, 2, vec![0, 0, 0]) .expect_err("stride smaller than width should be rejected"); assert_eq!(err, OwnedRgba8888FrameError::StrideTooSmall); } #[test] fn checked_constructor_rejects_short_pixel_buffer() { let err = OwnedRgba8888Frame::new(FrameId::ZERO, ownership(), 2, 2, 3, vec![0; 5]) .expect_err("buffer shorter than stride * height should be rejected"); assert_eq!(err, OwnedRgba8888FrameError::PixelBufferTooSmall); } #[test] fn row_returns_visible_width_without_stride_padding() { let frame = OwnedRgba8888Frame::new(FrameId::ZERO, ownership(), 2, 2, 3, vec![1, 2, 99, 3, 4, 88]) .expect("valid padded frame"); assert_eq!(frame.row(0), Some(&[1, 2][..])); assert_eq!(frame.row(1), Some(&[3, 4][..])); assert_eq!(frame.row(2), None); } }