softbuffer_rgb/
lib.rs

1//! **`softbuffer-rgb` is a wrapper around [`softbuffer`](https://docs.rs/softbuffer/latest/softbuffer/) that makes it easier to modify a raw pixel buffer.**
2//! 
3//! Instead of doing this:
4//! 
5//! ```ignore
6//! buffer.buffer_mut()[y * width + x] = u32::from_le_bytes([0, 200, 70, 10]);
7//! ```
8//! 
9//! ...you can now do this:
10//! 
11//! ```ignore
12//! buffer.pixels[y][x] = [0, 200, 70, 10];
13//! ```
14//!
15//! ## Problem
16//!
17//! `softbuffer` stores pixel data in a u32 buffer where each u32 is an "0RGB" color.
18//! The first byte is always zero, the second byte is red, the third byte is green, and the fourth byte is blue.
19//!
20//! It's intuitive to store colors as arrays, like this: 
21//! 
22//!```rust
23//!let color = [0, 200, 70, 10];
24//!```
25//! But in `softbuffer`, colors need to be u32s:
26//! 
27//!```rust
28//!let color = u32::from_le_bytes([0, 200, 70, 10]);
29//!```
30//!
31//! Additionally, `softbuffer` buffers are one-dimensional. 
32//! Typically, you'll want to program in a 2D (x, y) coordinate space, meaning that you'll have to convert 2D (x, y) coordinates to 1D indices. 
33//! It's a cheap operation but if you have to do it for many pixels, per frame, the performance cost can add up!
34//!
35//! ## Solution
36//!
37//! `softbuffer-rgb` uses a tiny bit of unsafe code to rearrange the raw buffer data into a 3D array: `(width, height, 0RGB)`.
38//! Modifying this `pixels` array will modify the the underlying u32 buffer array, and vice versa.
39//!
40//! As a result:
41//!
42//! - `softbuffer-rgb` can be easier to use than `softbuffer`.
43//! - `softbuffer-rgb` can be slightly faster because you don't need to convert to u32s and you don't need to convert (x, y) coordinates to indices.
44//!
45//! ## Caveat
46//!
47//! `softbuffer-rgb` relies on generic constants to define the size of `pixels`, meaning that the buffer size must be known at compile-time.
48//!
49//! ## Example
50//!
51//! ```rust
52//!use softbuffer::{Context, Surface};
53//!use std::num::NonZeroU32;
54//!use winit::application::ApplicationHandler;
55//!use winit::dpi::LogicalSize;
56//!use winit::event::{StartCause, WindowEvent};
57//!use winit::event_loop::{ActiveEventLoop, EventLoop};
58//!use winit::window::{Window, WindowAttributes, WindowId};
59//!
60//!use softbuffer_rgb::RgbBuffer;
61//!
62//!const X: usize = 400;
63//!const Y: usize = 300;
64//!
65//!fn main() {
66//!    let mut app = App::default();
67//!    let event_loop = EventLoop::new().unwrap();
68//!    event_loop.run_app(&mut app).unwrap();
69//!}
70//!
71//!#[derive(Default)]
72//!struct App {
73//!    window: Option<Window>,
74//!}
75//!
76//!impl ApplicationHandler for App {
77//!    fn resumed(&mut self, _: &ActiveEventLoop) {}
78//!
79//!    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
80//!        if let StartCause::Init = cause {
81//!            let window_attributes =
82//!                WindowAttributes::default().with_inner_size(LogicalSize::new(X as u32, Y as u32));
83//!            // Create the window.
84//!            self.window = Some(event_loop.create_window(window_attributes).unwrap());
85//!            // Get the window.
86//!            let window = self.window.as_ref().unwrap();
87//!            let context = Context::new(window).unwrap();
88//!            let mut surface = Surface::new(&context, &window).unwrap();
89//!            surface
90//!                .resize(
91//!                    NonZeroU32::new(X as u32).unwrap(),
92//!                    NonZeroU32::new(Y as u32).unwrap(),
93//!                )
94//!                .unwrap();
95//!            let mut rgb_buffer =
96//!                RgbBuffer::<X, Y, _, _>::from_softbuffer(surface.buffer_mut().unwrap()).unwrap();
97//!            let x = 12;
98//!            let y = 23;
99//!            rgb_buffer.pixels[y][x] = [0, 200, 100, 70];
100//!            event_loop.exit();
101//!        }
102//!    }
103//!
104//!    fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, _: WindowEvent) {}
105//!}
106//!```
107
108use std::{fmt, slice};
109
110use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
111pub use softbuffer;
112use softbuffer::Buffer;
113
114#[derive(Debug, Clone)]
115pub struct SizeError {
116    pub x: usize,
117    pub y: usize,
118}
119
120impl fmt::Display for SizeError {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(f, "Invalid size: ({0}, {1})", self.x, self.y)
123    }
124}
125
126pub type Color = [u8; 4];
127
128/// An `RgbBuffer` contains a softbuffer `buffer` and `pixels`, a mutable slice of the same data.
129/// `buffer` and `pixels` reference the same underlying data.
130/// Modifying the elements of one will affect the values of the other.
131///
132/// Color data is represented as a 4-element array where the first element is always 0.
133/// This will align the color data correctly for `softbuffer`.
134///
135/// Generic parameters:
136///
137/// - `X` and `Y` are the width and height of the surface. This should always match the actual dimensions of the underlying `Surface`.
138/// - `D` and `W` are generics that should match those of the `Buffer<D, W>`.
139pub struct RgbBuffer<'s, const X: usize, const Y: usize, D: HasDisplayHandle, W: HasWindowHandle> {
140    /// The "raw" softbuffer `Buffer`.
141    pub buffer: Buffer<'s, D, W>,
142    /// The "raw" RGB pixel data as a 3D array where the axes are: `Y`, `X`, and 4 (XRGB).
143    /// Note that the order is: `Y, X`. Therefore, to get the pixel at `x=4, y=5`: `self.pixels[y][x]`.
144    /// The color has four elements. The first element should always be 0, and the other three are R, G, and B: `self.pixels[y][x] = [0, 200, 160, 30];`
145    /// This will align the color data correctly for `softbuffer`.
146    ///
147    /// Do not reassign the entire array!
148    /// It's a slice of `self.buffer`.
149    /// If it's something else, something bad might happen!
150    pub pixels: &'s mut [[Color; X]],
151}
152
153impl<'s, const X: usize, const Y: usize, D: HasDisplayHandle, W: HasWindowHandle>
154    RgbBuffer<'s, X, Y, D, W>
155{
156    /// Convert a `Buffer` into an `RgbBuffer`. This consumes `buffer` and returns an `RgbBuffer`.
157    /// This returns an `Error` if `X * Y != buffer.len()` (i.e. if the dimensions of the `RgbBuffer` are invalid).
158    pub fn from_softbuffer(mut buffer: Buffer<'s, D, W>) -> Result<Self, SizeError> {
159        // Test whether the dimensions are valid.
160        if X * Y != buffer.len() {
161            Err(SizeError { x: X, y: Y })
162        } else {
163            // Convert the raw buffer to an array of rows.
164            let ptr = buffer.as_mut_ptr() as *mut [Color; X];
165            // Get the 3D pixel array.
166            let pixels = unsafe { slice::from_raw_parts_mut(ptr, Y) };
167            Ok(RgbBuffer { buffer, pixels })
168        }
169    }
170
171    /// Fill the buffer with an `[0, r, g, b]` color.
172    pub fn fill(&mut self, color: Color) {
173        self.pixels.fill([color; X]);
174    }
175
176    /// Set the color of multiple pixels.
177    ///
178    /// - `positions`: A slice of `[x, y]` positions.
179    /// - `color`: The `[0, r, g, b]` color.
180    ///
181    /// Panics if any position in `positions` is out of bounds.
182    pub fn set_pixels(&mut self, positions: &[[usize; 2]], color: Color) {
183        // Copy the color into each position.
184        for position in positions {
185            self.pixels[position[1]][position[0]] = color;
186        }
187    }
188
189    /// Fill a rectangle with a color.
190    ///
191    /// - `x` and `y` are the coordinates of the top-left pixel.
192    /// - `w` and `h` are the width and height of the rectangle.
193    /// - `color` is the `[0, r, g, b]` color.
194    ///
195    /// Panics if the top-left or bottom-right positions are out of bounds.
196    pub fn fill_rectangle(&mut self, x: usize, y: usize, w: usize, h: usize, color: Color) {
197        // Create a row of colors and get a slice of it.
198        let colors = &[color; Y][x..x + w];
199        // Fill the rectangle.
200        self.pixels[y..y + h]
201            .iter_mut()
202            .for_each(|cols| cols[x..x + w].copy_from_slice(colors));
203    }
204}