Browse Source

Implement image bitmap saving and loading

Skymap from ambientCG.com, licensed under the Creative Commons CC0 1.0
Universal License: ambientCG.com/a/OutdoorHDRI042
Christoph Stelz 1 year ago
parent
commit
7fa7ea4ce1
2 changed files with 230 additions and 16 deletions
  1. BIN
      skymap.bmp
  2. 230 16
      src/main.rs

BIN
skymap.bmp


+ 230 - 16
src/main.rs

@@ -1,34 +1,248 @@
+use std::fs::File;
+use std::io::BufReader;
+use std::io::Read;
 
-mod random {
-    use std::fs::File;
-    use std::io::Read;
+// ┏━┓┏━┓┏┓╻╺┳┓┏━┓┏┳┓   ┏┓╻╻ ╻┏┳┓┏┓ ┏━╸┏━┓   ┏━╸┏━╸┏┓╻┏━╸┏━┓┏━┓╺┳╸╻┏━┓┏┓╻
+// ┣┳┛┣━┫┃┗┫ ┃┃┃ ┃┃┃┃   ┃┗┫┃ ┃┃┃┃┣┻┓┣╸ ┣┳┛   ┃╺┓┣╸ ┃┗┫┣╸ ┣┳┛┣━┫ ┃ ┃┃ ┃┃┗┫
+// ╹┗╸╹ ╹╹ ╹╺┻┛┗━┛╹ ╹   ╹ ╹┗━┛╹ ╹┗━┛┗━╸╹┗╸   ┗━┛┗━╸╹ ╹┗━╸╹┗╸╹ ╹ ╹ ╹┗━┛╹ ╹
 
-    pub struct RNG {
-        random_device: File
+pub struct RNG {
+    random_device: BufReader<File>,
+}
+
+impl RNG {
+    pub fn next_float(&mut self) -> f32 {
+        let mut buf: [u8; 4] = [0, 0, 0, 0];
+
+        self.random_device
+            .read(&mut buf)
+            .expect("Read from /dev/random failed");
+
+        let val: f32 = u32::from_be_bytes(buf) as f32;
+        val as f32 / u32::max_value() as f32
     }
 
-    impl RNG {
-        pub fn next_float(&mut self) -> f32 {
-            let mut buf: [u8; 4] = [0, 0, 0, 0];
+    pub fn create() -> RNG {
+        let f = File::open("/dev/urandom").expect("Did not find source of randomness");
+        let reader: BufReader<File> = BufReader::new(f);
+
+        return RNG {
+            random_device: reader,
+        };
+    }
+}
+
+// ┏━╸┏━╸┏━┓┏┳┓┏━╸╺┳╸┏━┓╻ ╻
+// ┃╺┓┣╸ ┃ ┃┃┃┃┣╸  ┃ ┣┳┛┗┳┛
+// ┗━┛┗━╸┗━┛╹ ╹┗━╸ ╹ ╹┗╸ ╹
 
-            self.random_device.read(&mut buf).expect("Read from /dev/random failed");
+#[derive(Copy, Clone)]
+pub struct Vec3 {
+    x: f32,
+    y: f32,
+    z: f32,
+}
+
+// ╻┏┳┓┏━┓┏━╸┏━╸   ╻┏━┓
+// ┃┃┃┃┣━┫┃╺┓┣╸    ┃┃ ┃
+// ╹╹ ╹╹ ╹┗━┛┗━╸   ╹┗━┛
+use std::convert::From;
+use std::io::BufWriter;
+use std::io::Write;
+use std::path::Path;
+
+pub struct Image {
+    width: u16,
+    height: u16,
+    data: Vec<Vec3>,
+}
+
+impl Image {
+    fn get_pixel(&self, x: usize, y: usize) -> &Vec3 {
+        &self.data[y * self.width as usize + x]
+    }
+
+    fn mod_pixel(&mut self, x: usize, y: usize) -> &mut Vec3 {
+        &mut self.data[y * self.width as usize + x]
+    }
 
-            let val : f32 = u32::from_be_bytes(buf) as f32;
+    fn new(width: u16, height: u16) -> Image {
+        let mut data: Vec<Vec3> = Vec::new();
+        data.resize(
+            usize::from(width) * usize::from(height),
+            Vec3 {
+                x: 0.0,
+                y: 0.0,
+                z: 0.0,
+            },
+        );
 
-            val as f32 / u32::max_value() as f32
+        Image {
+            width: width,
+            height: height,
+            data: data,
         }
+    }
+}
+
+fn read_int(v: &[u8]) -> u32 {
+    let mut result = 0;
+
+    for i in 0..3 {
+        result |= (v[i] as u32) << (i * 8);
+    }
+
+    result
+}
+
+fn read_int16(v: &[u8]) -> u16 {
+    v[1] as u16 >> 8 | v[0] as u16
+}
+
+fn read_vec3(v: &[u8]) -> Vec3 {
+    Vec3 {
+        x: v[2] as f32,
+        y: v[1] as f32,
+        z: v[0] as f32,
+    }
+}
+
+fn round_row_size(width: u32) -> u32 {
+    ((24 * width + 31) / 32) * 4
+}
+
+pub fn load_bmp(path: &Path) -> Image {
+    // See https://en.wikipedia.org/wiki/BMP_file_format for more information.
+    // BMP is little-endian (least-significant bytes first)
+    // Some limitations here:
+    //   - we only support bottom-up bitmaps
+    //   - without compression
+    //   - and a depth of 24 bits per pixel
+
+    let mut buf = Vec::new();
+    let mut f = BufReader::new(File::open(path).expect("Could not find image file"));
+    f.read_to_end(&mut buf).expect("Could not read image file");
 
-        pub fn create() -> RNG {
-            return RNG {
-                random_device: File::open("/dev/urandom").expect("Did not find source of randomness")
-            }
+    assert!(
+        buf[0] == 'B' as u8 && buf[1] == 'M' as u8,
+        "File is not a bitmap file"
+    );
+
+    let width = read_int(&buf[18..22]);
+    let height = read_int(&buf[22..26]);
+
+    let compression = read_int(&buf[30..34]);
+    assert!(compression == 0, "Only uncompressed BMPs are supported");
+
+    let depth = read_int16(&buf[28..30]);
+    assert!(depth == 24, "Only 24 bits per pixel are supported");
+
+    let offset = read_int(&buf[10..14]);
+
+    let row_size = round_row_size(width);
+
+    let mut image_data = Vec::new();
+
+    for row in 0..height {
+        for col in 0..width {
+            let y = height - row - 1;
+            let x = 3 * col;
+            let idx = (offset + y * row_size + x) as usize;
+            let pixel = read_vec3(&buf[idx..(idx + 3)]);
+            image_data.push(pixel);
         }
     }
 
+    assert!(image_data.len() == (width * height) as usize);
+
+    Image {
+        width: width as u16,
+        height: height as u16,
+        data: image_data,
+    }
+}
+
+fn write_int(f: &mut dyn Write, v: u32) -> std::io::Result<()> {
+    let bytes = [v as u8, (v >> 8) as u8, (v >> 16) as u8, (v >> 24) as u8];
+    f.write_all(&bytes)?;
+
+    Ok(())
 }
 
+fn write_int16(f: &mut dyn Write, v: u16) -> std::io::Result<()> {
+    let bytes = [v as u8, (v >> 8) as u8];
+    f.write_all(&bytes)?;
+
+    Ok(())
+}
+
+fn write_vec3(f: &mut dyn Write, v: &Vec3) -> std::io::Result<()> {
+    let bytes = [v.z as u8, v.y as u8, v.x as u8];
+    f.write_all(&bytes)?;
+
+    Ok(())
+}
+
+fn save_bmp(p: &Path, i: &Image) -> std::io::Result<()> {
+    let f: File = File::create(p)?;
+    let mut w = BufWriter::new(f);
+
+    // File header
+    w.write_all(&['B' as u8, 'M' as u8])?;
+    write_int(&mut w, 0)?; // size, left blank for now
+    write_int(&mut w, 0)?; // reserved bytes
+    write_int(&mut w, 54)?; // offset
+
+    // Bitmap info header
+    write_int(&mut w, 40)?; // header size in bytes
+    write_int(&mut w, i.width as u32)?;
+    write_int(&mut w, i.height as u32)?;
+    write_int16(&mut w, 1)?; // number of color planes
+    write_int16(&mut w, 24)?; // bits per pixel
+    write_int(&mut w, 0)?; // no compression
+    write_int(&mut w, 0)?; // image size
+    write_int(&mut w, 1000)?; // horizontal resolution in pixels per metre
+    write_int(&mut w, 1000)?; // vertical resolution
+    write_int(&mut w, 0)?; // number of colors
+    write_int(&mut w, 0)?; // number of important colors
+
+    // Pixel data
+    for y in (0..i.height).rev() {
+        for x in 0..i.width {
+            write_vec3(&mut w, i.get_pixel(x.into(), y.into()))?;
+        }
+    }
+
+    Ok(())
+}
 
 fn main() {
-    let mut rng : random::RNG = random::RNG::create();
+    let mut rng = RNG::create();
     println!("{}", rng.next_float());
+
+    let path = Path::new("skymap.bmp");
+    let image = load_bmp(&path);
+    println!(
+        "Image length: {}, {}x{}",
+        image.data.len(),
+        image.width,
+        image.height
+    );
+
+    let pix = image.get_pixel(0, 0);
+    println!("RGB Val: ({}, {}, {})", pix.x, pix.y, pix.z);
+
+    // Create a new pixel from scratch
+    let mut rand_bitmap = Image::new(1024, 1024);
+    for x in 0..1024 {
+        for y in 0..1024 {
+            let mut p = rand_bitmap.mod_pixel(x, y);
+            p.x = rng.next_float() * 255.0;
+            p.y = rng.next_float() * 255.0;
+            p.z = rng.next_float() * 255.0;
+        }
+    }
+
+    let outpath = Path::new("random.bmp");
+    save_bmp(&outpath, &rand_bitmap).expect("Could not save bitmap :(");
 }