Add an RGBA pixel buffer to minifb

Part 1 -

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.