Skip to main content

ass_renderer/renderer/
frame.rs

1//! Rendered frame representation
2
3#[cfg(feature = "nostd")]
4use alloc::{sync::Arc, vec, vec::Vec};
5#[cfg(not(feature = "nostd"))]
6use std::{sync::Arc, vec::Vec};
7
8/// Rendered frame containing pixel data.
9///
10/// The pixel buffer is reference-counted (`Arc`) so that cloning a frame — e.g.
11/// returning a cached static frame for a new timestamp — is O(1) and shares the
12/// pixels. Mutating accessors use copy-on-write, so an exclusively-owned frame is
13/// still mutated in place.
14#[derive(Clone)]
15pub struct Frame {
16    buffer: Arc<Vec<u8>>,
17    width: u32,
18    height: u32,
19    timestamp: u32,
20    format: PixelFormat,
21}
22
23/// Pixel format for frame data
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum PixelFormat {
26    /// RGBA with 8 bits per channel
27    Rgba8,
28    /// BGRA with 8 bits per channel
29    Bgra8,
30    /// RGB with 8 bits per channel
31    Rgb8,
32}
33
34impl Frame {
35    /// Create a new frame with the given buffer
36    pub fn new(buffer: Vec<u8>, width: u32, height: u32, timestamp: u32) -> Self {
37        Self {
38            buffer: Arc::new(buffer),
39            width,
40            height,
41            timestamp,
42            format: PixelFormat::Rgba8,
43        }
44    }
45
46    /// Create a frame from RGBA data
47    pub fn from_rgba(buffer: Vec<u8>, width: u32, height: u32) -> Self {
48        Self::new(buffer, width, height, 0)
49    }
50
51    /// Create a new frame with specific pixel format
52    pub fn with_format(
53        buffer: Vec<u8>,
54        width: u32,
55        height: u32,
56        timestamp: u32,
57        format: PixelFormat,
58    ) -> Self {
59        Self {
60            buffer: Arc::new(buffer),
61            width,
62            height,
63            timestamp,
64            format,
65        }
66    }
67
68    /// Create an empty frame (transparent)
69    pub fn empty(width: u32, height: u32, timestamp: u32) -> Self {
70        let size = (width * height * 4) as usize;
71        Self::new(vec![0; size], width, height, timestamp)
72    }
73
74    /// Clone this frame sharing its pixel buffer (O(1)) but with a new timestamp.
75    /// Used to serve a cached static frame for the current time without copying.
76    pub fn with_timestamp(&self, timestamp: u32) -> Self {
77        Self {
78            buffer: Arc::clone(&self.buffer),
79            timestamp,
80            ..*self
81        }
82    }
83
84    /// Get frame buffer data
85    pub fn data(&self) -> &[u8] {
86        self.buffer.as_slice()
87    }
88
89    /// Get frame pixels (alias for data())
90    pub fn pixels(&self) -> &[u8] {
91        self.buffer.as_slice()
92    }
93
94    /// Get mutable frame buffer data (copy-on-write if the buffer is shared)
95    pub fn data_mut(&mut self) -> &mut [u8] {
96        Arc::make_mut(&mut self.buffer).as_mut_slice()
97    }
98
99    /// Take ownership of the buffer (clones only if the buffer is still shared)
100    pub fn into_buffer(self) -> Vec<u8> {
101        Arc::try_unwrap(self.buffer).unwrap_or_else(|arc| (*arc).clone())
102    }
103
104    /// Get frame width
105    pub fn width(&self) -> u32 {
106        self.width
107    }
108
109    /// Get frame height
110    pub fn height(&self) -> u32 {
111        self.height
112    }
113
114    /// Get frame timestamp in centiseconds
115    pub fn timestamp(&self) -> u32 {
116        self.timestamp
117    }
118
119    /// Get pixel format
120    pub fn format(&self) -> PixelFormat {
121        self.format
122    }
123
124    /// Get bytes per pixel
125    pub fn bytes_per_pixel(&self) -> usize {
126        match self.format {
127            PixelFormat::Rgba8 | PixelFormat::Bgra8 => 4,
128            PixelFormat::Rgb8 => 3,
129        }
130    }
131
132    /// Get stride (bytes per row)
133    pub fn stride(&self) -> usize {
134        self.width as usize * self.bytes_per_pixel()
135    }
136
137    /// Convert to RGBA format if not already
138    pub fn to_rgba(mut self) -> Self {
139        match self.format {
140            PixelFormat::Rgba8 => self,
141            PixelFormat::Bgra8 => {
142                for chunk in Arc::make_mut(&mut self.buffer).chunks_exact_mut(4) {
143                    chunk.swap(0, 2);
144                }
145                self.format = PixelFormat::Rgba8;
146                self
147            }
148            PixelFormat::Rgb8 => {
149                let mut rgba = Vec::with_capacity((self.width * self.height * 4) as usize);
150                for chunk in self.buffer.chunks_exact(3) {
151                    rgba.extend_from_slice(&[chunk[0], chunk[1], chunk[2], 255]);
152                }
153                self.buffer = Arc::new(rgba);
154                self.format = PixelFormat::Rgba8;
155                self
156            }
157        }
158    }
159
160    /// Check if frame is empty (all transparent)
161    pub fn is_empty(&self) -> bool {
162        match self.format {
163            PixelFormat::Rgba8 | PixelFormat::Bgra8 => {
164                self.buffer.chunks_exact(4).all(|pixel| pixel[3] == 0)
165            }
166            PixelFormat::Rgb8 => false,
167        }
168    }
169}