bmp_monochrome/
lib.rs

1//! # BMP monochrome
2//!
3//! This library encode and decode monochromatic bitmap with no extra dependencies.
4//! Especially useful to encode QR-codes
5//!
6
7#![deny(missing_docs)]
8
9use std::convert::TryFrom;
10use std::fmt::{Debug, Display, Formatter};
11use std::io::Error;
12use std::num::TryFromIntError;
13
14mod bit;
15mod decode;
16mod encode;
17
18#[cfg(feature = "fuzz")]
19pub mod fuzz;
20
21const B: u8 = 66;
22const M: u8 = 77;
23const COLOR_PALLET_SIZE: u32 = 2 * 4; // 2 colors each 4 bytes
24const HEADER_SIZE: u32 = 2 + 12 + 40 + COLOR_PALLET_SIZE;
25
26/// The `Bmp` struct contains the data as a vector of vectors of booleans.
27/// Each boolean represent a pixel.
28/// In `rows` the first element is the upper row, inside the first vector there are the pixel
29/// from left to right, thus `rows[0][0]` is the upper-left element.
30/// Max len of the vetors (both rows and colums) is [u16::MAX]`
31/// Note in the serialized format the first element is the lower-left pixel
32/// see [BMP file format](https://en.wikipedia.org/wiki/BMP_file_format)
33#[derive(PartialEq, Eq, Clone)]
34pub struct Bmp {
35    rows: Vec<Vec<bool>>,
36}
37
38/// Internal error struct
39#[derive(Debug)]
40pub enum BmpError {
41    /// Generic
42    Generic,
43    /// Relative to the content
44    Content,
45    /// Relative to the header
46    Header,
47    /// Relative to the data
48    Data,
49    /// Relative to the size
50    Size(u16, u16),
51}
52
53impl Display for BmpError {
54    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
55        write!(f, "{:?}", self)
56    }
57}
58
59impl Debug for Bmp {
60    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
61        write!(f, "Bmp width={} height={}", self.width(), self.height(),)
62    }
63}
64
65impl std::error::Error for BmpError {}
66
67impl From<std::num::TryFromIntError> for BmpError {
68    fn from(_: TryFromIntError) -> Self {
69        BmpError::Generic
70    }
71}
72
73#[derive(Debug)]
74struct BmpHeader {
75    height: u16,
76    width: u16,
77    bg_is_zero: bool,
78}
79
80impl Bmp {
81    /// Creates a new Bmp, failing if `rows` is empty or it's first element is empty
82    /// or it's elements has different len
83    pub fn new(rows: Vec<Vec<bool>>) -> Result<Bmp, BmpError> {
84        if rows.is_empty() || rows[0].is_empty() || !rows.iter().all(|e| e.len() == rows[0].len()) {
85            Err(BmpError::Data)
86        } else {
87            check_size(u16::try_from(rows.len())?, u16::try_from(rows[0].len())?)?;
88            Ok(Bmp { rows })
89        }
90    }
91
92    /// return the Bmp height in pixel
93    pub fn height(&self) -> u16 {
94        self.rows.len() as u16
95    }
96
97    /// return the Bmp width in pixel
98    pub fn width(&self) -> u16 {
99        self.rows[0].len() as u16
100    }
101
102    /// return the pixel situated at (i,j), where (0,0) is the upper-left corner
103    /// could panic if i > self.height() || j > self.width()
104    pub fn get(&self, i: u16, j: u16) -> bool {
105        self.rows[i as usize][j as usize]
106    }
107
108    /// return a new Bmp where every pixel is multiplied by `mul`, erroring if mul is 0 or 1 or the
109    /// resulting image would be bigger than limits enforced by [crate::check_size]
110    pub fn mul(&self, mul: u8) -> Result<Bmp, BmpError> {
111        if mul <= 1 {
112            return Err(BmpError::Generic);
113        }
114        let mul = mul as u16;
115        let new_width = self.width().checked_mul(mul).ok_or(BmpError::Generic)?;
116        let new_height = self.height().checked_mul(mul).ok_or(BmpError::Generic)?;
117        check_size(new_width, new_height)?;
118        let mut rows = Vec::with_capacity(new_height as usize);
119
120        let mul = mul as usize;
121        for i in 0..self.height() {
122            let mut row = Vec::with_capacity(new_width as usize);
123            for j in 0..self.width() {
124                row.extend(vec![self.get(i, j); mul]);
125            }
126            rows.extend(vec![row; mul]);
127        }
128
129        Ok(Bmp { rows })
130    }
131
132    /// return a new Bmp where every square is divided by `div`
133    /// if all the square is not of the same color it errors
134    pub fn div(&self, div: u8) -> Result<Bmp, BmpError> {
135        if div <= 1 {
136            return Err(BmpError::Generic);
137        }
138        let div = div as u16;
139        let new_height = self.height() / div;
140        let new_width = self.width() / div;
141        if new_height == 0 || new_width == 0 || self.height() % div != 0 || self.width() % div != 0
142        {
143            return Err(BmpError::Generic);
144        }
145        let mut new_rows = vec![];
146
147        let div = div as usize;
148        for rows in self.rows.chunks(div) {
149            let mut new_row = vec![];
150            for j in 0..div - 1 {
151                if rows[j] != rows[j + 1] {
152                    return Err(BmpError::Generic);
153                }
154            }
155            for cols in rows[0].chunks(div) {
156                if cols.iter().all(|e| cols[0] == *e) {
157                    new_row.push(cols[0]);
158                } else {
159                    return Err(BmpError::Generic);
160                }
161            }
162            new_rows.push(new_row);
163        }
164        Ok(Bmp { rows: new_rows })
165    }
166
167    fn div_with_greater_possible(&self, greater_start: u8) -> Bmp {
168        for i in (2..greater_start).rev() {
169            if let Ok(bmp) = self.div(i) {
170                return bmp;
171            }
172        }
173        self.clone()
174    }
175
176    /// `normalize` removes the white border if any, and reduce the module pixel size to 1
177    /// (the module must be smaller than 10x10 pixel)
178    pub fn normalize(&self) -> Bmp {
179        self.remove_white_border().div_with_greater_possible(10)
180    }
181
182    /// return a new Bmp with `border_size` pixels around
183    pub fn add_white_border(&self, border_size: u8) -> Result<Bmp, BmpError> {
184        let double_border = border_size as u16 * 2;
185        let width = self
186            .width()
187            .checked_add(double_border)
188            .ok_or(BmpError::Generic)?;
189        let height = self
190            .height()
191            .checked_add(double_border)
192            .ok_or(BmpError::Generic)?;
193        check_size(width, height)?;
194        let mut new_rows = Vec::with_capacity(height as usize);
195        let border_size = border_size as usize;
196        new_rows.extend(vec![vec![false; width as usize]; border_size]);
197        for row in self.rows.iter() {
198            let mut new_row = Vec::with_capacity(width as usize);
199            new_row.extend(vec![false; border_size]);
200            new_row.extend(row);
201            new_row.extend(vec![false; border_size]);
202            new_rows.push(new_row);
203        }
204        new_rows.extend(vec![vec![false; width as usize]; border_size]);
205
206        Ok(Bmp { rows: new_rows })
207    }
208
209    /// remove all the white border, if any
210    pub fn remove_white_border(&self) -> Bmp {
211        let mut cur = self.clone();
212        loop {
213            match cur.remove_one_white_border() {
214                Ok(bmp) => cur = bmp,
215                Err(_) => return cur,
216            }
217        }
218    }
219
220    fn remove_one_white_border(&self) -> Result<Bmp, BmpError> {
221        if self.width() <= 2 || self.height() <= 2 {
222            return Err(BmpError::Generic);
223        }
224        let new_width = self.width() as usize - 2;
225        let new_height = self.height() as usize - 2;
226        let mut new_rows = vec![];
227        if self.rows[0].iter().all(|e| !*e)
228            && self.rows.last().unwrap().iter().all(|e| !*e)
229            && self.rows.iter().all(|r| !r[0])
230            && self.rows.iter().all(|r| !*r.last().unwrap())
231        {
232            for row in &self.rows[1..=new_height] {
233                new_rows.push(row[1..=new_width].to_vec())
234            }
235            Ok(Bmp { rows: new_rows })
236        } else {
237            Err(BmpError::Generic)
238        }
239    }
240
241    /// Return a struct implementing Display to visualize in terminal or in tests
242    pub fn display(&self) -> StringOutput {
243        StringOutput(self)
244    }
245
246    /// Return inverted bitmap, black pixels become white and viceversa.
247    pub fn inverse(&self) -> Bmp {
248        let mut new_vec = Vec::with_capacity(self.height() as usize);
249        for vec in self.rows.iter() {
250            new_vec.push(vec.iter().map(|e| !e).collect());
251        }
252        Bmp::new(new_vec).unwrap()
253    }
254}
255
256/// The struct returned from the [`Bmp::print()`] method which implements Display
257pub struct StringOutput<'a>(&'a Bmp);
258impl<'a> Display for StringOutput<'a> {
259    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
260        for row in self.0.rows.iter() {
261            for el in row.iter() {
262                if *el {
263                    write!(f, "#")?;
264                } else {
265                    write!(f, ".")?;
266                }
267            }
268            writeln!(f)?;
269        }
270        Ok(())
271    }
272}
273
274impl From<std::io::Error> for BmpError {
275    fn from(_: Error) -> Self {
276        BmpError::Generic
277    }
278}
279
280impl BmpHeader {
281    /// return bytes needed for `width` bits
282    fn bytes_per_row(&self) -> u32 {
283        (self.width as u32 + 7) / 8
284    }
285
286    /// return the padding
287    fn padding(&self) -> u32 {
288        (4 - self.bytes_per_row() % 4) % 4
289    }
290
291    /// return wether the bit 0 is to be considered black
292    fn bg_is_zero(&self) -> bool {
293        self.bg_is_zero
294    }
295}
296
297/// arbitrary limit width * height < 1 million
298/// height and width must be > 0
299fn check_size(width: u16, height: u16) -> Result<u32, BmpError> {
300    let width_height = width as u32 * height as u32;
301    if width_height <= 1_000_000 && width > 0 && height > 0 {
302        Ok(width_height)
303    } else {
304        Err(BmpError::Size(width, height))
305    }
306}
307
308#[cfg(test)]
309mod test {
310    use crate::*;
311    use rand::Rng;
312    use std::fs::File;
313    use std::io::Cursor;
314
315    #[test]
316    fn test_data_matrix() {
317        assert!(Bmp::new(vec![]).is_err());
318        assert!(Bmp::new(vec![vec![true]]).is_ok());
319        assert!(Bmp::new(vec![vec![true], vec![true]]).is_ok());
320        assert!(Bmp::new(vec![vec![true], vec![true, false]]).is_err());
321    }
322
323    #[test]
324    fn test_padding() {
325        let mut header = BmpHeader {
326            height: 0,
327            width: 0,
328            bg_is_zero: false,
329        };
330        assert_eq!(header.padding(), 0);
331
332        header.width = 1;
333        assert_eq!(header.padding(), 3);
334
335        header.width = 9;
336        assert_eq!(header.padding(), 2);
337
338        header.width = 17;
339        assert_eq!(header.padding(), 1);
340
341        header.width = 25;
342        assert_eq!(header.padding(), 0);
343    }
344
345    #[test]
346    fn test_bytes_per_row() {
347        let mut header = BmpHeader {
348            height: 0,
349            width: 0,
350            bg_is_zero: false,
351        };
352        assert_eq!(header.bytes_per_row(), 0);
353
354        header.width = 1;
355        assert_eq!(header.bytes_per_row(), 1);
356
357        header.width = 8;
358        assert_eq!(header.bytes_per_row(), 1);
359
360        header.width = 9;
361        assert_eq!(header.bytes_per_row(), 2);
362    }
363
364    #[test]
365    fn test_mul() {
366        let data = Bmp::new(vec![vec![false, true], vec![false, true]]).unwrap();
367
368        let data_bigger = Bmp::new(vec![
369            vec![false, false, true, true],
370            vec![false, false, true, true],
371            vec![false, false, true, true],
372            vec![false, false, true, true],
373        ])
374        .unwrap();
375
376        assert_eq!(data.mul(2).unwrap(), data_bigger);
377
378        let data = Bmp::new(vec![vec![false, true], vec![false, false]]).unwrap();
379
380        let data_bigger = Bmp::new(vec![
381            vec![false, false, true, true],
382            vec![false, false, true, true],
383            vec![false, false, false, false],
384            vec![false, false, false, false],
385        ])
386        .unwrap();
387
388        assert_eq!(data.mul(2).unwrap(), data_bigger);
389    }
390
391    #[test]
392    fn test_div() {
393        let data = Bmp::new(vec![
394            vec![false, false, true, true],
395            vec![false, false, true, true],
396            vec![false, false, true, true],
397            vec![false, false, true, true],
398        ])
399        .unwrap();
400        let expected = Bmp::new(vec![vec![false, true], vec![false, true]]).unwrap();
401        assert_eq!(expected, data.div(2).unwrap());
402    }
403
404    #[test]
405    fn test_mul_div() {
406        let expected = random_bmp();
407        let mul = expected.mul(3).unwrap();
408        let div = mul.div(3).unwrap();
409        assert_eq!(expected, div);
410    }
411
412    #[test]
413    fn test_add_white_border() {
414        let data = Bmp::new(vec![vec![false]]).unwrap();
415        let data_bigger = Bmp::new(vec![vec![false; 5]; 5]).unwrap();
416
417        assert_eq!(data.add_white_border(2).unwrap(), data_bigger);
418    }
419
420    #[test]
421    fn test_rect() {
422        let rect = Bmp::new(vec![
423            vec![false, false],
424            vec![false, false],
425            vec![false, true],
426        ])
427        .unwrap();
428        rect.write(File::create("test_bmp/rect.bmp").unwrap())
429            .unwrap();
430    }
431
432    #[test]
433    fn test_bmp() {
434        let data_test1 = Bmp::new(vec![vec![false, true], vec![true, false]]).unwrap();
435        let bytes_test1 = Bmp::read(&mut File::open("test_bmp/test1.bmp").unwrap()).unwrap();
436        assert_eq!(data_test1, bytes_test1);
437
438        let bmp_test2 = data_test1.mul(3).unwrap().add_white_border(12).unwrap();
439        let bytes_test2 = Bmp::read(&mut File::open("test_bmp/test2.bmp").unwrap()).unwrap();
440        assert_eq!(
441            bmp_test2.display().to_string(),
442            bytes_test2.display().to_string()
443        );
444    }
445
446    #[test]
447    fn test_bmp_with_bg_is_zero() {
448        let bmp = Bmp::read(&mut File::open("test_bmp/qr-bolt11.bmp").unwrap()).unwrap();
449        let mut cursor = Cursor::new(vec![]);
450        bmp.write(&mut cursor).unwrap();
451        cursor.set_position(0);
452        let bmp2 = Bmp::read(cursor).unwrap();
453        assert_eq!(bmp, bmp2);
454    }
455
456    #[test]
457    fn test_monochrome_image() {
458        // taken from https://github.com/pertbanking/bitmap-monochrome/blob/master/monochrome_image.bmp
459        let expected = Bmp::new(
460            vec![
461                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
462                1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
463                0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1,
464                1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0,
465                0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1,
466                1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0,
467                1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
468                1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1,
469                0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
470                0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
471                0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
472                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
473            ]
474            .chunks(18)
475            .map(|r| r.iter().map(|e| *e == 0).collect())
476            .collect(),
477        )
478        .unwrap();
479
480        let bmp = Bmp::read(File::open("test_bmp/monochrome_image.bmp").unwrap()).unwrap();
481        assert_eq!(expected, bmp);
482    }
483
484    #[test]
485    fn test_rt() {
486        let expected = random_bmp();
487        let mut cursor = Cursor::new(vec![]);
488        expected.write(&mut cursor).unwrap();
489        cursor.set_position(0);
490        let bmp = Bmp::read(&mut cursor).unwrap();
491        assert_eq!(expected, bmp);
492    }
493
494    #[test]
495    fn test_get() {
496        let file = File::open("test_bmp/test1.bmp").unwrap();
497        let bmp = Bmp::read(file).unwrap();
498        assert!(!bmp.get(0, 0), "lower-left is not dark");
499    }
500
501    #[test]
502    fn test_remove_white_border() {
503        let bmp5 = Bmp::new(vec![vec![false; 5]; 5]).unwrap();
504        let bmp3 = Bmp::new(vec![vec![false; 3]; 3]).unwrap();
505        assert_eq!(bmp3, bmp5.remove_one_white_border().unwrap());
506        let bmp1 = Bmp::new(vec![vec![false]]).unwrap();
507        assert_eq!(bmp1, bmp3.remove_one_white_border().unwrap());
508        assert_eq!(bmp1, bmp5.remove_white_border());
509    }
510
511    #[test]
512    fn test_div_with_greater_possible() {
513        let bmp = Bmp::read(File::open("test_bmp/monochrome_image.bmp").unwrap()).unwrap();
514        let mul = bmp.mul(4).unwrap();
515        let div = mul.div_with_greater_possible(10);
516        assert_eq!(div, bmp);
517    }
518
519    #[test]
520    fn test_normalize() {
521        let bmp = Bmp::read(File::open("test_bmp/qr_not_normalized.bmp").unwrap()).unwrap();
522        let bmp_normalized = Bmp::read(File::open("test_bmp/qr_normalized.bmp").unwrap()).unwrap();
523        assert_eq!(bmp.normalize(), bmp_normalized);
524    }
525
526    #[test]
527    fn test_inverse() {
528        let bmp = random_bmp();
529        let inverted = bmp.inverse();
530        assert_ne!(bmp, inverted);
531        assert_eq!(bmp, inverted.inverse());
532    }
533
534    fn random_bmp() -> Bmp {
535        let mut rng = rand::thread_rng();
536        let width: u16 = rng.gen_range(1, 20);
537        let height: u16 = rng.gen_range(1, 20);
538        let mut data = vec![];
539        for _ in 0..height {
540            let row: Vec<bool> = (0..width).map(|_| rng.gen()).collect();
541            data.push(row);
542        }
543        Bmp::new(data).unwrap()
544    }
545}