Initial commit
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /target | ||||
							
								
								
									
										92
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "adler" | ||||
| version = "1.0.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" | ||||
|  | ||||
| [[package]] | ||||
| name = "array-init" | ||||
| version = "2.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596" | ||||
|  | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "1.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | ||||
|  | ||||
| [[package]] | ||||
| name = "cfg-if" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||||
|  | ||||
| [[package]] | ||||
| name = "colors-transform" | ||||
| version = "0.2.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178" | ||||
|  | ||||
| [[package]] | ||||
| name = "crc32fast" | ||||
| version = "1.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "flate2" | ||||
| version = "1.0.24" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" | ||||
| dependencies = [ | ||||
|  "crc32fast", | ||||
|  "miniz_oxide", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "log" | ||||
| version = "0.4.17" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "miniz_oxide" | ||||
| version = "0.5.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" | ||||
| dependencies = [ | ||||
|  "adler", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "png" | ||||
| version = "0.17.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "crc32fast", | ||||
|  "flate2", | ||||
|  "miniz_oxide", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "qoi-test" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "array-init", | ||||
|  "colors-transform", | ||||
|  "log", | ||||
|  "png", | ||||
| ] | ||||
							
								
								
									
										12
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| [package] | ||||
| name = "qoi-test" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| array-init = "2.0.1" | ||||
| png = "0.17.6" | ||||
| log = "0.4.17" | ||||
| colors-transform = "0.2.11" | ||||
							
								
								
									
										9
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| # QOI (Quite OK Image Format) Rust De-/Encoder | ||||
|  | ||||
| This is a (currently not bug free) implementation of the QOI image compression algorithm in Rust. | ||||
|  | ||||
| Currently supports de- and encoding from and to PNG. | ||||
|  | ||||
| ## To build | ||||
|  | ||||
| run `cargo build -r` to build a stable version for your rustc toolchain in `./target/release` | ||||
							
								
								
									
										
											BIN
										
									
								
								checkerboard.qoi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								checkerboard.qoi
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										828
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										828
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,828 @@ | ||||
| //! # qoi_img | ||||
| //! `qoi_img` is a bad, from-scratch implementation of the decoder and encoder for the `.qoi` file format as described as on [qoiformat.org](https://qoiformat.org/qoi-specification.pdf). | ||||
| //! This crate should not be published as better crates are available, e.g. [rapid-qoi](https://crates.io/crates/rapid-qoi). | ||||
|  | ||||
| #![allow(dead_code, unused_variables)] | ||||
| pub mod qoi_img { | ||||
|  | ||||
|     use std::fmt; | ||||
|     use std::io::prelude::*; | ||||
|     use std::fs::File; | ||||
|     use log::{debug,info, Level, Record, SetLoggerError, LevelFilter}; | ||||
|  | ||||
|     use array_init; | ||||
|  | ||||
|     //Custom error for custom error handling | ||||
|     #[derive(Debug, Clone, PartialEq)] | ||||
|     pub enum ImgError { | ||||
|         DataError, | ||||
|         PixelNumberError, | ||||
|         DecodeError, | ||||
|         HeaderError, | ||||
|     } | ||||
|     //inherit from base Error | ||||
|     impl std::error::Error for ImgError {} | ||||
|  | ||||
|  | ||||
|     //Output for error handling | ||||
|     impl fmt::Display for ImgError { | ||||
|         fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|             match self { | ||||
|                 ImgError::DataError => write!(f, "invalid number of bytes (must be devisible by 4)"), | ||||
|                 ImgError::PixelNumberError => write!(f, "number of pixels does not match height and width params"), | ||||
|                 ImgError::DecodeError => write!(f, "decoder failed to construct valid image"), | ||||
|                 ImgError::HeaderError => write!(f, "not a valid QOI file header") | ||||
|             }  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     //boilerplate implementation of the log crate | ||||
|     struct SimpleLogger; | ||||
|  | ||||
|     impl log::Log for SimpleLogger { | ||||
|         fn enabled(&self, metadata: &log::Metadata) -> bool { | ||||
|             metadata.level() <= Level::Debug | ||||
|         } | ||||
|         fn log(&self, record: &Record) { | ||||
|             if self.enabled(record.metadata()) { | ||||
|                 eprintln!("{} - {}", record.level(), record.args()); | ||||
|             } | ||||
|         } | ||||
|         fn flush(&self) { | ||||
|              | ||||
|         } | ||||
|     } | ||||
|     //logging boilerplate | ||||
|     static LOGGER: SimpleLogger = SimpleLogger; | ||||
|     /// Initialises the logger provided by [log](https://crates.io/crates/log) | ||||
|     /// # Example | ||||
|     ///  | ||||
|     /// ``` | ||||
|     /// # use std::error::Error; | ||||
|     /// # use crate::qoi_test::qoi_img::*; | ||||
|     /// # fn main() -> Result<(), Box<ImgError>> { | ||||
|     /// init().expect("Failed to initialize logger"); | ||||
|     /// # | ||||
|     /// # Ok(()) | ||||
|     /// #  | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     ///  | ||||
|     /// If you want to pass the error on replace the `println!`: | ||||
|     ///  | ||||
|     /// ``` | ||||
|     /// # use std::error::Error; | ||||
|     /// # use crate::qoi_test::qoi_img::*; | ||||
|     /// # fn main() -> Result<(), ImgError> { | ||||
|     /// match init() { | ||||
|     /// Ok(()) => (), | ||||
|     /// Err(e) => println!("Logger failed to initialize!") | ||||
|     /// } | ||||
|     /// # | ||||
|     /// # Ok(()) | ||||
|     /// #  | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     pub fn init() -> Result<(), SetLoggerError>{ | ||||
|         log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Debug)) | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// Custom image struct, which is used to store decoded data. Used by [encode_from_image] to encode the necessary data in bytes. Requires a Vector over [Pixel] values, `Vec<Pixel>`, | ||||
|     /// which can be generated by [`self::new`] if given byte data. Otherwise, [self.pixels] must be given filled vector. | ||||
|     /// `height` and `width` are given as u32 (note that qoi encoding does not guarantee functionality for images containing over 4000000 pixels.) | ||||
|     /// `channels` specifies the number of channels 3 (RGB)  or 4 (RBGA). | ||||
|     /// `colorspace` specifies whether sRGB or all linear channels are used (0,1); | ||||
|     /// # Examples | ||||
|     /// Create a new image via constructor [`Image::new()`]; | ||||
|     /// ```rust | ||||
|     /// # use std::error::Error; | ||||
|     /// # use crate::qoi_test::qoi_img::*; | ||||
|     /// # fn main() -> Result<(), Box<ImgError>> { | ||||
|     ///  | ||||
|     /// let pixels: Vec<u8> = vec![0;1024*1024*4]; | ||||
|     /// let height: u32 = 1024; | ||||
|     /// let width: u32 = 1024; | ||||
|     /// let channels: u8 = 4; | ||||
|     /// let colorspace: u8 = 0; | ||||
|     /// let img: Image = Image::new(pixels, height, width, channels, colorspace)?; | ||||
|     /// # | ||||
|     /// # Ok(()) | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     ///  | ||||
|     /// Alternatively, [`Image::from_pixels()`] can be used to create an image from pixel values. | ||||
|     pub struct Image { | ||||
|         pixels: Vec<Pixel>, | ||||
|         height: u32, | ||||
|         width: u32, | ||||
|         channels: u8, | ||||
|         colorspace: u8, | ||||
|     } | ||||
|  | ||||
|     impl Image { | ||||
|         //Image constructor, expects an array of u8 pixels values in order, left to right, top to bottom. | ||||
|         pub fn new(data: Vec<u8>, height: u32, width: u32, channels: u8, colorspace: u8,) -> Result<Image, ImgError> { | ||||
|             let pixels: Vec<Pixel> = match Image::pixels_from_bytes(data) { | ||||
|                 Ok(out) => out, | ||||
|                 Err(error) => return Err(error), | ||||
|             }; | ||||
|             if pixels.len() == (height * width) as usize { | ||||
|                 let  out: Image = Image { | ||||
|                     pixels, | ||||
|                     height, | ||||
|                     width, | ||||
|                     channels, | ||||
|                     colorspace, | ||||
|                 }; | ||||
|                 Ok(out) | ||||
|             } else { | ||||
|                 Err(ImgError::PixelNumberError) | ||||
|             } | ||||
|              | ||||
|         } | ||||
|  | ||||
|         pub fn from_pixels(pixels: Vec<Pixel>, height: u32, width: u32, channels: u8, colorspace: u8) -> Image { | ||||
|             let img = Image { | ||||
|                 pixels, | ||||
|                 height, | ||||
|                 width, | ||||
|                 channels, | ||||
|                 colorspace | ||||
|             }; | ||||
|             img | ||||
|         } | ||||
|  | ||||
|         fn pixels_from_bytes(data: Vec<u8>) -> Result<Vec<Pixel>, ImgError> { | ||||
|             let mut pixels: Vec<Pixel> = Vec::with_capacity(data.len()/4); | ||||
|             if data.len() % 4 == 0 { | ||||
|                 for i in 0..data.len()/4 { | ||||
|                     pixels.push(Pixel { | ||||
|                         r: data[i*4], | ||||
|                         g: data[i*4+1], | ||||
|                         b: data[i*4+2], | ||||
|                         a: data[i*4+3], | ||||
|                     }); | ||||
|                 } | ||||
|                 Ok(pixels) | ||||
|             } else { | ||||
|                 Err(ImgError::DataError) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Clone,Copy,Debug, PartialEq)] | ||||
|     pub struct Pixel { | ||||
|         r: u8,  | ||||
|         g: u8, | ||||
|         b: u8, | ||||
|         a: u8,  | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, PartialEq)] | ||||
|     pub enum ChunkType { | ||||
|         Run, | ||||
|         Index, | ||||
|         Luma, | ||||
|         Diff, | ||||
|         RGB, | ||||
|         RGBA | ||||
|     } | ||||
|  | ||||
|     impl Pixel { | ||||
|          | ||||
|  | ||||
|         pub fn new(r: u8, g: u8, b: u8, a: u8) -> Pixel { | ||||
|             Pixel {  | ||||
|                 r, | ||||
|                 g, | ||||
|                 b, | ||||
|                 a, | ||||
|             } | ||||
|         } | ||||
|         fn equals(&self, other: &Pixel) -> bool { | ||||
|             if (self.r == other.r) && (self.g == other.g) && (self.b == other.b) && (self.a == other.a) { | ||||
|                 true | ||||
|             } else { | ||||
|                 false | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         fn equals_rgb(&self, other: &Pixel) -> bool { | ||||
|             if (self.r == other.r) && (self.g == other.g) && (self.b == other.b) { | ||||
|                 true | ||||
|             } else { | ||||
|                 false | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         //self = curr pixel, other = prev pixel | ||||
|         pub fn determine_chunk(&self, other: &Pixel, buffer: &Vec<Pixel>) -> (ChunkType, Option<(u8,u8,u8)>){ | ||||
|              | ||||
|             if self.equals(&other) { | ||||
|                 return (ChunkType::Run, None); | ||||
|             } | ||||
|              | ||||
|             if self.equals(&buffer[color_hash(&self) as usize]) { | ||||
|                 return (ChunkType::Index,Some((color_hash(&self), 0,0 ))); | ||||
|             } | ||||
|  | ||||
|             if self.a != other.a { | ||||
|                 return (ChunkType::RGBA, None); | ||||
|             }  | ||||
|  | ||||
|             let diff_tuple: (i16,i16,i16) = self.diff(other); | ||||
|             let dr: i16 = diff_tuple.0; | ||||
|             let dg: i16 = diff_tuple.1; | ||||
|             let db: i16 = diff_tuple.2; | ||||
|  | ||||
|             if (dr > -3 && dr < 2) && (dg > -3 && dg < 2) && (db > -3 && db < 2) { | ||||
|                 let dr: u8 = (dr + DIFF_BIAS as i16) as u8; | ||||
|                 let dg: u8 = (dg + DIFF_BIAS as i16) as u8; | ||||
|                 let db: u8 = (db + DIFF_BIAS as i16) as u8; | ||||
|                 return (ChunkType::Diff, Some((dr, dg, db))); | ||||
|             } else if (dg > -33 && dg < 32) && ((dr - dg) > -9) && ((dr - dg) < 8) && ((db - dg) > -9) && ((db - dg) < 8) { | ||||
|                 let dg_out: u8 = (dg + LUMA_BIAS_G as i16) as u8; | ||||
|                 let dr_dg: u8 = (dr - dg + LUMA_BIAS_RB as i16) as u8; | ||||
|                 let db_dg: u8 = (db - dg + LUMA_BIAS_RB as i16) as u8; | ||||
|                 return (ChunkType::Luma, Some((dg_out, dr_dg, db_dg))); | ||||
|             } else { | ||||
|                 return (ChunkType::RGB, None); | ||||
|             } | ||||
|              | ||||
|         } | ||||
|         pub fn diff(&self, other: &Pixel) -> (i16, i16, i16) { | ||||
|             let mut dr: i16; | ||||
|             let dr_inv: i16; | ||||
|             let mut dg: i16; | ||||
|             let dg_inv: i16; | ||||
|             let mut db: i16; | ||||
|             let db_inv: i16; | ||||
|  | ||||
|             dr = self.r.wrapping_sub(other.r) as i16; | ||||
|             dr_inv = other.r.wrapping_sub(self.r) as i16; | ||||
|  | ||||
|             if dr.abs() > dr_inv.abs() { | ||||
|                 dr = dr_inv; | ||||
|                 dr = -dr; | ||||
|             } | ||||
|  | ||||
|             dg = self.g.wrapping_sub(other.g) as i16; | ||||
|             dg_inv = other.g.wrapping_sub(self.g) as i16; | ||||
|  | ||||
|             if dg.abs() > dg_inv.abs() { | ||||
|                 dg = dg_inv; | ||||
|                 dg = -dg; | ||||
|             } | ||||
|              | ||||
|             db = self.b.wrapping_sub(other.b) as i16; | ||||
|             db_inv = other.b.wrapping_sub(self.b) as i16; | ||||
|  | ||||
|             if db.abs() > db_inv.abs() { | ||||
|                 db = db_inv; | ||||
|                 db = -db; | ||||
|             } | ||||
|  | ||||
|             (dr, dg, db) | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     //Definition of header bytes | ||||
|     struct Header { | ||||
|         magic: [char; 4],   //magic bytes "qoif" | ||||
|         width: u32,         //image width in pixels (BE) | ||||
|         height: u32,        //image height in pixels (BE) | ||||
|         channels: u8,       // 3 = RGB, 4 = RBGA | ||||
|         colorspace: u8,     // 0 = sRGB with linear alpha, 1 = all channels linear | ||||
|     } | ||||
|  | ||||
|     impl Header { | ||||
|         fn convert_to_bytestream(&self) -> [u8;14] { | ||||
|             let mut out: [u8; 14] = [0;14]; | ||||
|  | ||||
|             //First, set magic bytes | ||||
|             out[0]  = self.magic[0] as u8; | ||||
|             out[1]  = self.magic[1] as u8; | ||||
|             out[2]  = self.magic[2] as u8; | ||||
|             out[3]  = self.magic[3] as u8; | ||||
|  | ||||
|             //split width and height into 8-bit chunks | ||||
|             let width_bytes = self.width.to_be_bytes(); | ||||
|             let height_bytes = self.height.to_be_bytes(); | ||||
|              | ||||
|             out[4]  =  width_bytes[0]; | ||||
|             out[5]  =  width_bytes[1]; | ||||
|             out[6]  =  width_bytes[2]; | ||||
|             out[7]  =  width_bytes[3]; | ||||
|             out[8]  = height_bytes[0]; | ||||
|             out[9]  = height_bytes[1]; | ||||
|             out[10] = height_bytes[2]; | ||||
|             out[11] = height_bytes[3]; | ||||
|  | ||||
|             //Set information bits | ||||
|             out[12] = self.channels; | ||||
|             out[13] = self.colorspace; | ||||
|  | ||||
|             out | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     //Definition of End of Stream bytes | ||||
|     #[derive(Debug)] | ||||
|     struct End { | ||||
|         bytes: [u8;8] | ||||
|     } | ||||
|     impl End { | ||||
|         fn new() -> End { | ||||
|             End { | ||||
|                 bytes: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     //chunks as defined in the QOI spec | ||||
|     const QOI_OP_RGB:   u8 = 0b1111_1110; | ||||
|     const QOI_OP_RGBA:  u8 = 0b1111_1111; | ||||
|     const QOI_OP_RUN:   u8 = 0b1100_0000; | ||||
|     const QOI_OP_INDEX: u8 = 0b0000_0000; | ||||
|     const QOI_OP_DIFF:  u8 = 0b0100_0000; | ||||
|     const QOI_OP_LUMA:  u8 = 0b1000_0000; | ||||
|  | ||||
|     //Biases as defined in the QOI spec | ||||
|     const RUN_BIAS:     u8 =          1; | ||||
|  | ||||
|     const DIFF_BIAS:    u8 =          2; | ||||
|      | ||||
|     const LUMA_BIAS_G:  u8 =         32; | ||||
|     const LUMA_BIAS_RB: u8 =          8; | ||||
|  | ||||
|  | ||||
|      | ||||
|     //hash function for assigning buffer indices to stored pixels | ||||
|     fn color_hash(pixel: &Pixel) -> u8 { | ||||
|         let store: u32 = pixel.r as u32 * 3 + pixel.g as u32 * 5 + pixel.b as u32 * 7 + pixel.a as u32 * 11; | ||||
|         (store % 64) as u8 | ||||
|     } | ||||
|  | ||||
|     pub fn encode_from_image(img: Image) -> Vec<u8> { | ||||
|  | ||||
|         let mut prev_pixel: Pixel = Pixel {r: 0u8, b: 0u8, g: 0u8, a: 255u8}; | ||||
|  | ||||
|         let mut prev_buffer: Vec<Pixel> = Vec::with_capacity(64); | ||||
|  | ||||
|         for i in 0..64 { | ||||
|             let pix: Pixel = Pixel {r:0,g:0,b:0,a:0}; | ||||
|             prev_buffer.push(pix); | ||||
|         } | ||||
|  | ||||
|         let mut encoded_bytes: Vec<u8> = Vec::new(); | ||||
|         let mut run: u8 = 0; | ||||
|          | ||||
|  | ||||
|         let head = Header { | ||||
|             magic: ['q', 'o', 'i', 'f'], | ||||
|             width: img.width, | ||||
|             height: img.height, | ||||
|             channels: img.channels, | ||||
|             colorspace: img.colorspace | ||||
|         }; | ||||
|         let head_stream = head.convert_to_bytestream(); | ||||
|          | ||||
|  | ||||
|         for i in head_stream { | ||||
|             encoded_bytes.push(i); | ||||
|         } | ||||
|  | ||||
|         let mut counter: u32 = 0; | ||||
|  | ||||
|         for pixel in img.pixels { | ||||
|             counter += 1; | ||||
|             let chunk: (ChunkType, Option<(u8,u8,u8)>) = pixel.determine_chunk(&prev_pixel, &prev_buffer); | ||||
|             if chunk == (ChunkType::Run, None) { | ||||
|                 run += 1; | ||||
|                 prev_pixel = pixel.clone(); | ||||
|                 continue; | ||||
|             } | ||||
|             if run > 0 { | ||||
|                 if run > 62 { | ||||
|                     while run > 0 { | ||||
|                         if run/62 > 0 { | ||||
|                             encoded_bytes.push(QOI_OP_RUN | (62-RUN_BIAS)); | ||||
|                             run -= 62; | ||||
|                         } else if run%62 > 0 { | ||||
|                             encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS)); | ||||
|                             run = 0; | ||||
|                         } else { | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS)); | ||||
|                     run = 0; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             match chunk { | ||||
|                 (ChunkType::Index, Some((index, irr1, irr2))) => { | ||||
|                     encoded_bytes.push(QOI_OP_INDEX | index); | ||||
|                     prev_pixel = pixel; | ||||
|                 }, | ||||
|                 (ChunkType::Diff, Some((dr, dg,db))) =>  { | ||||
|                     let mut out: u8 = 0b0000_0000; | ||||
|                     out = out | db; | ||||
|                     out = out | (dg << 2); | ||||
|                     out = out | (dr << 4); | ||||
|                     encoded_bytes.push(QOI_OP_DIFF | out); | ||||
|                     prev_pixel = pixel.clone(); | ||||
|                     prev_buffer[color_hash(&pixel) as usize] = pixel; | ||||
|                 }, | ||||
|                 (ChunkType::Luma, Some((dg, dr_dg, db_dg))) => { | ||||
|                     let mut out: [u8; 2] = [0b0000_0000;2]; | ||||
|                     out[0] |= dg; | ||||
|                     out[0] |= QOI_OP_LUMA; | ||||
|                     out[1] |= db_dg; | ||||
|                     out[1] |= dr_dg << 4; | ||||
|                     encoded_bytes.push(out[0]); | ||||
|                     encoded_bytes.push(out[1]); | ||||
|                     prev_pixel = pixel.clone(); | ||||
|                     prev_buffer[color_hash(&pixel) as usize] = pixel; | ||||
|                 }, | ||||
|                 (ChunkType::RGB, None) => { | ||||
|                     encoded_bytes.push(QOI_OP_RGB); | ||||
|                     encoded_bytes.push(pixel.r); | ||||
|                     encoded_bytes.push(pixel.g); | ||||
|                     encoded_bytes.push(pixel.b); | ||||
|                     prev_pixel = pixel.clone(); | ||||
|                     prev_buffer[color_hash(&pixel) as usize] = pixel;  | ||||
|                 },  | ||||
|                 (ChunkType::RGBA, None) => { | ||||
|                     if (pixel.a as i16 - prev_pixel.a as i16) == 0i16 { | ||||
|                         //this should never be reached, but it is | ||||
|                         encoded_bytes.push(QOI_OP_RGB); | ||||
|                         encoded_bytes.push(pixel.r); | ||||
|                         encoded_bytes.push(pixel.g); | ||||
|                         encoded_bytes.push(pixel.b); | ||||
|                         prev_pixel = pixel.clone(); | ||||
|                         prev_buffer[color_hash(&pixel) as usize] = pixel;  | ||||
|                     } else { | ||||
|                         encoded_bytes.push(QOI_OP_RGBA); | ||||
|                         encoded_bytes.push(pixel.r); | ||||
|                         encoded_bytes.push(pixel.g); | ||||
|                         encoded_bytes.push(pixel.b); | ||||
|                         encoded_bytes.push(pixel.a); | ||||
|                         prev_pixel = pixel.clone(); | ||||
|                         prev_buffer[color_hash(&pixel) as usize] = pixel; | ||||
|                     } | ||||
|                 }, | ||||
|                 _ => panic!("Critical error at encoding stage: Illegal output from difference function.") | ||||
|             } | ||||
|              | ||||
|         } | ||||
|  | ||||
|         if run > 0 { | ||||
|             if run > 62 { | ||||
|                 while run > 0 { | ||||
|                     if run/62 > 0 { | ||||
|                         encoded_bytes.push(QOI_OP_RUN | (62-RUN_BIAS)); | ||||
|                         run -= 62; | ||||
|                     } else if run%62 > 0 { | ||||
|                         encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS)); | ||||
|                         run = 0; | ||||
|                     } else { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let end_bytes = End::new(); | ||||
|         for i in end_bytes.bytes { | ||||
|             encoded_bytes.push(i) | ||||
|         } | ||||
|  | ||||
|         info!("Number of pixels processed: {}.", counter); | ||||
|         info!("Number of bytes in encoding: {:?}.", encoded_bytes.len()-22); | ||||
|         info!("Compression rate: {:.2}%.", (1.0-(encoded_bytes.len()-22) as f64/(counter*4)as f64)*100.0); | ||||
|          | ||||
|         encoded_bytes | ||||
|          | ||||
|     } | ||||
|   | ||||
|     pub fn write_to_file(bytes: Vec<u8>, filename: &str) -> std::io::Result<()>{ | ||||
|         let mut file_path: String = String::from(filename); | ||||
|         file_path.push_str(".qoi"); | ||||
|          | ||||
|         let mut buffer = File::create(file_path)?; | ||||
|         let mut pos = 0; | ||||
|  | ||||
|         while pos < bytes.len() { | ||||
|             let bytes_written = buffer.write(&bytes[pos..])?; | ||||
|             pos += bytes_written; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn read_header(bytes: &[u8]) -> Result<(u32,u32,u8,u8), ImgError> { | ||||
|         if bytes[0] == 'q' as u8 && bytes[1] == 'o' as u8 && bytes[2] == 'i' as u8 && bytes[3] == 'f' as u8 { | ||||
|             let mut width:  u32 = 0b0000_0000_0000_0000_0000_0000_0000_0000; | ||||
|             let mut height: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0000; | ||||
|             width  |=  ((bytes[4] as u32) << 24) as u32; | ||||
|             width  |=  ((bytes[5] as u32) << 16) as u32; | ||||
|             width  |=  ((bytes[6] as u32) <<  8) as u32; | ||||
|             width  |=  (bytes[7])                as u32; | ||||
|             height |=  ((bytes[8] as u32) << 24) as u32; | ||||
|             height |=  ((bytes[9] as u32) << 16) as u32; | ||||
|             height |= ((bytes[10] as u32) <<  8) as u32; | ||||
|             height |= (bytes[11])                as u32; | ||||
|             return Ok((width, height, bytes[12], bytes[13])); | ||||
|         } else { | ||||
|             return Err(ImgError::HeaderError); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn read_tag(tag: u8) -> Result<ChunkType,ImgError> { | ||||
|         if tag == QOI_OP_RGB { | ||||
|             return Ok(ChunkType::RGB); | ||||
|         }  | ||||
|         if tag == QOI_OP_RGBA { | ||||
|             return Ok(ChunkType::RGBA); | ||||
|         } | ||||
|         if (tag & 0b1100_0000) == QOI_OP_DIFF{ | ||||
|             return Ok(ChunkType::Diff); | ||||
|         } | ||||
|         if (tag & 0b1100_0000) == QOI_OP_INDEX { | ||||
|             return Ok(ChunkType::Index); | ||||
|         } | ||||
|         if (tag & 0b1100_0000) == QOI_OP_LUMA { | ||||
|             return Ok(ChunkType::Luma); | ||||
|         } | ||||
|         if (tag & 0b1100_0000) == QOI_OP_RUN { | ||||
|             return Ok(ChunkType::Run); | ||||
|         } | ||||
|         return Err(ImgError::DecodeError); | ||||
|     } | ||||
|  | ||||
|     fn dec_rgb(bytes: &[u8], alpha: u8) -> Pixel { | ||||
|         let pixel: Pixel = Pixel::new(bytes[1], bytes[2], bytes[3], alpha); | ||||
|         pixel | ||||
|     } | ||||
|  | ||||
|     fn dec_rgba(bytes: &[u8]) -> Pixel { | ||||
|         let pixel: Pixel = Pixel::new(bytes[1], bytes[2], bytes[3], bytes[4]); | ||||
|         pixel | ||||
|     } | ||||
|  | ||||
|     fn dec_diff(byte: u8, prev_pixel: &Pixel) -> Pixel { | ||||
|         let dr: u8; | ||||
|         let dg: u8; | ||||
|         let db: u8; | ||||
|  | ||||
|         dr = (byte & 0b00110000) >> 4; | ||||
|         dg = (byte & 0b00001100) >> 2; | ||||
|         db =  byte & 0b00000011;        | ||||
|          | ||||
|         let r: u8 = prev_pixel.r.wrapping_add(dr); | ||||
|         let g: u8 = prev_pixel.g.wrapping_add(dg); | ||||
|         let b: u8 = prev_pixel.b.wrapping_add(db); | ||||
|  | ||||
|         let r: u8 = r.wrapping_sub(DIFF_BIAS); | ||||
|         let b: u8 = b.wrapping_sub(DIFF_BIAS); | ||||
|         let g: u8 = g.wrapping_sub(DIFF_BIAS); | ||||
|  | ||||
|         let pixel: Pixel = Pixel::new(r,g,b, prev_pixel.a); | ||||
|         pixel | ||||
|     } | ||||
|  | ||||
|     fn dec_luma(bytes: &[u8], prev_pixel: &Pixel) -> Pixel { | ||||
|         let dr: u8; | ||||
|         let dr_dg: u8; | ||||
|         let db_dg: u8; | ||||
|         let dg: u8; | ||||
|         let db: u8; | ||||
|  | ||||
|         dg =     bytes[0] & 0b00111111; | ||||
|         dr_dg = (bytes[1] & 0b11110000) >> 4; | ||||
|         db_dg =  bytes[1] & 0b00001111; | ||||
|         dr = dr_dg + dg; | ||||
|         db = db_dg + dg; | ||||
|  | ||||
|         let r: u8 = prev_pixel.r.wrapping_add(dr); | ||||
|         let g: u8 = prev_pixel.g.wrapping_add(dg); | ||||
|         let b: u8 = prev_pixel.b.wrapping_add(db); | ||||
|  | ||||
|         let r: u8 = r.wrapping_sub(LUMA_BIAS_RB + LUMA_BIAS_G); | ||||
|         let g: u8 = g.wrapping_sub(LUMA_BIAS_G); | ||||
|         let b: u8 = b.wrapping_sub(LUMA_BIAS_RB + LUMA_BIAS_G); | ||||
|  | ||||
|         let pixel: Pixel = Pixel::new(r, g, b, prev_pixel.a); | ||||
|         pixel | ||||
|     } | ||||
|  | ||||
|     fn dec_run() {} | ||||
|      | ||||
|     pub fn decode(mut bytes: Vec<u8>) -> Result<Image, ImgError> { | ||||
|         let width: u32; | ||||
|         let height: u32; | ||||
|         let channels: u8; | ||||
|         let colorspace: u8; | ||||
|  | ||||
|         let mut prev_pixel: Pixel = Pixel {r: 0u8, g: 0u8, b: 0u8, a: 255u8}; | ||||
|  | ||||
|         let mut prev_buffer: [Pixel; 64] = array_init::array_init(|_| Pixel::new(0,0,0,0)); | ||||
|          | ||||
|         match read_header(&bytes[0..14]) { | ||||
|             Ok((w, h, ch, c))=> { | ||||
|                 width = w; | ||||
|                 height = h; | ||||
|                 channels = ch; | ||||
|                 colorspace = c; | ||||
|             }, | ||||
|             Err(err) => { | ||||
|                 return Err(err); | ||||
|             } | ||||
|         } | ||||
|         let mut pixels: Vec<Pixel> = Vec::with_capacity((width*height*4) as usize); | ||||
|  | ||||
|         if bytes[bytes.len()-1] == 1 { | ||||
|             for i in 2..9 { | ||||
|                 if bytes[bytes.len()-i] != 0 { | ||||
|                     debug!("Ending bytes not present."); | ||||
|                     return Err(ImgError::DecodeError); | ||||
|                 } | ||||
|             } | ||||
|             for i in 0..8 { | ||||
|                 bytes.pop(); | ||||
|             } | ||||
|         } else { | ||||
|             debug!("Ending bytes not present."); | ||||
|             return Err(ImgError::DecodeError); | ||||
|         } | ||||
|          | ||||
|         let mut i: usize = 14; | ||||
|  | ||||
|         while i < bytes.len() { | ||||
|             match read_tag(bytes[i]) { | ||||
|                 Ok(tag) => { | ||||
|                     match tag { | ||||
|                         ChunkType::RGB => { | ||||
|                             let dec_pix: Pixel = dec_rgb(&bytes[i..i+4], prev_pixel.a); | ||||
|                             prev_pixel = dec_pix.clone(); | ||||
|                             prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone(); | ||||
|                             pixels.push(dec_pix); | ||||
|                             i += 3; | ||||
|                         }, | ||||
|                         ChunkType::RGBA => { | ||||
|                             let dec_pix: Pixel = dec_rgba(&bytes[i..i+5]); | ||||
|                             prev_pixel = dec_pix.clone(); | ||||
|                             prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone(); | ||||
|                             pixels.push(dec_pix); | ||||
|                             i += 4; | ||||
|                         }, | ||||
|                         ChunkType::Diff => { | ||||
|                             let dec_pix: Pixel = dec_diff(bytes[i], &prev_pixel); | ||||
|                             prev_pixel = dec_pix.clone(); | ||||
|                             prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone(); | ||||
|                             pixels.push(dec_pix); | ||||
|                         }, | ||||
|                         ChunkType::Index => { | ||||
|                             let dec_pix: Pixel = prev_buffer[bytes[i] as usize]; | ||||
|                             prev_pixel = dec_pix.clone(); | ||||
|                             prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone(); | ||||
|                             pixels.push(dec_pix); | ||||
|                         }, | ||||
|                         ChunkType::Luma => { | ||||
|                             let dec_pix: Pixel = dec_luma(&bytes[i..i+2], &prev_pixel); | ||||
|                             prev_pixel = dec_pix.clone(); | ||||
|                             prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone(); | ||||
|                             pixels.push(dec_pix); | ||||
|                             i += 1; | ||||
|                         }, | ||||
|                         ChunkType::Run => { | ||||
|                             let length: u8 = (bytes[i] & 0b00111111) + RUN_BIAS; | ||||
|                             for j in 0..length { | ||||
|                                 pixels.push(prev_pixel.clone()); | ||||
|                             } | ||||
|                             prev_buffer[color_hash(&prev_pixel) as usize] = prev_pixel.clone(); | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 Err(err) => return Err(err), | ||||
|             } | ||||
|             i += 1; | ||||
|         } | ||||
|  | ||||
|         if pixels.len() as u32 != height*width { | ||||
|             debug!("h*w: {}", height*width); | ||||
|             debug!("n pixels: {}", pixels.len()); | ||||
|             return Err(ImgError::DecodeError); | ||||
|         } | ||||
|  | ||||
|         let img = Image::from_pixels(pixels, height, width, channels, colorspace); | ||||
|         Ok(img) | ||||
|  | ||||
|     } | ||||
|  | ||||
|     #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use std::io; | ||||
|     use std::io::{Read, BufReader}; | ||||
|     use std::fs::File; | ||||
|  | ||||
|     #[test] | ||||
|     fn diff_test() { | ||||
|         init().expect("Logger initialisation failed!"); | ||||
|         let pix1: Pixel = Pixel::new(  0,  0,  0,255); | ||||
|         let pix2: Pixel = Pixel::new(255,255,255,255); | ||||
|  | ||||
|         let pix3: Pixel = Pixel::new(155,155,155,255); | ||||
|         let pix4: Pixel = Pixel::new(160,160,160,255); | ||||
|  | ||||
|         assert_eq!(pix1.diff(&pix2), ( 1, 1, 1)); | ||||
|         assert_eq!(pix2.diff(&pix1), (-1,-1,-1)); | ||||
|         assert_eq!(pix4.diff(&pix3), ( 5, 5, 5)); | ||||
|         assert_eq!(pix3.diff(&pix4), (-5,-5,-5)); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn encode_test() -> io::Result<()> { | ||||
|         let f: File = File::open("test.qoi")?; | ||||
|         let mut reader = BufReader::new(f); | ||||
|         let mut bytes: Vec<u8> = Vec::new(); | ||||
|  | ||||
|         reader.read_to_end(&mut bytes)?; | ||||
|  | ||||
|         let out_img: super::Image; | ||||
|  | ||||
|         match super::decode(bytes) { | ||||
|             Ok(img) => out_img = img, | ||||
|             Err(err) => panic!("wallah geht nicht :/ {:?}", err) | ||||
|         } | ||||
|         write_to_file(encode_from_image(out_img), "test_dec").expect("wallahi!"); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn decode_test() -> io::Result<()> { | ||||
|         let f: File = File::open("testcard_rgba.qoi")?; | ||||
|         let mut reader = BufReader::new(f); | ||||
|         let mut bytes: Vec<u8> = Vec::new(); | ||||
|  | ||||
|         reader.read_to_end(&mut bytes)?; | ||||
|  | ||||
|         let out_img: super::Image; | ||||
|  | ||||
|         match super::decode(bytes) { | ||||
|             Ok(img) => out_img = img, | ||||
|             Err(err) => panic!("Ja bruder war nicht so erfolgreich ahahahahahha {:?}", err) | ||||
|         } | ||||
|  | ||||
|         write_to_file(encode_from_image(out_img), "testcard_new").expect("Boowomb!"); | ||||
|  | ||||
|         Ok(()) | ||||
|  | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn tag_test() { | ||||
|         //init().expect("Logger initialisation failed!"); | ||||
|         let test_rgb:   u8 = 0b1111_1110; | ||||
|         let test_rgba:  u8 = 0b1111_1111; | ||||
|         let test_luma:  u8 = 0b1011_1010; | ||||
|         let test_run:   u8 = 0b1110_1101; | ||||
|         let test_diff:  u8 = 0b0110_1010; | ||||
|         let test_index: u8 = 0b0010_1010; | ||||
|  | ||||
|  | ||||
|         assert_eq!(Ok(ChunkType::RGB), super::read_tag(test_rgb)); | ||||
|         assert_eq!(Ok(ChunkType::RGBA), super::read_tag(test_rgba)); | ||||
|         assert_eq!(Ok(ChunkType::Luma), super::read_tag(test_luma)); | ||||
|         assert_eq!(Ok(ChunkType::Diff), super::read_tag(test_diff)); | ||||
|         assert_eq!(Ok(ChunkType::Index), super::read_tag(test_index)); | ||||
|         assert_eq!(Ok(ChunkType::Run), super::read_tag(test_run)); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn sub_decoders_test() { | ||||
|         //init().expect("Logger initialisation failed!"); | ||||
|         let pix: Pixel  = Pixel { r: 255, g: 255, b: 255, a: 255 }; | ||||
|         let prev: Pixel = Pixel { r:   1, g:   1, b:   1, a: 255 }; | ||||
|         let byte: u8    = 0b01000000; | ||||
|  | ||||
|         assert_eq!(pix,dec_diff(byte, &prev)); | ||||
|  | ||||
|         let pix: Pixel = Pixel { r: 17, g: 22, b: 28, a: 100 }; | ||||
|         let prev: Pixel = Pixel { r: 10, g: 10, b: 10, a: 100 }; | ||||
|         let byte: [u8;2] = [ 0b10101100, 0b00111110 ]; | ||||
|  | ||||
|         assert_eq!(pix, dec_luma(&byte[0..2], &prev)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
							
								
								
									
										256
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,256 @@ | ||||
| use std::env; | ||||
| use std::fs::File; | ||||
| use std::io; | ||||
| use std::io::{BufReader, Read}; | ||||
| use std::time::SystemTime; | ||||
|  | ||||
| use colors_transform::{Color, Hsl, Rgb}; | ||||
| use png; | ||||
| use qoi_test::qoi_img::*; | ||||
|  | ||||
| fn encode_checkerboard() { | ||||
|     let mut pixels: Vec<Pixel> = Vec::with_capacity(64 * 64); | ||||
|     let red: u8 = 150; | ||||
|     let green: u8 = 0; | ||||
|     let blue: u8 = 150; | ||||
|     //row iterator | ||||
|     for i in 0..64 { | ||||
|         //column iterator | ||||
|         for j in 0..64 { | ||||
|             //if row is 0..16, 32..48 | ||||
|             if (i / 16) == 0 || (i / 16) == 2 { | ||||
|                 //if column is 0..16, 32..48 | ||||
|                 if (j / 16) == 0 || (j / 16) == 2 { | ||||
|                     let push_pix: Pixel = Pixel::new(red, green, blue, 255); | ||||
|                     pixels.push(push_pix); | ||||
|                 } else { | ||||
|                     let push_pix: Pixel = Pixel::new(255, 255, 255, 255); | ||||
|                     pixels.push(push_pix); | ||||
|                 } | ||||
|             } else { | ||||
|                 if (j / 16) == 1 || (j / 16) == 3 { | ||||
|                     let push_pix: Pixel = Pixel::new(red, green, blue, 255); | ||||
|                     pixels.push(push_pix); | ||||
|                 } else { | ||||
|                     let push_pix: Pixel = Pixel::new(255, 255, 255, 255); | ||||
|                     pixels.push(push_pix); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let img: Image = Image::from_pixels(pixels, 64, 64, 4, 0); | ||||
|     write_to_file(encode_from_image(img), "checkerboard").expect("Error writing file!"); | ||||
| } | ||||
|  | ||||
| fn encode_debug() { | ||||
|     let mut img_data: Vec<u8> = Vec::new(); | ||||
|     //row iterator | ||||
|     for i in 0..1024 { | ||||
|         //cell iterator | ||||
|         for j in 0..1024 { | ||||
|             //subpixel iterator | ||||
|             for k in 0..4 { | ||||
|                 let rgb: Hsl = Hsl::from(0.3515625 * j as f32, 100.0, 50.0); | ||||
|                 let rgb: Rgb = rgb.to_rgb(); | ||||
|                 let alpha: f64 = -(255.0 / 1024.0) * (i as f64) + 255.0; | ||||
|                 match k { | ||||
|                     0 => img_data.push(rgb.get_red() as u8), | ||||
|                     1 => img_data.push(rgb.get_green() as u8), | ||||
|                     2 => img_data.push(rgb.get_blue() as u8), | ||||
|                     3 => img_data.push(alpha as u8), | ||||
|                     _ => panic!("unrecoverable for-loop failure"), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     let img: Image = match Image::new(img_data, 1024, 1024, 4, 0) { | ||||
|         Ok(image) => image, | ||||
|         Err(err) => panic!("Problem generating image: {:?}", err), | ||||
|     }; | ||||
|     let start = SystemTime::now(); | ||||
|     let img_bytes: Vec<u8> = encode_from_image(img); | ||||
|     let stop = match start.elapsed() { | ||||
|         Ok(elapsed) => elapsed.as_millis(), | ||||
|         Err(e) => { | ||||
|             println!("Error: {e:?}"); | ||||
|             return (); | ||||
|         } | ||||
|     }; | ||||
|     println!("Encode took: {} ms.", stop); | ||||
|     write_to_file(img_bytes, "test").expect("Error writing file!"); | ||||
| } | ||||
|  | ||||
| fn demo() { | ||||
|     let decoder = png::Decoder::new(File::open("testcard_rgba.png").unwrap()); | ||||
|     let mut reader = match decoder.read_info() { | ||||
|         Ok(reader) => reader, | ||||
|         Err(e) => panic!("ERROR: couldn't read file: {e:}"), | ||||
|     }; | ||||
|     let mut buf = vec![0; reader.output_buffer_size()]; | ||||
|  | ||||
|     let info = match reader.next_frame(&mut buf) { | ||||
|         Ok(i) => i, | ||||
|         Err(e) => panic!("ERROR: {e:?}"), | ||||
|     }; | ||||
|  | ||||
|     let bytes = &buf[..info.buffer_size()]; | ||||
|     let byte_vec: Vec<u8> = bytes.to_vec(); | ||||
|  | ||||
|     let img: Image = match Image::new(byte_vec, 256, 256, 4, 0) { | ||||
|         Ok(image) => image, | ||||
|         Err(err) => panic!("Problem generating imag: {:?}", err), | ||||
|     }; | ||||
|     let start = SystemTime::now(); | ||||
|     write_to_file(encode_from_image(img), "img").expect("Error writing file!"); | ||||
|     let stop = match start.elapsed() { | ||||
|         Ok(elapsed) => elapsed.as_millis(), | ||||
|         Err(e) => { | ||||
|             println!("Error: {e:?}"); | ||||
|             return (); | ||||
|         } | ||||
|     }; | ||||
|     println!("Encode took: {} ms.", stop); | ||||
|     let start = SystemTime::now(); | ||||
|     encode_checkerboard(); | ||||
|     let stop = match start.elapsed() { | ||||
|         Ok(elapsed) => elapsed.as_millis(), | ||||
|         Err(e) => { | ||||
|             println!("Error: {e:?}"); | ||||
|             return (); | ||||
|         } | ||||
|     }; | ||||
|     println!("Encode took: {} ms.", stop); | ||||
|     encode_debug(); | ||||
| } | ||||
|  | ||||
| //Attempts to encode given png image as second argument into qoi | ||||
| fn encode(args: &Vec<String>) { | ||||
|     //Path is fetched from arguments | ||||
|     let path = &args[2]; | ||||
|  | ||||
|     //Init png decoder, attempt to decode png into bitmap, throw error if unsuccessful | ||||
|     let decoder = png::Decoder::new(File::open(path).unwrap()); | ||||
|     let mut reader = match decoder.read_info() { | ||||
|         Ok(reader) => reader, | ||||
|         Err(e) => panic!("ERROR: couldn't read file: {e:}"), | ||||
|     }; | ||||
|  | ||||
|     //read image metadata | ||||
|     let width: u32 = reader.info().width; | ||||
|     let height: u32 = reader.info().height; | ||||
|     //for now: hardcoded to 4 | ||||
|     let channels: u8 = 4; | ||||
|  | ||||
|     //create buffer matching the size of png-decoder output, writing size to output | ||||
|     let mut buf = vec![0; reader.output_buffer_size()]; | ||||
|     let info = match reader.next_frame(&mut buf) { | ||||
|         Ok(i) => i, | ||||
|         Err(e) => panic!("ERROR: {e:?}"), | ||||
|     }; | ||||
|  | ||||
|     //convert buffer into vector | ||||
|     let bytes = &buf[..info.buffer_size()]; | ||||
|     let byte_vec: Vec<u8> = bytes.to_vec(); | ||||
|  | ||||
|     //create bitmap data from raw byte vector | ||||
|     let img: Image = match Image::new(byte_vec, height, width, channels, 0) { | ||||
|         Ok(image) => image, | ||||
|         Err(err) => panic!("Problem generating image: {:?}", err), | ||||
|     }; | ||||
|  | ||||
|     //encode generated bitmap | ||||
|     if args.len() >= 4 { | ||||
|         let filename: &String = &args[3]; | ||||
|         write_to_file(encode_from_image(img), filename).expect("ERROR: Can't write file."); | ||||
|     } else { | ||||
|         let mut filename = path.clone(); | ||||
|         for _i in 0..4 { | ||||
|             filename.pop(); | ||||
|         } | ||||
|         write_to_file(encode_from_image(img), filename.as_str()).expect("ERROR: Can't write file."); | ||||
|     } | ||||
|     println!("Encoding successful!"); | ||||
| } | ||||
|  | ||||
| fn decode(args: &Vec<String>) -> io::Result<()> { | ||||
|     let mut path: String = String::new(); | ||||
|     if args.len() > 2 { | ||||
|         path.push_str(args[2].as_str()); | ||||
|     } else { | ||||
|         println!("ERROR: incorrect number of arguments! (specify file to decode!)."); | ||||
|         () | ||||
|     } | ||||
|  | ||||
|     let f: File = match File::open(path.as_str()) { | ||||
|         Ok(f) => f, | ||||
|         Err(e) => panic!("ERROR: {e:?}"), | ||||
|     }; | ||||
|     let mut reader = BufReader::new(f); | ||||
|     let mut bytes: Vec<u8> = Vec::new(); | ||||
|  | ||||
|     reader.read_to_end(&mut bytes)?; | ||||
|  | ||||
|     match qoi_test::qoi_img::decode(bytes) { | ||||
|         Ok(_img) => println!("Decoding successful!"), | ||||
|         Err(err) => panic!("ERROR: {err:?}"), | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn bench(args: &Vec<String>) { | ||||
|     if args.len() < 4 { | ||||
|         panic!("ERROR: invalid number of arguments!"); | ||||
|     } | ||||
|  | ||||
|     let start = SystemTime::now(); | ||||
|     encode(args); | ||||
|     match start.elapsed() { | ||||
|         Ok(elapsed) => println!("Encode took {} μs", elapsed.as_micros()), | ||||
|         Err(e) => panic!("ERROR: {e:?}"), | ||||
|     } | ||||
|     let mut new_arg: Vec<String> = Vec::new(); | ||||
|     new_arg.push(String::from("")); | ||||
|     new_arg.push(String::from("")); | ||||
|     let mut to_push: String = args[3].clone(); | ||||
|     to_push.push_str(".qoi"); | ||||
|     new_arg.push(to_push); | ||||
|     let start = SystemTime::now(); | ||||
|     decode(&new_arg).expect( | ||||
|         "ERROR: Unspecified error during io-pipeline. Ensure file path is valid and can be read.", | ||||
|     ); | ||||
|     match start.elapsed() { | ||||
|         Ok(elapsed) => println!("Decode took {} μs", elapsed.as_micros()), | ||||
|         Err(e) => panic!("ERROR: {e:?}"), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     //Initialize logger | ||||
|     init().expect("Failed to initialize logger."); | ||||
|  | ||||
|     let args: Vec<String> = env::args().collect(); | ||||
|  | ||||
|     if args.len() == 1 { | ||||
|         panic!("ERROR: no arguments send!"); | ||||
|     } | ||||
|  | ||||
|     match args[1].as_str() { | ||||
|         "demo" => { | ||||
|             demo(); | ||||
|         } | ||||
|         //can only handle pngs for now | ||||
|         "encode" => { | ||||
|             encode(&args); | ||||
|         } | ||||
|         "decode" => { | ||||
|             decode(&args).expect("ERROR: Unspecified error during io-pipeline. Ensure file path is valid and can be read."); | ||||
|         } | ||||
|         "bench" => { | ||||
|             bench(&args); | ||||
|         } | ||||
|         _ => { | ||||
|             panic!("Invalid arguments!") | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								testcard_rgba.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								testcard_rgba.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								testcard_rgba.qoi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								testcard_rgba.qoi
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user