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}