patica/
bmp.rs

1use orfail::OrFail;
2use pati::{Color, Point};
3use std::{collections::BTreeMap, io::Write};
4
5pub fn write_image<W: Write>(
6    mut writer: W,
7    width: u16,
8    height: u16,
9    pixels: impl Iterator<Item = (Point, Color)>,
10) -> orfail::Result<()> {
11    let image_data_offset: u32 = 54;
12    let num_of_pixels = width as u32 * height as u32;
13    let file_size: u32 = image_data_offset + num_of_pixels * 4;
14
15    // File header.
16    writer.write_all(b"BM").or_fail()?;
17    writer.write_all(&file_size.to_le_bytes()).or_fail()?;
18    writer.write_all(&[0, 0, 0, 0]).or_fail()?;
19    writer
20        .write_all(&image_data_offset.to_le_bytes())
21        .or_fail()?;
22
23    // Information header.
24    writer.write_all(&[40, 0, 0, 0]).or_fail()?; // Header size.
25    writer.write_all(&(width as i32).to_le_bytes()).or_fail()?;
26    writer.write_all(&(height as i32).to_le_bytes()).or_fail()?;
27    writer.write_all(&[1, 0]).or_fail()?; // Planes.
28    writer.write_all(&[32, 0]).or_fail()?; // Bits per pixel.
29    writer.write_all(&[0, 0, 0, 0]).or_fail()?; // No compression.
30    writer
31        .write_all(&(num_of_pixels * 4).to_le_bytes())
32        .or_fail()?; // Image size.
33    writer.write_all(&[0, 0, 0, 0]).or_fail()?; // Horizontal resolution.
34    writer.write_all(&[0, 0, 0, 0]).or_fail()?; // Vertical resolution.
35    writer.write_all(&[0, 0, 0, 0]).or_fail()?; // Colors in palette.
36    writer.write_all(&[0, 0, 0, 0]).or_fail()?; // Important colors.
37
38    // Image data.
39    let pixels = pixels.collect::<BTreeMap<_, _>>();
40    for y in 0..height {
41        for x in 0..width {
42            let c = pixels
43                .get(&Point::new(x as i16, y as i16))
44                .copied()
45                .unwrap_or(Color::rgba(255, 255, 255, 0));
46            writer.write_all(&[c.b, c.g, c.r, c.a]).or_fail()?;
47        }
48    }
49    Ok(())
50}