From 7c640bfeaf2a0fbc8c3550b4c19790a800a7f6a2 Mon Sep 17 00:00:00 2001 From: Dylan Date: Wed, 26 Aug 2026 23:58:16 +1200 Subject: feat: Added a bunch of stuff Added derive macro for byte encode Added start for PNG reading Added BMP writing --- src/png.rs | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 src/png.rs (limited to 'src/png.rs') diff --git a/src/png.rs b/src/png.rs new file mode 100644 index 0000000..ef1aae0 --- /dev/null +++ b/src/png.rs @@ -0,0 +1,251 @@ +use std::collections::{self, HashMap}; + +use byte_encode_derive::ByteEncode; + +use crate::{byte_encode::ByteEncode, crc::update_crc, image::{ColorType, Image, ImageBase}, reader::FileReader, zlib::{zlib_decode, ByteBuffer}}; + +#[derive(Default)] +pub struct PNGImage +{ + IHDR: Option, + IDAT: Option, + IEND: Option, + tEXt: Option, +} + +trait Mergable: Sized +{ + fn merge(a: Self, b: Self) -> Self; +} + +impl Mergable for Option +{ + fn merge(a: Option, b: Option) -> Option + { + if let Some(ref a_content) = a + { + if let Some(b_content) = b + { + Some(T::merge(a_content.clone(), b_content)) + } + else + { + a + } + } + else + { + b + } + } +} + +trait PNGChunk: Mergable +{ + fn read(reader: &mut FileReader, length: usize) -> Self; + const CHUNK_TYPE: [char; 4]; +} + +#[derive(ByteEncode, Clone)] +struct IHDR +{ + pub width: u32, + pub height: u32, + pub bit_depth: u8, + pub color_type: u8, + pub compression_method: u8, + pub filter_method: u8, + pub interlace_method: u8 +} +impl PNGChunk for IHDR +{ + const CHUNK_TYPE: [char; 4] = ['I', 'H', 'D', 'R']; + fn read(reader: &mut FileReader, length: usize) -> Self { + reader.read() + } +} +impl Mergable for IHDR { + fn merge(a: Self, b: Self) -> Self { + todo!() + } +} +#[derive(ByteEncode, Clone)] +struct IEND +{ +} +impl PNGChunk for IEND +{ + const CHUNK_TYPE: [char; 4] = ['I', 'E', 'N', 'D']; + fn read(reader: &mut FileReader, length: usize) -> Self { + Self {} + } +} +impl Mergable for IEND { + fn merge(a: Self, b: Self) -> Self { + todo!() + } +} + +#[derive(Clone)] +struct tEXt +{ + keywords: HashMap +} +impl PNGChunk for tEXt +{ + const CHUNK_TYPE: [char; 4] = ['t', 'E', 'X', 't']; + fn read(reader: &mut FileReader, length: usize) -> Self + { + let mut i = 0; + let mut keyword = String::new(); + let mut c: u8 = reader.read(); + i += 1; + while c != 0 + { + keyword.push(c as char); + c = reader.read::<1, u8>(); + i += 1; + } + + let mut text_string = String::new(); + while i < length + { + text_string.push(reader.read::<1, u8>() as char); + i += 1; + } + + let mut keywords = HashMap::new(); + keywords.insert(keyword, text_string); + + Self + { + keywords + } + } +} +impl Mergable for tEXt { + fn merge(a: Self, b: Self) -> Self { + let mut keywords = HashMap::new(); + keywords.extend(a.keywords); + keywords.extend(b.keywords); + Self + { + keywords + } + } +} + +#[derive(Clone)] +struct IDAT +{ + data: Vec +} +impl PNGChunk for IDAT +{ + fn read(reader: &mut FileReader, length: usize) -> Self { + let data = reader.get_bytes(length); + Self + { + data + } + } + + const CHUNK_TYPE: [char; 4] = ['I', 'D', 'A', 'T']; +} +impl Mergable for IDAT +{ + fn merge(a: Self, b: Self) -> Self { + let mut data = Vec::new(); + data.extend(a.data); + data.extend(b.data); + Self + { + data + } + } +} + +impl Image for PNGImage +{ + fn read_image(reader: &mut FileReader) -> Result, String> { + let magic: [u8; 8] = reader.read_array(); + if magic != [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] + { + return Err("Not a PNG!".to_owned()); + } + + reader.set_endianness(false); + reader.set_checksum_handler(update_crc, 0xffffffffu32); + + let mut p = PNGImage::default(); + + while p.IEND.is_none() + { + let length: usize = reader.read::<4, u32>() as usize; + reader.reset_checksum(); + let chunk_type: [char; 4] = reader.read_array::<1, u8, 4>().map(|x| x as char); + let chunk_flags = chunk_type.map(|x| x.is_lowercase()); + println!("type: {chunk_type:?}"); + println!("length: {length}"); + match chunk_type + { + IHDR::CHUNK_TYPE => { p.IHDR = Option::::merge(p.IHDR, Some(IHDR::read(reader, length))) } + IDAT::CHUNK_TYPE => { p.IDAT = Option::::merge(p.IDAT, Some(IDAT::read(reader, length))) } + IEND::CHUNK_TYPE => { p.IEND = Option::::merge(p.IEND, Some(IEND::read(reader, length))) } + tEXt::CHUNK_TYPE => { p.tEXt = Option::::merge(p.tEXt, Some(tEXt::read(reader, length))) } + _ => { + if chunk_flags[0] + { + println!("Skipping ancillary chunk"); + println!("{}", if chunk_flags[1] { "Private (can find definition)" } else { "Public" }) + } + else + { + println!("Skipping important chunk!!!"); + } + reader.skip(length) + } + } + let mut calculated_crc = reader.get_checksum(); + let crc: u32 = reader.read(); + if crc ^ calculated_crc != 0xffffffffu32 && !chunk_flags[0] + { + calculated_crc ^= 0xffffffffu32; + println!("Bad crc: {crc} {calculated_crc}"); + } + } + + if let Some(ref tEXt) = p.tEXt + { + for x in tEXt.keywords.iter() + { + println!("{0}: {1}", x.0, x.1); + } + } + + if p.IEND.is_none() + { + return Err("IEND not present".to_owned()); + } + + let Some(IHDR) = p.IHDR else { return Err("IHDR not present".to_owned()); }; + let Some(IDAT) = p.IDAT else { return Err("IDAT not present".to_owned()); }; + + let colors_per_pixel: u8 = if IHDR.color_type == 4 || IHDR.color_type == 6 { 4 } else { 3 }; + let bpp: u8 = IHDR.bit_depth * colors_per_pixel; + let width: u32 = IHDR.width; + let height: u32 = IHDR.height; + println!("{bpp}, {width}, {height}"); + + let compression_method: u8 = IDAT.data[0]; + let additional_flags: u8 = IDAT.data[1]; + + let decoded = zlib_decode(ByteBuffer::new(&IDAT.data[2..])); + + todo!("PNG not finished") + } + + fn write_image(image: &crate::image::ImageBase, writer: &mut crate::writer::FileWriter)-> () { + todo!() + } +} -- cgit v1.3.1