Skip to main content

nes_sim/
video.rs

1use crate::api::VideoFrame;
2
3const NES_RGB_PALETTE: [[u8; 3]; 64] = [
4    [84, 84, 84],
5    [0, 30, 116],
6    [8, 16, 144],
7    [48, 0, 136],
8    [68, 0, 100],
9    [92, 0, 48],
10    [84, 4, 0],
11    [60, 24, 0],
12    [32, 42, 0],
13    [8, 58, 0],
14    [0, 64, 0],
15    [0, 60, 0],
16    [0, 50, 60],
17    [0, 0, 0],
18    [0, 0, 0],
19    [0, 0, 0],
20    [152, 150, 152],
21    [8, 76, 196],
22    [48, 50, 236],
23    [92, 30, 228],
24    [136, 20, 176],
25    [160, 20, 100],
26    [152, 34, 32],
27    [120, 60, 0],
28    [84, 90, 0],
29    [40, 114, 0],
30    [8, 124, 0],
31    [0, 118, 40],
32    [0, 102, 120],
33    [0, 0, 0],
34    [0, 0, 0],
35    [0, 0, 0],
36    [236, 238, 236],
37    [76, 154, 236],
38    [120, 124, 236],
39    [176, 98, 236],
40    [228, 84, 236],
41    [236, 88, 180],
42    [236, 106, 100],
43    [212, 136, 32],
44    [160, 170, 0],
45    [116, 196, 0],
46    [76, 208, 32],
47    [56, 204, 108],
48    [56, 180, 204],
49    [60, 60, 60],
50    [0, 0, 0],
51    [0, 0, 0],
52    [236, 238, 236],
53    [168, 204, 236],
54    [188, 188, 236],
55    [212, 178, 236],
56    [236, 174, 236],
57    [236, 174, 212],
58    [236, 180, 176],
59    [228, 196, 144],
60    [204, 210, 120],
61    [180, 222, 120],
62    [168, 226, 144],
63    [152, 226, 180],
64    [160, 214, 228],
65    [160, 162, 160],
66    [0, 0, 0],
67    [0, 0, 0],
68];
69
70pub fn palette_index_to_rgb(index: u8) -> [u8; 3] {
71    NES_RGB_PALETTE[(index & 0x3F) as usize]
72}
73
74pub fn frame_to_rgb(frame: VideoFrame<'_>) -> Vec<u8> {
75    let mut rgb = Vec::with_capacity(frame.pixels.len() * 3);
76    for &pixel in frame.pixels {
77        rgb.extend_from_slice(&palette_index_to_rgb(pixel));
78    }
79    rgb
80}
81
82pub fn frame_to_argb32(frame: VideoFrame<'_>) -> Vec<u32> {
83    let mut argb = Vec::with_capacity(frame.pixels.len());
84    for &pixel in frame.pixels {
85        let [r, g, b] = palette_index_to_rgb(pixel);
86        argb.push((0xFF_u32 << 24) | (u32::from(r) << 16) | (u32::from(g) << 8) | u32::from(b));
87    }
88    argb
89}
90
91/// 预分配的视频缓冲区,避免每帧堆分配
92pub struct VideoBuffer {
93    buffer: Vec<u32>,
94}
95
96impl VideoBuffer {
97    pub fn new(capacity: usize) -> Self {
98        Self {
99            buffer: vec![0; capacity],
100        }
101    }
102
103    pub fn as_mut_slice(&mut self) -> &mut [u32] {
104        &mut self.buffer
105    }
106
107    pub fn as_slice(&self) -> &[u32] {
108        &self.buffer
109    }
110}
111
112/// 将帧数据转换为 ARGB32 格式,写入预分配的缓冲区
113pub fn frame_to_argb32_into(frame: VideoFrame<'_>, output: &mut [u32]) {
114    for (dst, &pixel) in output.iter_mut().zip(frame.pixels) {
115        let [r, g, b] = palette_index_to_rgb(pixel);
116        *dst = (0xFF_u32 << 24) | (u32::from(r) << 16) | (u32::from(g) << 8) | u32::from(b);
117    }
118}
119
120/// 将帧数据转换为 RGBA 字节格式(用于 Tauri IPC)
121pub fn frame_to_rgba(frame: VideoFrame<'_>) -> Vec<u8> {
122    let mut rgba = Vec::with_capacity(frame.pixels.len() * 4);
123    for &pixel in frame.pixels {
124        let [r, g, b] = palette_index_to_rgb(pixel);
125        rgba.push(r);
126        rgba.push(g);
127        rgba.push(b);
128        rgba.push(255);
129    }
130    rgba
131}