nesbox_utils/
pixels.rs

1use bevy::prelude::*;
2
3#[derive(Component, Copy, Clone, Debug)]
4pub struct Color(pub u8, pub u8, pub u8, pub u8);
5
6impl Default for Color {
7    fn default() -> Self {
8        Self(0x22, 0x22, 0x22, 0xff)
9    }
10}
11
12#[derive(Resource, Default)]
13pub struct PixelsResource {
14    pub width: u32,
15    pub height: u32,
16    buffer: Vec<u8>,
17    default_color: Color,
18}
19
20impl PixelsResource {
21    pub fn new(width: u32, height: u32) -> Self {
22        PixelsResource {
23            width,
24            height,
25            buffer: vec![0u8; (width * height * 4) as usize],
26            ..default()
27        }
28    }
29
30    pub fn frame(&self) -> &[u8] {
31        &self.buffer
32    }
33
34    pub fn get_frame_mut(&mut self) -> &mut [u8] {
35        &mut self.buffer
36    }
37
38    pub fn fill(&mut self, color: &Color) {
39        self.fill_rect(
40            0,
41            0,
42            self.width as i32,
43            self.height as i32,
44            Some(color),
45            false,
46        );
47    }
48
49    pub fn fill_rect(
50        &mut self,
51        x: i32,
52        y: i32,
53        w: i32,
54        h: i32,
55        color_option: Option<&Color>,
56        blend: bool,
57    ) {
58        let width = self.width as i32;
59        let height = self.height as i32;
60
61        let x_min = 0.max(x);
62        let x_max = width.min(x + w);
63        if x_min > x_max {
64            return;
65        }
66        let y_min = 0.max(y);
67        let y_max = height.min(y + h);
68        if y_min > y_max {
69            return;
70        }
71
72        let color = color_option.unwrap_or(&self.default_color);
73        if blend == true && color.3 != 0xff {
74            let c = &[color.0, color.1, color.2, color.3];
75            for y in y_min..y_max {
76                for x in x_min..x_max {
77                    self.mix_color((y * width + x) as usize * 4, c, c[3]);
78                }
79            }
80            return;
81        }
82
83        let x_offset = x_min as usize * 4;
84        let x_max_offset = x_max as usize * 4;
85        let line_bytes = width as usize * 4;
86        let render_width = (x_max - x_min) as usize;
87        let object_row = &[color.0, color.1, color.2, color.3].repeat(render_width);
88
89        for y in y_min..y_max {
90            let y_offset = y as usize * line_bytes;
91            self.buffer[(x_offset + y_offset)..(x_max_offset + y_offset)]
92                .copy_from_slice(object_row);
93        }
94    }
95
96    pub fn mix_color(&mut self, index: usize, color: &[u8], fg_a: u8) {
97        if color[3] == 0 {
98            return;
99        }
100        let fg_r = color[0] as u32;
101        let fg_g = color[1] as u32;
102        let fg_b = color[2] as u32;
103        let fg_a = fg_a as u32;
104
105        let g_idx = index + 1;
106        let b_idx = index + 2;
107        let a_idx = index + 3;
108
109        let buffer = &mut self.buffer;
110
111        let r = buffer[index] as u32;
112        let g = buffer[g_idx] as u32;
113        let b = buffer[b_idx] as u32;
114        let bg_a = buffer[a_idx] as u32;
115
116        let ra = 255 - (255 - fg_a) * (255 - bg_a) / 255;
117        let rr = (fg_r * fg_a) / ra + (r * bg_a * (255 - fg_a)) / ra / 255;
118        let rg = (fg_g * fg_a) / ra + (g * bg_a * (255 - fg_a)) / ra / 255;
119        let rb = (fg_b * fg_a) / ra + (b * bg_a * (255 - fg_a)) / ra / 255;
120        buffer[index] = rr as u8;
121        buffer[g_idx] = rg as u8;
122        buffer[b_idx] = rb as u8;
123        buffer[a_idx] = ra as u8;
124    }
125
126    pub fn mix_rect(&mut self, x: i32, y: i32, buffer: &[u8], width: u32, color: Option<&Color>) {
127        let color = color.map(|c| [c.0, c.1, c.2, c.3]);
128        let w = width as i32;
129        let h = buffer.len() as i32 / w / 4;
130
131        let width = self.width as i32;
132        let height = self.height as i32;
133
134        let x_min = 0.max(x);
135        let x_max = width.min(x + w);
136        if x_min > x_max {
137            return;
138        }
139        let y_min = 0.max(y);
140        let y_max = height.min(y + h);
141        if y_min > y_max {
142            return;
143        }
144
145        for yy in y_min..y_max {
146            let y_offset = w * (yy - y) * 4;
147            for xx in x_min..x_max {
148                let offset = (y_offset + (xx - x) * 4) as usize;
149                self.mix_color(
150                    (yy * width + xx) as usize * 4,
151                    &color.unwrap_or(buffer[offset..(offset + 4)].try_into().unwrap()),
152                    buffer[offset + 3],
153                );
154            }
155        }
156    }
157}