Rendering - Part 1
The minifb crate uses a 1D array but to make it easier for us to understand the output window we will add an RGBA pixel buffer which is a 2D array. We will also set up an RGBA struct to make it easier to work with the pixel colour values.
To make the program a little more interactive we will let the user cycle through rendering a square in Red, Green and Blue. We will also make is so when the ESC key is pressed the program exits.
The Rust code
// Add an RGBA pixel buffer to minifb // by Rich of maths.earth 20250223 extern crate minifb; use minifb::{Key, Window, WindowOptions}; // Define the window dimensions. const WIDTH: usize = 800; const HEIGHT: usize = 600; /// A struct to represent an RGBA pixel. #[derive(Clone, Copy)] struct Pixel { r: u8, g: u8, b: u8, a: u8, } impl Pixel { /// Create a new Pixel with the given red, green, blue and alpha values. fn new(r: u8, g: u8, b: u8, a: u8) -> Self { Self { r, g, b, a } } /// Convert this pixel into a 32-bit colour in 0xAARRGGBB format. fn to_u32(self) -> u32 { ((self.a as u32) << 24) | ((self.r as u32) << 16) | ((self.g as u32) << 8) | (self.b as u32) } } fn main() { // Create a new window. let mut window = Window::new( "Lesson One: 2D Pixel Buffer Rendering with minifb (Using Pixel struct)", WIDTH, HEIGHT, WindowOptions::default(), ) .expect("Unable to create window"); // Create a 2D pixel buffer initialised to black. // This buffer is a vector of rows, each row being a vector of Pixel structs. let mut pixel_buffer: Vec> = vec![vec![Pixel::new(0, 0, 0, 255); WIDTH]; HEIGHT]; // Define the dimensions of the square. let square_width = 200; let square_height = 200; // Calculate the top-left corner so that the square is centred. let square_x = (WIDTH - square_width) / 2; let square_y = (HEIGHT - square_height) / 2; // Set the initial square colour to red. let mut square_color = Pixel::new(255, 0, 0, 255); // Colour state: 0 = red, 1 = green, 2 = blue. let mut color_state: u32 = 0; // Variable to detect key transitions for the space bar. let mut prev_space_down = false; // Main loop. while window.is_open() && !window.is_key_down(Key::Escape) { // Check if the Space key is currently pressed. let current_space = window.is_key_down(Key::Space); // If space was just pressed (transition from not pressed to pressed) if current_space && !prev_space_down { // Cycle the square colour. color_state = (color_state + 1) % 3; square_color = match color_state { 0 => Pixel::new(255, 0, 0, 255), // Red 1 => Pixel::new(0, 255, 0, 255), // Green 2 => Pixel::new(0, 0, 255, 255), // Blue _ => Pixel::new(255, 0, 0, 255), // Fallback to red }; } // Store the current state for debouncing. prev_space_down = current_space; // Clear the pixel buffer by filling it with black. for row in pixel_buffer.iter_mut() { for pixel in row.iter_mut() { *pixel = Pixel::new(0, 0, 0, 255); } } // Draw the square into the pixel buffer using the current square colour. for y in square_y..(square_y + square_height) { for x in square_x..(square_x + square_width) { pixel_buffer[y][x] = square_color; } } // Convert the 2D pixel buffer to a 1D u32 buffer. // Each pixel is converted into 0xAARRGGBB format. let mut buffer: Vec = Vec::with_capacity(WIDTH * HEIGHT); for row in &pixel_buffer { for &pixel in row { buffer.push(pixel.to_u32()); } } // Update the window with the new pixel buffer. // minifb manages double buffering internally. window .update_with_buffer(&buffer, WIDTH, HEIGHT) .expect("Failed to update window"); } }
Cargo.toml
[package] name = "minifb-rgb" version = "0.1.0" edition = "2024" [dependencies] minifb = "0.28.0"
Mission Accomplished
And there we have it, we can render a 2d square which cycles through Red, Green and Blue.