summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDylan <boss@tehbox.org>2026-06-21 15:01:28 +1200
committerDylan <boss@tehbox.org>2026-06-21 15:03:02 +1200
commita472b1cdaa8a1f72b96988009b3d4d9820e7128f (patch)
tree833959761c3c1f60c60425bdc7553162cacb3f2d /src
parent14de6db28fb689cdb066c5b01a2885b6a6a35157 (diff)
downloadtehimage-rs-a472b1cdaa8a1f72b96988009b3d4d9820e7128f.tar.gz
tehimage-rs-a472b1cdaa8a1f72b96988009b3d4d9820e7128f.zip
feat: Added bitmap writing
Diffstat (limited to 'src')
-rw-r--r--src/bmp.rs107
-rw-r--r--src/image.rs89
-rw-r--r--src/main.rs187
-rw-r--r--src/reader.rs8
-rw-r--r--src/writer.rs69
5 files changed, 279 insertions, 181 deletions
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<ImageBase, &'static str>
+ {
+ 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<T: ColorDepth>
+{
+ pub r: T,
+ pub g: T,
+ pub b: T,
+ pub a: T
+}
+#[derive(Debug, Clone, Copy)]
+pub struct RGB<T: ColorDepth>
+{
+ pub r: T,
+ pub g: T,
+ pub b: T,
+}
+
+pub trait ColorType: Clone
+{
+ const DEFAULT: Self;
+}
+impl<T: ColorDepth> ColorType for RGBA<T>
+{
+ const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT, a: T::DEFAULT };
+}
+impl<T: ColorDepth> ColorType for RGB<T>
+{
+ const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT};
+}
+
+#[derive(Debug)]
+pub struct PixelArr<T: ColorType>
+{
+ pub width: u32,
+ pub height: u32,
+ raw: Vec<T>
+}
+impl<T: ColorType> PixelArr<T> {
+ pub fn new(width: u32, height: u32) -> Self {
+ let raw = vec![T::DEFAULT;(width*height) as usize];
+ Self { width, height, raw }
+ }
+}
+impl<T: ColorType> ops::Index<(usize, usize)> for PixelArr<T>
+{
+ type Output = T;
+
+ fn index(&self, index: (usize, usize)) -> &Self::Output {
+ let (x, y) = index;
+ &self.raw[x + y*(self.width as usize)]
+ }
+}
+impl<T: ColorType> ops::IndexMut<(usize, usize)> for PixelArr<T>
+{
+ 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<RGB<u8>>
+}
+
+pub trait Image
+{
+ fn read_image(reader: &mut FileReader) -> Result<ImageBase, &'static str>;
+ 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<T: Add<Output = T> + 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<T: ColorDepth>
-{
- r: T,
- g: T,
- b: T,
- a: T
-}
-#[derive(Debug, Clone)]
-struct RGB<T: ColorDepth>
-{
- r: T,
- g: T,
- b: T,
-}
-
-trait ColorType: Clone
-{
- const DEFAULT: Self;
-}
-impl<T: ColorDepth> ColorType for RGBA<T>
-{
- const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT, a: T::DEFAULT };
-}
-impl<T: ColorDepth> ColorType for RGB<T>
-{
- const DEFAULT: Self = Self { r: T::DEFAULT, g: T::DEFAULT, b: T::DEFAULT};
-}
-
-#[derive(Debug)]
-struct PixelArr<T: ColorType>
-{
- width: u32,
- height: u32,
- raw: Vec<T>
-}
-impl<T: ColorType> PixelArr<T> {
- fn new(width: u32, height: u32) -> Self {
- let raw = vec![T::DEFAULT;(width*height) as usize];
- Self { width, height, raw }
- }
-}
-impl<T: ColorType> ops::Index<(usize, usize)> for PixelArr<T>
-{
- type Output = T;
-
- fn index(&self, index: (usize, usize)) -> &Self::Output {
- let (x, y) = index;
- &self.raw[x + y*(self.width as usize)]
- }
-}
-impl<T: ColorType> ops::IndexMut<(usize, usize)> for PixelArr<T>
-{
- 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<RGB<u8>>
-}
-
-struct BMPImage
-{
-}
-
-
-trait Image
-{
- fn read_image(reader: &mut FileReader) -> Result<ImageBase, &'static str>;
-}
-
-impl Image for BMPImage
-{
- fn read_image(reader: &mut FileReader) -> Result<ImageBase, &'static str>
- {
- 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<const BUFF_SIZE: usize = 1024>
+pub struct FileReader
{
buf_reader: BufReader<File>
}
-impl<const BUFF_SIZE: usize> FileReader<BUFF_SIZE> {
+impl FileReader {
pub fn new(buf_reader: BufReader<File>) -> Self
{
Self
@@ -23,7 +23,7 @@ impl<const BUFF_SIZE: usize> FileReader<BUFF_SIZE> {
std::array::from_fn(|_| self.read())
}
- pub fn read<const N: usize, T: FromBytes<N> + Sized>(&mut self) -> T
+ pub fn read<const N: usize, T: FromBytes<N>>(&mut self) -> T
{
let mut data = [0u8; N];
self.buf_reader.read_exact(&mut data).expect("Bad read");
@@ -31,7 +31,7 @@ impl<const BUFF_SIZE: usize> FileReader<BUFF_SIZE> {
}
}
-pub trait FromBytes<const N: usize>: Sized
+pub trait FromBytes<const N: usize>: 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<File>
+}
+
+impl FileWriter
+{
+ pub fn new(buf_writer: BufWriter<File>) -> 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 N: usize, T: ToBytes<N>, const C: usize>(&mut self, xs: [T; C])
+ {
+ for x in xs
+ {
+ self.write(x);
+ }
+ }
+ pub fn write<const N: usize, T: ToBytes<N>>(&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<const N: usize>: 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)
+ }
+}