⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/video/
mod.rs

1//! Video framework for VeridianOS
2//!
3//! Provides video frame management, pixel format conversion, image decoding
4//! (TGA, QOI, PPM, BMP), framebuffer operations (scaling, blitting, color
5//! space conversion), and a simple media player for raw frame sequences.
6
7#![allow(dead_code)]
8
9pub mod decode;
10pub mod framebuffer;
11pub mod player;
12
13use alloc::{vec, vec::Vec};
14use core::sync::atomic::{AtomicBool, Ordering};
15
16use crate::{error::KernelError, graphics::PixelFormat};
17
18// ---------------------------------------------------------------------------
19// Video frame
20// ---------------------------------------------------------------------------
21
22/// A single video/image frame stored in CPU-accessible memory.
23#[derive(Debug, Clone)]
24pub(crate) struct VideoFrame {
25    /// Width in pixels.
26    pub(crate) width: u32,
27    /// Height in pixels.
28    pub(crate) height: u32,
29    /// Pixel format.
30    pub(crate) format: PixelFormat,
31    /// Raw pixel data (row-major, tightly packed or with stride padding).
32    data: Vec<u8>,
33    /// Row stride in bytes (>= width * bpp).
34    pub(crate) stride: u32,
35}
36
37impl VideoFrame {
38    /// Allocate a new, zeroed frame.
39    pub(crate) fn new(width: u32, height: u32, format: PixelFormat) -> Self {
40        let bpp = format.bytes_per_pixel() as u32;
41        let stride = width * bpp;
42        let size = (stride as usize) * (height as usize);
43        Self {
44            width,
45            height,
46            format,
47            data: vec![0u8; size],
48            stride,
49        }
50    }
51
52    /// Byte offset of pixel (x, y) in the data buffer.
53    #[inline]
54    pub(crate) fn pixel_offset(&self, x: u32, y: u32) -> usize {
55        (y as usize) * (self.stride as usize) + (x as usize) * self.format.bytes_per_pixel()
56    }
57
58    /// Write a pixel at (x, y).  Out-of-bounds writes are silently ignored.
59    pub(crate) fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) {
60        if x >= self.width || y >= self.height {
61            return;
62        }
63        let off = self.pixel_offset(x, y);
64        match self.format {
65            PixelFormat::Xrgb8888 => {
66                if off + 3 < self.data.len() {
67                    self.data[off] = b;
68                    self.data[off + 1] = g;
69                    self.data[off + 2] = r;
70                    self.data[off + 3] = 0xFF;
71                }
72            }
73            PixelFormat::Argb8888 => {
74                if off + 3 < self.data.len() {
75                    self.data[off] = b;
76                    self.data[off + 1] = g;
77                    self.data[off + 2] = r;
78                    self.data[off + 3] = a;
79                }
80            }
81            PixelFormat::Rgb888 => {
82                if off + 2 < self.data.len() {
83                    self.data[off] = r;
84                    self.data[off + 1] = g;
85                    self.data[off + 2] = b;
86                }
87            }
88            PixelFormat::Rgb565 => {
89                if off + 1 < self.data.len() {
90                    let val: u16 =
91                        ((r as u16 & 0xF8) << 8) | ((g as u16 & 0xFC) << 3) | (b as u16 >> 3);
92                    self.data[off] = (val & 0xFF) as u8;
93                    self.data[off + 1] = (val >> 8) as u8;
94                }
95            }
96            PixelFormat::Bgr888 => {
97                if off + 2 < self.data.len() {
98                    self.data[off] = b;
99                    self.data[off + 1] = g;
100                    self.data[off + 2] = r;
101                }
102            }
103            PixelFormat::Bgrx8888 => {
104                if off + 3 < self.data.len() {
105                    self.data[off] = b;
106                    self.data[off + 1] = g;
107                    self.data[off + 2] = r;
108                    self.data[off + 3] = 0xFF;
109                }
110            }
111            PixelFormat::Rgba8888 => {
112                if off + 3 < self.data.len() {
113                    self.data[off] = r;
114                    self.data[off + 1] = g;
115                    self.data[off + 2] = b;
116                    self.data[off + 3] = a;
117                }
118            }
119            PixelFormat::Bgra8888 => {
120                if off + 3 < self.data.len() {
121                    self.data[off] = b;
122                    self.data[off + 1] = g;
123                    self.data[off + 2] = r;
124                    self.data[off + 3] = a;
125                }
126            }
127            PixelFormat::Xbgr8888 => {
128                if off + 3 < self.data.len() {
129                    self.data[off] = 0xFF;
130                    self.data[off + 1] = b;
131                    self.data[off + 2] = g;
132                    self.data[off + 3] = r;
133                }
134            }
135            PixelFormat::Abgr8888 => {
136                if off + 3 < self.data.len() {
137                    self.data[off] = a;
138                    self.data[off + 1] = b;
139                    self.data[off + 2] = g;
140                    self.data[off + 3] = r;
141                }
142            }
143            PixelFormat::Gray8 => {
144                if off < self.data.len() {
145                    // ITU-R BT.601 luma (integer approximation)
146                    self.data[off] = ((r as u32 * 77 + g as u32 * 150 + b as u32 * 29) >> 8) as u8;
147                }
148            }
149        }
150    }
151
152    /// Read a pixel at (x, y) as (R, G, B, A).
153    /// Returns (0,0,0,0) for out-of-bounds coordinates.
154    pub(crate) fn get_pixel(&self, x: u32, y: u32) -> (u8, u8, u8, u8) {
155        if x >= self.width || y >= self.height {
156            return (0, 0, 0, 0);
157        }
158        let off = self.pixel_offset(x, y);
159        match self.format {
160            PixelFormat::Xrgb8888 => {
161                if off + 3 < self.data.len() {
162                    (self.data[off + 2], self.data[off + 1], self.data[off], 0xFF)
163                } else {
164                    (0, 0, 0, 0)
165                }
166            }
167            PixelFormat::Argb8888 => {
168                if off + 3 < self.data.len() {
169                    (
170                        self.data[off + 2],
171                        self.data[off + 1],
172                        self.data[off],
173                        self.data[off + 3],
174                    )
175                } else {
176                    (0, 0, 0, 0)
177                }
178            }
179            PixelFormat::Rgb888 => {
180                if off + 2 < self.data.len() {
181                    (self.data[off], self.data[off + 1], self.data[off + 2], 0xFF)
182                } else {
183                    (0, 0, 0, 0)
184                }
185            }
186            PixelFormat::Rgb565 => {
187                if off + 1 < self.data.len() {
188                    let val = (self.data[off] as u16) | ((self.data[off + 1] as u16) << 8);
189                    let r = ((val >> 11) & 0x1F) as u8;
190                    let g = ((val >> 5) & 0x3F) as u8;
191                    let b = (val & 0x1F) as u8;
192                    // Expand to 8-bit
193                    (
194                        (r << 3) | (r >> 2),
195                        (g << 2) | (g >> 4),
196                        (b << 3) | (b >> 2),
197                        0xFF,
198                    )
199                } else {
200                    (0, 0, 0, 0)
201                }
202            }
203            PixelFormat::Bgr888 => {
204                if off + 2 < self.data.len() {
205                    (self.data[off + 2], self.data[off + 1], self.data[off], 0xFF)
206                } else {
207                    (0, 0, 0, 0)
208                }
209            }
210            PixelFormat::Bgrx8888 => {
211                if off + 3 < self.data.len() {
212                    (self.data[off + 2], self.data[off + 1], self.data[off], 0xFF)
213                } else {
214                    (0, 0, 0, 0)
215                }
216            }
217            PixelFormat::Rgba8888 => {
218                if off + 3 < self.data.len() {
219                    (
220                        self.data[off],
221                        self.data[off + 1],
222                        self.data[off + 2],
223                        self.data[off + 3],
224                    )
225                } else {
226                    (0, 0, 0, 0)
227                }
228            }
229            PixelFormat::Bgra8888 => {
230                if off + 3 < self.data.len() {
231                    (
232                        self.data[off + 2],
233                        self.data[off + 1],
234                        self.data[off],
235                        self.data[off + 3],
236                    )
237                } else {
238                    (0, 0, 0, 0)
239                }
240            }
241            PixelFormat::Xbgr8888 => {
242                if off + 3 < self.data.len() {
243                    (
244                        self.data[off + 3],
245                        self.data[off + 2],
246                        self.data[off + 1],
247                        0xFF,
248                    )
249                } else {
250                    (0, 0, 0, 0)
251                }
252            }
253            PixelFormat::Abgr8888 => {
254                if off + 3 < self.data.len() {
255                    (
256                        self.data[off + 3],
257                        self.data[off + 2],
258                        self.data[off + 1],
259                        self.data[off],
260                    )
261                } else {
262                    (0, 0, 0, 0)
263                }
264            }
265            PixelFormat::Gray8 => {
266                if off < self.data.len() {
267                    let v = self.data[off];
268                    (v, v, v, 0xFF)
269                } else {
270                    (0, 0, 0, 0)
271                }
272            }
273        }
274    }
275
276    /// Fill the entire frame with a solid color.
277    pub(crate) fn clear(&mut self, r: u8, g: u8, b: u8) {
278        for y in 0..self.height {
279            for x in 0..self.width {
280                self.set_pixel(x, y, r, g, b, 0xFF);
281            }
282        }
283    }
284
285    /// Immutable access to the raw pixel data.
286    pub(crate) fn data(&self) -> &[u8] {
287        &self.data
288    }
289
290    /// Mutable access to the raw pixel data.
291    pub(crate) fn data_mut(&mut self) -> &mut [u8] {
292        &mut self.data
293    }
294}
295
296// ---------------------------------------------------------------------------
297// Video info
298// ---------------------------------------------------------------------------
299
300/// Metadata describing a video stream.
301#[derive(Debug, Clone, Copy)]
302pub(crate) struct VideoInfo {
303    pub(crate) width: u32,
304    pub(crate) height: u32,
305    pub(crate) format: PixelFormat,
306    /// Frame-rate numerator (e.g. 30 for 30 fps).
307    pub(crate) frame_rate_num: u32,
308    /// Frame-rate denominator (e.g. 1 for 30 fps).
309    pub(crate) frame_rate_den: u32,
310}
311
312// ---------------------------------------------------------------------------
313// Subsystem init
314// ---------------------------------------------------------------------------
315
316static INITIALIZED: AtomicBool = AtomicBool::new(false);
317
318/// Initialize the video subsystem.
319pub(crate) fn init() -> Result<(), KernelError> {
320    if INITIALIZED.load(Ordering::Acquire) {
321        return Ok(());
322    }
323
324    println!("[VIDEO] Initializing video subsystem...");
325
326    player::init()?;
327
328    INITIALIZED.store(true, Ordering::Release);
329    println!("[VIDEO] Video subsystem initialized");
330    Ok(())
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[test]
338    fn test_pixel_format_bpp() {
339        assert_eq!(PixelFormat::Xrgb8888.bytes_per_pixel(), 4);
340        assert_eq!(PixelFormat::Argb8888.bytes_per_pixel(), 4);
341        assert_eq!(PixelFormat::Rgb888.bytes_per_pixel(), 3);
342        assert_eq!(PixelFormat::Rgb565.bytes_per_pixel(), 2);
343        assert_eq!(PixelFormat::Gray8.bytes_per_pixel(), 1);
344        assert_eq!(PixelFormat::Bgr888.bytes_per_pixel(), 3);
345        assert_eq!(PixelFormat::Bgrx8888.bytes_per_pixel(), 4);
346    }
347
348    #[test]
349    fn test_pixel_format_alpha() {
350        assert!(!PixelFormat::Xrgb8888.has_alpha());
351        assert!(PixelFormat::Argb8888.has_alpha());
352        assert!(!PixelFormat::Rgb888.has_alpha());
353        assert!(!PixelFormat::Gray8.has_alpha());
354    }
355
356    #[test]
357    fn test_video_frame_new() {
358        let f = VideoFrame::new(320, 240, PixelFormat::Xrgb8888);
359        assert_eq!(f.width, 320);
360        assert_eq!(f.height, 240);
361        assert_eq!(f.stride, 320 * 4);
362        assert_eq!(f.data.len(), 320 * 240 * 4);
363    }
364
365    #[test]
366    fn test_video_frame_set_get_pixel() {
367        let mut f = VideoFrame::new(4, 4, PixelFormat::Argb8888);
368        f.set_pixel(1, 2, 0xAA, 0xBB, 0xCC, 0xDD);
369        let (r, g, b, a) = f.get_pixel(1, 2);
370        assert_eq!((r, g, b, a), (0xAA, 0xBB, 0xCC, 0xDD));
371    }
372
373    #[test]
374    fn test_video_frame_out_of_bounds() {
375        let mut f = VideoFrame::new(2, 2, PixelFormat::Rgb888);
376        // Should not panic
377        f.set_pixel(10, 10, 255, 0, 0, 255);
378        let px = f.get_pixel(10, 10);
379        assert_eq!(px, (0, 0, 0, 0));
380    }
381}