From a472b1cdaa8a1f72b96988009b3d4d9820e7128f Mon Sep 17 00:00:00 2001 From: Dylan Date: Sun, 21 Jun 2026 15:01:28 +1200 Subject: feat: Added bitmap writing --- src/bmp.rs | 107 +++++++++++++++++++++++++++++++++ src/image.rs | 89 ++++++++++++++++++++++++++++ src/main.rs | 187 ++++------------------------------------------------------ src/reader.rs | 8 +-- src/writer.rs | 69 ++++++++++++++++++++++ 5 files changed, 279 insertions(+), 181 deletions(-) create mode 100644 src/bmp.rs create mode 100644 src/image.rs create mode 100644 src/writer.rs (limited to 'src') diff --git a/src/bmp.rs b/src/bmp.rs new file mode 100644 index 0000000..f6c0c7e --- /dev/null +++ b/src/bmp.rs @@ -0,0 +1,107 @@ +use crate::{image::{ColorType, Image, ImageBase, PixelArr, RGB}, reader::FileReader, writer::FileWriter}; + +impl Image for BMPImage +{ + fn read_image(reader: &mut FileReader) -> Result + { + let magic: [u8; 2] = reader.read_array(); + if magic != [0x42, 0x4d] + { + return Err("Not a bitmap!"); + } + + let _file_size: u32 = reader.read(); + reader.skip(4); + let _offset: u32 = reader.read(); + + let header_size: u32 = reader.read(); + + // println!("File header: {file_size} {offset} {header_size}"); + + if header_size == 40 + { + + let width: u32 = reader.read(); + let height: u32 = reader.read(); + let _color_planes: u16 = reader.read(); + let bpp = reader.read::<2, u16>() as u8; + let compression_method: u32 = reader.read(); + let _image_size: u32 = reader.read(); + let _horr_res: u32 = reader.read(); + let _vert_res: u32 = reader.read(); + let _color_count: u32 = reader.read(); + let _impotant_color_count: u32 = reader.read(); + + if + bpp != 24 + || compression_method != 0 + { + return Err("bpp or compresssion method not supported") + } + + let row_size = (((bpp as u32) * width + 31)/32) * 4; + let skip: usize = (row_size - width * 3) as usize; + let mut pixel_arr = PixelArr::new(width, height); + for y in (0..height).rev() + { + for x in 0..width + { + let mut pixel = RGB::DEFAULT; + pixel.b = reader.read(); + pixel.g = reader.read(); + pixel.r = reader.read(); + pixel_arr[(x as usize, y as usize)] = pixel; + } + reader.skip(skip); + } + + return Ok(ImageBase + { + bit_depth: bpp, + pixels: pixel_arr, + }) + } + else + { + return Err("Header size not supported yet"); + } + + + } + + fn write_image(image: &ImageBase, writer: &mut FileWriter) -> () { + writer.write_array([0x42, 0x4d] as [u8; 2]); + + let row_size = (((24) * image.pixels.width + 31)/32) * 4; + let file_size: u32 = 14 + 12 + row_size*image.pixels.height; + let offset: u32 = 26; + writer.write(file_size as u32); + writer.write_zeros(4); + writer.write(offset as u32); + writer.write(12 as u32); // Header size + + writer.write(image.pixels.width as u16); + writer.write(image.pixels.height as u16); + writer.write(1 as u16); // Color planes + writer.write(24 as u16); // bpp + + let skip: usize = (row_size - image.pixels.width * 3) as usize; + for y in (0..image.pixels.height).rev() + { + for x in 0..image.pixels.width + { + let pixel = image.pixels[(x as usize, y as usize)]; + writer.write(pixel.b); + writer.write(pixel.g); + writer.write(pixel.r); + } + writer.write_zeros(skip); + } + writer.flush(); + } +} + + +pub struct BMPImage +{ +} diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..ab5dc99 --- /dev/null +++ b/src/image.rs @@ -0,0 +1,89 @@ +use std::ops; + +use crate::{reader::FileReader, writer::FileWriter}; + +pub trait ColorDepth: Clone +{ + const DEFAULT: Self; +} +impl ColorDepth for u8 { + const DEFAULT: Self = 0; +} +impl ColorDepth for u16 { + const DEFAULT: Self = 0; +} +impl ColorDepth for u32 { + const DEFAULT: Self = 0; +} + +#[derive(Debug, Clone, Copy)] +pub struct RGBA +{ + pub r: T, + pub g: T, + pub b: T, + pub a: T +} +#[derive(Debug, Clone, Copy)] +pub struct RGB +{ + pub r: T, + pub g: T, + pub b: T, +} + +pub trait ColorType: Clone +{ + const DEFAULT: Self; +} +impl ColorType for RGBA +{ + const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT, a: T::DEFAULT }; +} +impl ColorType for RGB +{ + const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT}; +} + +#[derive(Debug)] +pub struct PixelArr +{ + pub width: u32, + pub height: u32, + raw: Vec +} +impl PixelArr { + pub fn new(width: u32, height: u32) -> Self { + let raw = vec![T::DEFAULT;(width*height) as usize]; + Self { width, height, raw } + } +} +impl ops::Index<(usize, usize)> for PixelArr +{ + type Output = T; + + fn index(&self, index: (usize, usize)) -> &Self::Output { + let (x, y) = index; + &self.raw[x + y*(self.width as usize)] + } +} +impl ops::IndexMut<(usize, usize)> for PixelArr +{ + fn index_mut(&mut self, index: (usize, usize)) -> &mut T { + let (x, y) = index; + &mut self.raw[x + y*(self.width as usize)] + } +} + +#[derive(Debug)] +pub struct ImageBase +{ + pub bit_depth: u8, + pub pixels: PixelArr> +} + +pub trait Image +{ + fn read_image(reader: &mut FileReader) -> Result; + fn write_image(image: &ImageBase, writer: &mut FileWriter)-> (); +} diff --git a/src/main.rs b/src/main.rs index c348e77..2d68a02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,185 +1,18 @@ -// #![allow(dead_code)] - -use crate::reader::FileReader; -use std::{fs::File, io::BufReader, ops::{self}}; +use crate::{bmp::BMPImage, image::Image, reader::FileReader, writer::FileWriter}; +use std::{fs::File, io::{BufReader, BufWriter}}; mod reader; - -// #[derive(Debug)] -// struct RGB + Copy> -// { -// r: T, -// g: T, -// b: T -// } - -trait ColorDepth: Clone -{ - const DEFAULT: Self; -} -impl ColorDepth for u8 { - const DEFAULT: Self = 0; -} -impl ColorDepth for u16 { - const DEFAULT: Self = 0; -} -impl ColorDepth for u32 { - const DEFAULT: Self = 0; -} - -#[derive(Debug, Clone)] -struct RGBA -{ - r: T, - g: T, - b: T, - a: T -} -#[derive(Debug, Clone)] -struct RGB -{ - r: T, - g: T, - b: T, -} - -trait ColorType: Clone -{ - const DEFAULT: Self; -} -impl ColorType for RGBA -{ - const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT, a: T::DEFAULT }; -} -impl ColorType for RGB -{ - const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT}; -} - -#[derive(Debug)] -struct PixelArr -{ - width: u32, - height: u32, - raw: Vec -} -impl PixelArr { - fn new(width: u32, height: u32) -> Self { - let raw = vec![T::DEFAULT;(width*height) as usize]; - Self { width, height, raw } - } -} -impl ops::Index<(usize, usize)> for PixelArr -{ - type Output = T; - - fn index(&self, index: (usize, usize)) -> &Self::Output { - let (x, y) = index; - &self.raw[x + y*(self.width as usize)] - } -} -impl ops::IndexMut<(usize, usize)> for PixelArr -{ - fn index_mut(&mut self, index: (usize, usize)) -> &mut T { - let (x, y) = index; - &mut self.raw[x + y*(self.width as usize)] - } -} - -#[derive(Debug)] -struct ImageBase -{ - width: u32, - height: u32, - bit_depth: u8, - pixels: PixelArr> -} - -struct BMPImage -{ -} - - -trait Image -{ - fn read_image(reader: &mut FileReader) -> Result; -} - -impl Image for BMPImage -{ - fn read_image(reader: &mut FileReader) -> Result - { - let magic: [u8; 2] = reader.read_array(); - if magic != [0x42, 0x4d] - { - return Err("Not a bitmap!"); - } - - let _file_size: u32 = reader.read(); - reader.skip(4); - let _offset: u32 = reader.read(); - - let header_size: u32 = reader.read(); - - // println!("File header: {file_size} {offset} {header_size}"); - - if header_size == 40 - { - - let width: u32 = reader.read(); - let height: u32 = reader.read(); - let _color_planes: u16 = reader.read(); - let bpp = reader.read::<2, u16>() as u8; - let compression_method: u32 = reader.read(); - let _image_size: u32 = reader.read(); - let _horr_res: u32 = reader.read(); - let _vert_res: u32 = reader.read(); - let _color_count: u32 = reader.read(); - let _impotant_color_count: u32 = reader.read(); - - if - bpp != 24 - || compression_method != 0 - { - return Err("bpp or compresssion method not supported") - } - - let row_size = (((bpp as u32) * width + 31)/32) * 4; - let skip: usize = (row_size - width * 3) as usize; - let mut pixel_arr = PixelArr::new(width, height); - for y in (0..height).rev() - { - for x in 0..width - { - let mut pixel = RGB::DEFAULT; - pixel.b = reader.read(); - pixel.g = reader.read(); - pixel.r = reader.read(); - pixel_arr[(x as usize, y as usize)] = pixel; - } - reader.skip(skip); - } - - return Ok(ImageBase - { - width, - height, - bit_depth: bpp, - pixels: pixel_arr, - }) - } - else - { - return Err("Header size not supported yet"); - } - - - } -} +mod image; +mod bmp; +mod writer; fn main() { let file = File::open("smiley.bmp").expect("File does not exist"); let mut reader = FileReader::new(BufReader::new(file)); let _bmp = BMPImage::read_image(&mut reader).expect("Bitmap had error"); - // println!("{bmp:?}"); + + let out_file = File::create("tmp.bmp").expect("Cannot create fiel"); + let mut writer = FileWriter::new(BufWriter::new(out_file)); + BMPImage::write_image(&_bmp, &mut writer); + // println!("{_bmp:?}"); } diff --git a/src/reader.rs b/src/reader.rs index affd2da..00f66cb 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,11 +1,11 @@ use std::{fs::File, io::{BufReader, Read}}; -pub struct FileReader +pub struct FileReader { buf_reader: BufReader } -impl FileReader { +impl FileReader { pub fn new(buf_reader: BufReader) -> Self { Self @@ -23,7 +23,7 @@ impl FileReader { std::array::from_fn(|_| self.read()) } - pub fn read + Sized>(&mut self) -> T + pub fn read>(&mut self) -> T { let mut data = [0u8; N]; self.buf_reader.read_exact(&mut data).expect("Bad read"); @@ -31,7 +31,7 @@ impl FileReader { } } -pub trait FromBytes: Sized +pub trait FromBytes: Sized + Copy { fn from_le_bytes(buffer: &[u8; N]) -> Self; } diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..dc1ac85 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,69 @@ +use std::io::Write; +use std::{fs::File, io::BufWriter}; + +pub struct FileWriter +{ + buf_writer: BufWriter +} + +impl FileWriter +{ + pub fn new(buf_writer: BufWriter) -> Self + { + Self + { + buf_writer + } + } + + pub fn write_zeros(&mut self, c: usize) + { + for _ in 0..c + { + self.write(0 as u8); + } + // self.buf_writer.write_all(&[0u8; c]).expect("Could not write 0s"); + } + + pub fn write_array, const C: usize>(&mut self, xs: [T; C]) + { + for x in xs + { + self.write(x); + } + } + pub fn write>(&mut self, x: T) + { + let data = x.to_le_bytes(); + self.buf_writer.write_all(&data).expect("Could not write"); + } + + pub fn flush(&mut self) + { + self.buf_writer.flush().expect("Could not flush"); + } +} + +pub trait ToBytes: Sized + Copy +{ + fn to_le_bytes(&self) -> [u8; N]; +} + +impl ToBytes<4> for u32 +{ + fn to_le_bytes(&self) -> [u8; 4] { + u32::to_le_bytes(*self) + } +} +impl ToBytes<2> for u16 +{ + fn to_le_bytes(&self) -> [u8; 2] { + u16::to_le_bytes(*self) + } +} +impl ToBytes<1> for u8 +{ + fn to_le_bytes(&self) -> [u8; 1] { + u8::to_le_bytes(*self) + } +} -- cgit v1.3.1