simple_bmp/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4
5const BMP_HEADER_SIZE: usize = 54;
6
7
8/// Calculate the length of a BMP file of the given width and height.
9///
10/// The BMP format has a header and stores each row padded to 4 byte alignment,
11/// so the final length will be larger than a simple (WIDTH * HEIGHT * 3) or (WIDTH * HEIGHT * 3 + BMP_HEADER_LENGTH) calculation.
12///
13/// This will be the minimum required size of the buffer passed to write_bmp.
14/// Is a const function so it can be used as the size of a statically sized array.
15///
16/// ```rust
17/// let mut buffer = [0u8; simple_bmp::buffer_length(100, 100)];
18/// ```
19pub const fn buffer_length(width: usize, height: usize) -> usize {
20	let row_stride = (width * 3).next_multiple_of(4);
21	let pixel_data_size = height * row_stride;
22	BMP_HEADER_SIZE + pixel_data_size
23}
24
25/// Write a valid BMP file into the provided buffer, returning the number of bytes written.
26///
27/// The buffer can be longer than required. Extra space will remain untouched.
28/// See documentation on the [Error] enum for possible errors.
29pub fn write_bmp(buffer: &mut [u8], width: usize, height: usize, pixels: &[u8]) -> Result<usize, Error> {
30	let row_stride = (width * 3).next_multiple_of(4);
31	let pixel_data_size = height * row_stride;
32	let file_length = BMP_HEADER_SIZE + pixel_data_size;
33
34	if (i32::MAX as usize) < width {
35		return Err(Error::WidthTooLarge { max: i32::MAX as usize, was: width });
36	}
37
38	if (i32::MAX as usize) < height {
39		return Err(Error::HeightTooLarge { max: i32::MAX as usize, was: height });
40	}
41
42	if (u32::MAX as usize) < file_length {
43		return Err(Error::FileLengthTooLong { max: u32::MAX as usize, would_be: file_length });
44	}
45
46	if pixels.len() != width * height * 3 {
47		return Err(Error::BadPixelDataLength { expected: width * height * 3, was: pixels.len() });
48	}
49
50	if buffer.len() < file_length {
51		return Err(Error::BufferTooSmall { required: file_length, was: buffer.len() });
52	}
53
54	// Header
55	buffer[0..2].copy_from_slice(b"BM");
56	buffer[2..][..4].copy_from_slice(&(file_length as u32).to_le_bytes());
57	buffer[6..][..4].fill(0);
58	buffer[10..][..4].copy_from_slice(&54u32.to_le_bytes());
59
60	// DIB Header
61	buffer[14..][..4].copy_from_slice(&40u32.to_le_bytes());
62	buffer[18..][..4].copy_from_slice(&(width as i32).to_le_bytes());
63	buffer[22..][..4].copy_from_slice(&(height as i32).to_le_bytes());
64	buffer[26..][..2].copy_from_slice(&1u16.to_le_bytes());
65	buffer[28..][..2].copy_from_slice(&24u16.to_le_bytes());
66	buffer[30..][..4].copy_from_slice(&0u32.to_le_bytes());
67	buffer[34..][..4].copy_from_slice(&(pixel_data_size as u32).to_le_bytes());
68	buffer[38..][..4].copy_from_slice(&1000u32.to_le_bytes());
69	buffer[42..][..4].copy_from_slice(&1000u32.to_le_bytes());
70	buffer[46..][..4].copy_from_slice(&0u32.to_le_bytes());
71	buffer[50..][..4].copy_from_slice(&0u32.to_le_bytes());
72
73	// Pixel data
74	for row in 0..height {
75		let dst_begin = 54 + row_stride * row;
76		let dst_end = dst_begin + width * 3;
77		let src_begin = (height - row - 1) * width * 3;
78		let src_end = src_begin + width * 3;
79		buffer[dst_begin..dst_end].copy_from_slice(&pixels[src_begin..src_end]);
80	}
81
82	Ok(file_length)
83}
84
85
86/// Possible errors that can be returned by the write_bmp function
87#[derive(Debug, Copy, Clone, Eq, PartialEq)]
88pub enum Error {
89	/// Returned if the provided pixel data is not exactly (width * height * 3) bytes in length.
90	BadPixelDataLength { expected: usize, was: usize },
91
92	/// The BMP format stores the file length in a u32.
93	/// This error is returned if the provided width & height would produce a file too large to store the length in the BMP header.
94	FileLengthTooLong { max: usize, would_be: usize },
95
96	/// Returned if the given buffer is too small to contain the BMP File.
97	/// You can use [buffer_length] to determine how large the buffer needs to be.
98	BufferTooSmall { required: usize, was: usize },
99
100	/// The BMP file format stores the width in a signed i32.
101	/// This error is returned if the given width doesn't fit in the BMP header.
102	WidthTooLarge { max: usize, was: usize },
103
104	/// The BMP file format stores the height in a signed i32.
105	/// This error is returned if the given height doesn't fit in the BMP header.
106	HeightTooLarge { max: usize, was: usize },
107}
108
109
110#[cfg(test)]
111mod tests {
112	use super::*;
113
114	#[test]
115	fn bad_pixel_data_length() {
116		const WIDTH: usize = 100;
117		const HEIGHT: usize = 100;
118		const PIXEL_LENGTH: usize = WIDTH * HEIGHT * 3;
119
120		let mut buffer = [0u8; buffer_length(WIDTH, HEIGHT)];
121		let pixels = [0u8; PIXEL_LENGTH - 1];
122		let result = write_bmp(&mut buffer, WIDTH, HEIGHT, &pixels);
123		assert_eq!(result, Err(Error::BadPixelDataLength { expected: PIXEL_LENGTH, was: PIXEL_LENGTH - 1 }));
124
125		let mut buffer = [0u8; buffer_length(WIDTH, HEIGHT)];
126		let pixels = [0u8; PIXEL_LENGTH + 1];
127		let result = write_bmp(&mut buffer, WIDTH, HEIGHT, &pixels);
128		assert_eq!(result, Err(Error::BadPixelDataLength { expected: PIXEL_LENGTH, was: PIXEL_LENGTH + 1 }));
129	}
130
131	#[test]
132	fn bad_width() {
133		let pixels = [0u8; 100 * 100 * 3];
134		let mut buffer = [0u8; buffer_length(100, 100)];
135		let width = i32::MAX as usize + 1;
136		let result = write_bmp(&mut buffer, width, 100, &pixels);
137
138		match result {
139			Err(Error::WidthTooLarge { was, .. }) if was == width => {}
140			otherwise => panic!("Width error is incorrect. {:?}", otherwise),
141		}
142	}
143
144	#[test]
145	fn bad_height() {
146		let pixels = [0u8; 100 * 100 * 3];
147		let mut buffer = [0u8; buffer_length(100, 100)];
148		let height = i32::MAX as usize + 1;
149		let result = write_bmp(&mut buffer, 100, height, &pixels);
150
151		match result {
152			Err(Error::HeightTooLarge { was, .. }) if was == height => {}
153			otherwise => panic!("Height error is incorrect. {:?}", otherwise),
154		}
155	}
156
157	#[test]
158	fn bad_file_length() {
159		let mut buffer = [0u8; 100];
160		let pixels = [0u8; 100];
161		let result = write_bmp(&mut buffer, 65535, 65535, &pixels);
162
163		match result {
164			Err(Error::FileLengthTooLong { .. }) => {}
165			_ => assert!(false),
166		}
167	}
168
169	#[test]
170	fn bad_buffer_length() {
171		let mut buffer = [0u8; 100];
172		let pixels = [0u8; 100 * 100 * 3];
173		let result = write_bmp(&mut buffer, 100, 100, &pixels);
174
175		match result {
176			Err(Error::BufferTooSmall { .. }) => {}
177			_ => assert!(false),
178		}
179	}
180}