1#![no_std]
2#![doc = include_str!("../README.md")]
3
4
5const BMP_HEADER_SIZE: usize = 54;
6
7
8pub 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
25pub 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 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 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 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#[derive(Debug, Copy, Clone, Eq, PartialEq)]
88pub enum Error {
89 BadPixelDataLength { expected: usize, was: usize },
91
92 FileLengthTooLong { max: usize, would_be: usize },
95
96 BufferTooSmall { required: usize, was: usize },
99
100 WidthTooLarge { max: usize, was: usize },
103
104 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}