oxihuman_export/
bmp_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum BmpBitDepth {
10 Bpp24,
11 Bpp32,
12}
13
14impl BmpBitDepth {
15 pub fn bytes_per_pixel(&self) -> usize {
17 match self {
18 Self::Bpp24 => 3,
19 Self::Bpp32 => 4,
20 }
21 }
22}
23
24#[derive(Debug, Clone)]
26pub struct BmpExport {
27 pub width: u32,
28 pub height: u32,
29 pub bit_depth: BmpBitDepth,
30 pub pixels: Vec<[u8; 4]>,
31}
32
33impl BmpExport {
34 pub fn new_solid(width: u32, height: u32, bit_depth: BmpBitDepth, color: [u8; 4]) -> Self {
36 let pixels = vec![color; (width * height) as usize];
37 Self {
38 width,
39 height,
40 bit_depth,
41 pixels,
42 }
43 }
44
45 pub fn pixel_count(&self) -> usize {
47 self.pixels.len()
48 }
49}
50
51pub fn build_bmp_header(export: &BmpExport) -> Vec<u8> {
53 let bytes_per_pixel = export.bit_depth.bytes_per_pixel();
54 let row_size = (export.width as usize * bytes_per_pixel).div_ceil(4) * 4;
55 let pixel_data_size = row_size * export.height as usize;
56 let file_size = 54 + pixel_data_size;
57 let mut header = vec![0u8; 54];
58 header[0] = b'B';
59 header[1] = b'M';
60 header[2..6].copy_from_slice(&(file_size as u32).to_le_bytes());
61 header[10] = 54;
62 header[14] = 40;
63 header[18..22].copy_from_slice(&export.width.to_le_bytes());
64 header[22..26].copy_from_slice(&export.height.to_le_bytes());
65 header[26] = 1;
66 header[28] = (bytes_per_pixel * 8) as u8;
67 header
68}
69
70pub fn validate_bmp(export: &BmpExport) -> bool {
72 export.width > 0
73 && export.height > 0
74 && export.pixels.len() == (export.width * export.height) as usize
75}
76
77pub fn estimate_bmp_bytes(export: &BmpExport) -> usize {
79 let bytes_per_pixel = export.bit_depth.bytes_per_pixel();
80 let row_size = (export.width as usize * bytes_per_pixel).div_ceil(4) * 4;
81 54 + row_size * export.height as usize
82}
83
84pub fn average_brightness(export: &BmpExport) -> f32 {
86 if export.pixels.is_empty() {
87 return 0.0;
88 }
89 let sum: f32 = export
90 .pixels
91 .iter()
92 .map(|p| (p[0] as f32 + p[1] as f32 + p[2] as f32) / 3.0)
93 .sum();
94 sum / export.pixels.len() as f32
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn sample() -> BmpExport {
102 BmpExport::new_solid(8, 8, BmpBitDepth::Bpp24, [100, 150, 200, 255])
103 }
104
105 #[test]
106 fn test_pixel_count() {
107 assert_eq!(sample().pixel_count(), 64);
109 }
110
111 #[test]
112 fn test_validate_valid() {
113 assert!(validate_bmp(&sample()));
115 }
116
117 #[test]
118 fn test_bmp_header_magic() {
119 let h = build_bmp_header(&sample());
121 assert_eq!(h[0], b'B');
122 assert_eq!(h[1], b'M');
123 }
124
125 #[test]
126 fn test_estimate_bytes_positive() {
127 assert!(estimate_bmp_bytes(&sample()) > 0);
129 }
130
131 #[test]
132 fn test_average_brightness() {
133 let e = BmpExport::new_solid(2, 2, BmpBitDepth::Bpp24, [100, 100, 100, 255]);
135 assert!((average_brightness(&e) - 100.0).abs() < 1.0);
136 }
137
138 #[test]
139 fn test_bpp24_bytes_per_pixel() {
140 assert_eq!(BmpBitDepth::Bpp24.bytes_per_pixel(), 3);
142 }
143
144 #[test]
145 fn test_bpp32_estimate_larger() {
146 let e24 = BmpExport::new_solid(8, 8, BmpBitDepth::Bpp24, [0; 4]);
148 let e32 = BmpExport::new_solid(8, 8, BmpBitDepth::Bpp32, [0; 4]);
149 assert!(estimate_bmp_bytes(&e32) > estimate_bmp_bytes(&e24));
150 }
151
152 #[test]
153 fn test_validate_zero_dim() {
154 let mut e = sample();
156 e.width = 0;
157 assert!(!validate_bmp(&e));
158 }
159}