#![no_std]
#![doc = include_str!("../README.md")]
const BMP_HEADER_SIZE: usize = 54;
pub const fn buffer_length(width: usize, height: usize) -> usize {
let row_stride = (width * 3).next_multiple_of(4);
let pixel_data_size = height * row_stride;
BMP_HEADER_SIZE + pixel_data_size
}
pub fn write_bmp(buffer: &mut [u8], width: usize, height: usize, pixels: &[u8]) -> Result<usize, Error> {
let row_stride = (width * 3).next_multiple_of(4);
let pixel_data_size = height * row_stride;
let file_length = BMP_HEADER_SIZE + pixel_data_size;
if (i32::MAX as usize) < width {
return Err(Error::WidthTooLarge { max: i32::MAX as usize, was: width });
}
if (i32::MAX as usize) < height {
return Err(Error::HeightTooLarge { max: i32::MAX as usize, was: height });
}
if (u32::MAX as usize) < file_length {
return Err(Error::FileLengthTooLong { max: u32::MAX as usize, would_be: file_length });
}
if pixels.len() != width * height * 3 {
return Err(Error::BadPixelDataLength { expected: width * height * 3, was: pixels.len() });
}
if buffer.len() < file_length {
return Err(Error::BufferTooSmall { required: file_length, was: buffer.len() });
}
buffer[0..2].copy_from_slice(b"BM");
buffer[2..][..4].copy_from_slice(&(file_length as u32).to_le_bytes());
buffer[6..][..4].fill(0);
buffer[10..][..4].copy_from_slice(&54u32.to_le_bytes());
buffer[14..][..4].copy_from_slice(&40u32.to_le_bytes());
buffer[18..][..4].copy_from_slice(&(width as i32).to_le_bytes());
buffer[22..][..4].copy_from_slice(&(height as i32).to_le_bytes());
buffer[26..][..2].copy_from_slice(&1u16.to_le_bytes());
buffer[28..][..2].copy_from_slice(&24u16.to_le_bytes());
buffer[30..][..4].copy_from_slice(&0u32.to_le_bytes());
buffer[34..][..4].copy_from_slice(&(pixel_data_size as u32).to_le_bytes());
buffer[38..][..4].copy_from_slice(&1000u32.to_le_bytes());
buffer[42..][..4].copy_from_slice(&1000u32.to_le_bytes());
buffer[46..][..4].copy_from_slice(&0u32.to_le_bytes());
buffer[50..][..4].copy_from_slice(&0u32.to_le_bytes());
for row in 0..height {
let dst_begin = 54 + row_stride * row;
let dst_end = dst_begin + width * 3;
let src_begin = (height - row - 1) * width * 3;
let src_end = src_begin + width * 3;
buffer[dst_begin..dst_end].copy_from_slice(&pixels[src_begin..src_end]);
}
Ok(file_length)
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Error {
BadPixelDataLength { expected: usize, was: usize },
FileLengthTooLong { max: usize, would_be: usize },
BufferTooSmall { required: usize, was: usize },
WidthTooLarge { max: usize, was: usize },
HeightTooLarge { max: usize, was: usize },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bad_pixel_data_length() {
const WIDTH: usize = 100;
const HEIGHT: usize = 100;
const PIXEL_LENGTH: usize = WIDTH * HEIGHT * 3;
let mut buffer = [0u8; buffer_length(WIDTH, HEIGHT)];
let pixels = [0u8; PIXEL_LENGTH - 1];
let result = write_bmp(&mut buffer, WIDTH, HEIGHT, &pixels);
assert_eq!(result, Err(Error::BadPixelDataLength { expected: PIXEL_LENGTH, was: PIXEL_LENGTH - 1 }));
let mut buffer = [0u8; buffer_length(WIDTH, HEIGHT)];
let pixels = [0u8; PIXEL_LENGTH + 1];
let result = write_bmp(&mut buffer, WIDTH, HEIGHT, &pixels);
assert_eq!(result, Err(Error::BadPixelDataLength { expected: PIXEL_LENGTH, was: PIXEL_LENGTH + 1 }));
}
#[test]
fn bad_width() {
let pixels = [0u8; 100 * 100 * 3];
let mut buffer = [0u8; buffer_length(100, 100)];
let width = i32::MAX as usize + 1;
let result = write_bmp(&mut buffer, width, 100, &pixels);
match result {
Err(Error::WidthTooLarge { was, .. }) if was == width => {}
otherwise => panic!("Width error is incorrect. {:?}", otherwise),
}
}
#[test]
fn bad_height() {
let pixels = [0u8; 100 * 100 * 3];
let mut buffer = [0u8; buffer_length(100, 100)];
let height = i32::MAX as usize + 1;
let result = write_bmp(&mut buffer, 100, height, &pixels);
match result {
Err(Error::HeightTooLarge { was, .. }) if was == height => {}
otherwise => panic!("Height error is incorrect. {:?}", otherwise),
}
}
#[test]
fn bad_file_length() {
let mut buffer = [0u8; 100];
let pixels = [0u8; 100];
let result = write_bmp(&mut buffer, 65535, 65535, &pixels);
match result {
Err(Error::FileLengthTooLong { .. }) => {}
_ => assert!(false),
}
}
#[test]
fn bad_buffer_length() {
let mut buffer = [0u8; 100];
let pixels = [0u8; 100 * 100 * 3];
let result = write_bmp(&mut buffer, 100, 100, &pixels);
match result {
Err(Error::BufferTooSmall { .. }) => {}
_ => assert!(false),
}
}
}