mod reader;
mod writer;
pub use reader::read_bmp;
pub use writer::write_bmp;
#[derive(Debug, Clone)]
pub struct BmpImage {
pub pixels: Vec<u8>,
pub width: u32,
pub height: u32,
}
impl BmpImage {
pub fn new(width: u32, height: u32, r: u8, g: u8, b: u8) -> Self {
let pixel_count = (width * height) as usize;
let mut pixels = Vec::with_capacity(pixel_count * 3);
for _ in 0..pixel_count {
pixels.push(r);
pixels.push(g);
pixels.push(b);
}
BmpImage {
pixels,
width,
height,
}
}
pub fn get_pixel(&self, x: u32, y: u32) -> Option<(u8, u8, u8)> {
if x >= self.width || y >= self.height {
return None;
}
let idx = ((y * self.width + x) * 3) as usize;
if idx + 2 >= self.pixels.len() {
return None;
}
Some((self.pixels[idx], self.pixels[idx + 1], self.pixels[idx + 2]))
}
pub fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8) {
if x < self.width && y < self.height {
let idx = ((y * self.width + x) * 3) as usize;
if idx + 2 < self.pixels.len() {
self.pixels[idx] = r;
self.pixels[idx + 1] = g;
self.pixels[idx + 2] = b;
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct BmpFileHeader {
pub file_size: u32,
pub pixel_offset: u32,
}
#[derive(Debug, Clone)]
pub(crate) struct BmpInfoHeader {
pub width: i32,
pub height: i32,
pub bits_per_pixel: u16,
pub compression: u32,
pub image_size: u32,
}
pub(crate) fn row_stride(width: u32, bits_per_pixel: u16) -> usize {
let raw_bytes = (width as usize * bits_per_pixel as usize + 7) / 8;
(raw_bytes + 3) & !3 }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bmp_roundtrip_solid_color() {
let dir = std::env::temp_dir().join("scirs2_bmp_test_solid");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("solid.bmp");
let img = BmpImage::new(10, 10, 255, 0, 128);
write_bmp(&path, &img.pixels, img.width, img.height).expect("Write failed");
let loaded = read_bmp(&path).expect("Read failed");
assert_eq!(loaded.width, 10);
assert_eq!(loaded.height, 10);
assert_eq!(loaded.pixels.len(), 10 * 10 * 3);
for y in 0..10 {
for x in 0..10 {
let (r, g, b) = loaded.get_pixel(x, y).expect("Pixel out of range");
assert_eq!((r, g, b), (255, 0, 128), "Pixel mismatch at ({}, {})", x, y);
}
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_bmp_roundtrip_gradient() {
let dir = std::env::temp_dir().join("scirs2_bmp_test_gradient");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("gradient.bmp");
let width = 256u32;
let height = 1u32;
let mut pixels = Vec::with_capacity((width * height * 3) as usize);
for x in 0..width {
pixels.push(x as u8); pixels.push(0); pixels.push(0); }
write_bmp(&path, &pixels, width, height).expect("Write failed");
let loaded = read_bmp(&path).expect("Read failed");
assert_eq!(loaded.width, 256);
assert_eq!(loaded.height, 1);
for x in 0..256u32 {
let (r, _g, _b) = loaded.get_pixel(x, 0).expect("Pixel out of range");
assert_eq!(r, x as u8, "Red mismatch at x={}", x);
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_bmp_odd_width_padding() {
let dir = std::env::temp_dir().join("scirs2_bmp_test_padding");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("odd_width.bmp");
let width = 3u32;
let height = 2u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
pixels[0] = 255;
pixels[1] = 0;
pixels[2] = 0; pixels[3] = 0;
pixels[4] = 255;
pixels[5] = 0; pixels[6] = 0;
pixels[7] = 0;
pixels[8] = 255; pixels[9] = 128;
pixels[10] = 128;
pixels[11] = 0;
pixels[12] = 0;
pixels[13] = 128;
pixels[14] = 128;
pixels[15] = 128;
pixels[16] = 0;
pixels[17] = 128;
write_bmp(&path, &pixels, width, height).expect("Write failed");
let loaded = read_bmp(&path).expect("Read failed");
assert_eq!(loaded.get_pixel(0, 0), Some((255, 0, 0)));
assert_eq!(loaded.get_pixel(1, 0), Some((0, 255, 0)));
assert_eq!(loaded.get_pixel(2, 0), Some((0, 0, 255)));
assert_eq!(loaded.get_pixel(0, 1), Some((128, 128, 0)));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_bmp_large_image() {
let dir = std::env::temp_dir().join("scirs2_bmp_test_large");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("large.bmp");
let width = 200u32;
let height = 150u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x % 256) as u8;
pixels[idx + 1] = (y % 256) as u8;
pixels[idx + 2] = ((x + y) % 256) as u8;
}
}
write_bmp(&path, &pixels, width, height).expect("Write failed");
let loaded = read_bmp(&path).expect("Read failed");
assert_eq!(loaded.width, width);
assert_eq!(loaded.height, height);
assert_eq!(loaded.pixels, pixels);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_bmp_1x1() {
let dir = std::env::temp_dir().join("scirs2_bmp_test_1x1");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("tiny.bmp");
let pixels = vec![42u8, 84, 126];
write_bmp(&path, &pixels, 1, 1).expect("Write failed");
let loaded = read_bmp(&path).expect("Read failed");
assert_eq!(loaded.width, 1);
assert_eq!(loaded.height, 1);
assert_eq!(loaded.get_pixel(0, 0), Some((42, 84, 126)));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_bmp_pixel_access() {
let mut img = BmpImage::new(5, 5, 0, 0, 0);
img.set_pixel(2, 3, 100, 200, 50);
assert_eq!(img.get_pixel(2, 3), Some((100, 200, 50)));
assert_eq!(img.get_pixel(0, 0), Some((0, 0, 0)));
assert_eq!(img.get_pixel(10, 10), None);
}
#[test]
fn test_bmp_invalid_pixel_count() {
let dir = std::env::temp_dir().join("scirs2_bmp_test_invalid");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("invalid.bmp");
let pixels = vec![0u8; 10]; let result = write_bmp(&path, &pixels, 5, 5);
assert!(result.is_err());
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_bmp_nonexistent_file() {
let result = read_bmp("/nonexistent/path/image.bmp");
assert!(result.is_err());
}
#[test]
fn test_row_stride_calculation() {
assert_eq!(row_stride(1, 24), 4);
assert_eq!(row_stride(2, 24), 8);
assert_eq!(row_stride(4, 24), 12);
assert_eq!(row_stride(5, 24), 16);
}
}