1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! **`softbuffer-rgb` is a wrapper around [`softbuffer`](https://docs.rs/softbuffer/latest/softbuffer/) that makes it easier to modify a raw pixel buffer.**
//! 
//! Instead of doing this:
//! 
//! ```ignore
//! buffer.buffer_mut()[y * width + x] = u32::from_le_bytes([0, 200, 70, 10]);
//! ```
//! 
//! ...you can now do this:
//! 
//! ```ignore
//! buffer.pixels[y][x] = [0, 200, 70, 10];
//! ```
//!
//! ## Problem
//!
//! `softbuffer` stores pixel data in a u32 buffer where each u32 is an "0RGB" color.
//! The first byte is always zero, the second byte is red, the third byte is green, and the fourth byte is blue.
//!
//! It's intuitive to store colors as arrays, like this: 
//! 
//!```rust
//!let color = [0, 200, 70, 10];
//!```
//! But in `softbuffer`, colors need to be u32s:
//! 
//!```rust
//!let color = u32::from_le_bytes([0, 200, 70, 10]);
//!```
//!
//! Additionally, `softbuffer` buffers are one-dimensional. 
//! 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. 
//! It's a cheap operation but if you have to do it for many pixels, per frame, the performance cost can add up!
//!
//! ## Solution
//!
//! `softbuffer-rgb` uses a tiny bit of unsafe code to rearrange the raw buffer data into a 3D array: `(width, height, 0RGB)`.
//! Modifying this `pixels` array will modify the the underlying u32 buffer array, and vice versa.
//!
//! As a result:
//!
//! - `softbuffer-rgb` can be easier to use than `softbuffer`.
//! - `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.
//!
//! ## Caveat
//!
//! `softbuffer-rgb` relies on generic constants to define the size of `pixels`, meaning that the buffer size must be known at compile-time.
//!
//! ## Example
//!
//! ```rust
//!use softbuffer::{Context, Surface};
//!use std::num::NonZeroU32;
//!use winit::application::ApplicationHandler;
//!use winit::dpi::LogicalSize;
//!use winit::event::{StartCause, WindowEvent};
//!use winit::event_loop::{ActiveEventLoop, EventLoop};
//!use winit::window::{Window, WindowAttributes, WindowId};
//!
//!use softbuffer_rgb::RgbBuffer;
//!
//!const X: usize = 400;
//!const Y: usize = 300;
//!
//!fn main() {
//!    let mut app = App::default();
//!    let event_loop = EventLoop::new().unwrap();
//!    event_loop.run_app(&mut app).unwrap();
//!}
//!
//!#[derive(Default)]
//!struct App {
//!    window: Option<Window>,
//!}
//!
//!impl ApplicationHandler for App {
//!    fn resumed(&mut self, _: &ActiveEventLoop) {}
//!
//!    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
//!        if let StartCause::Init = cause {
//!            let window_attributes =
//!                WindowAttributes::default().with_inner_size(LogicalSize::new(X as u32, Y as u32));
//!            // Create the window.
//!            self.window = Some(event_loop.create_window(window_attributes).unwrap());
//!            // Get the window.
//!            let window = self.window.as_ref().unwrap();
//!            let context = Context::new(window).unwrap();
//!            let mut surface = Surface::new(&context, &window).unwrap();
//!            surface
//!                .resize(
//!                    NonZeroU32::new(X as u32).unwrap(),
//!                    NonZeroU32::new(Y as u32).unwrap(),
//!                )
//!                .unwrap();
//!            let mut rgb_buffer =
//!                RgbBuffer::<X, Y, _, _>::from_softbuffer(surface.buffer_mut().unwrap()).unwrap();
//!            let x = 12;
//!            let y = 23;
//!            rgb_buffer.pixels[y][x] = [0, 200, 100, 70];
//!            event_loop.exit();
//!        }
//!    }
//!
//!    fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, _: WindowEvent) {}
//!}
//!```

use std::{fmt, slice};

use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
pub use softbuffer;
use softbuffer::Buffer;

#[derive(Debug, Clone)]
pub struct SizeError {
    pub x: usize,
    pub y: usize,
}

impl fmt::Display for SizeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Invalid size: ({0}, {1})", self.x, self.y)
    }
}

pub type Color = [u8; 4];

/// An `RgbBuffer` contains a softbuffer `buffer` and `pixels`, a mutable slice of the same data.
/// `buffer` and `pixels` reference the same underlying data.
/// Modifying the elements of one will affect the values of the other.
///
/// Color data is represented as a 4-element array where the first element is always 0.
/// This will align the color data correctly for `softbuffer`.
///
/// Generic parameters:
///
/// - `X` and `Y` are the width and height of the surface. This should always match the actual dimensions of the underlying `Surface`.
/// - `D` and `W` are generics that should match those of the `Buffer<D, W>`.
pub struct RgbBuffer<'s, const X: usize, const Y: usize, D: HasDisplayHandle, W: HasWindowHandle> {
    /// The "raw" softbuffer `Buffer`.
    pub buffer: Buffer<'s, D, W>,
    /// The "raw" RGB pixel data as a 3D array where the axes are: `Y`, `X`, and 4 (XRGB).
    /// Note that the order is: `Y, X`. Therefore, to get the pixel at `x=4, y=5`: `self.pixels[y][x]`.
    /// 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];`
    /// This will align the color data correctly for `softbuffer`.
    ///
    /// Do not reassign the entire array!
    /// It's a slice of `self.buffer`.
    /// If it's something else, something bad might happen!
    pub pixels: &'s mut [[Color; X]],
}

impl<'s, const X: usize, const Y: usize, D: HasDisplayHandle, W: HasWindowHandle>
    RgbBuffer<'s, X, Y, D, W>
{
    /// Convert a `Buffer` into an `RgbBuffer`. This consumes `buffer` and returns an `RgbBuffer`.
    /// This returns an `Error` if `X * Y != buffer.len()` (i.e. if the dimensions of the `RgbBuffer` are invalid).
    pub fn from_softbuffer(mut buffer: Buffer<'s, D, W>) -> Result<Self, SizeError> {
        // Test whether the dimensions are valid.
        if X * Y != buffer.len() {
            Err(SizeError { x: X, y: Y })
        } else {
            // Convert the raw buffer to an array of rows.
            let ptr = buffer.as_mut_ptr() as *mut [Color; X];
            // Get the 3D pixel array.
            let pixels = unsafe { slice::from_raw_parts_mut(ptr, Y) };
            Ok(RgbBuffer { buffer, pixels })
        }
    }

    /// Fill the buffer with an `[0, r, g, b]` color.
    pub fn fill(&mut self, color: Color) {
        self.pixels.fill([color; X]);
    }

    /// Set the color of multiple pixels.
    ///
    /// - `positions`: A slice of `[x, y]` positions.
    /// - `color`: The `[0, r, g, b]` color.
    ///
    /// Panics if any position in `positions` is out of bounds.
    pub fn set_pixels(&mut self, positions: &[[usize; 2]], color: Color) {
        // Copy the color into each position.
        for position in positions {
            self.pixels[position[1]][position[0]] = color;
        }
    }

    /// Fill a rectangle with a color.
    ///
    /// - `x` and `y` are the coordinates of the top-left pixel.
    /// - `w` and `h` are the width and height of the rectangle.
    /// - `color` is the `[0, r, g, b]` color.
    ///
    /// Panics if the top-left or bottom-right positions are out of bounds.
    pub fn fill_rectangle(&mut self, x: usize, y: usize, w: usize, h: usize, color: Color) {
        // Create a row of colors and get a slice of it.
        let colors = &[color; Y][x..x + w];
        // Fill the rectangle.
        self.pixels[y..y + h]
            .iter_mut()
            .for_each(|cols| cols[x..x + w].copy_from_slice(colors));
    }
}