From 140476e3a04d78d1793ec97d68335476b7c27c4b Mon Sep 17 00:00:00 2001 From: Nihil Carcosa Date: Thu, 14 Nov 2024 16:52:58 +0100 Subject: [PATCH] fixed all bugs in de- and encoder, version bump to 0.9.0 --- .gitignore | 2 + Cargo.lock | 227 +++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 7 +- src/lib.rs | 217 ++++++++++++++++++++++++++++++++++++++----------- src/main.rs | 29 ------- 5 files changed, 392 insertions(+), 90 deletions(-) diff --git a/.gitignore b/.gitignore index c949d80..8eaa2ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target /qoi_test_images +*.qoi +*.png diff --git a/Cargo.lock b/Cargo.lock index 6fb00cb..1b053c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,61 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "array-init" version = "2.0.1" @@ -26,6 +81,39 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "colors-transform" version = "0.2.11" @@ -41,6 +129,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "fdeflate" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +dependencies = [ + "simd-adler32", +] + [[package]] name = "flate2" version = "1.0.24" @@ -48,17 +145,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.5.4", ] [[package]] -name = "log" -version = "0.4.17" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "miniz_oxide" @@ -70,23 +170,126 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.6" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags", "crc32fast", + "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] name = "qoi" -version = "0.1.1" +version = "0.9.0" dependencies = [ "array-init", + "clap", "colors-transform", "log", "png", ] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index f9017fc..7525718 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qoi" -version = "0.1.1" +version = "0.9.0" edition = "2021" authors = ["nihil carcosa "] description = "CLI tool and rust library for the de- and encoding of images to the QOI format." @@ -18,6 +18,7 @@ publish = false [dependencies] array-init = "2.0.1" -png = "0.17.6" -log = "0.4.17" +png = "0.17.14" +log = "0.4.22" colors-transform = "0.2.11" +clap = "4.5.21" diff --git a/src/lib.rs b/src/lib.rs index 1a85fb3..36b4364 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,14 @@ //! # 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_lib { use log::{debug, info, Level, LevelFilter, Record, SetLoggerError}; use std::fmt; - use std::fs::File; + use std::fs::*; use std::io::prelude::*; + use array_init; @@ -60,7 +60,7 @@ pub mod qoi_lib { /// /// ``` /// # use std::error::Error; - /// # use crate::qoi_test::qoi_img::*; + /// # use crate::qoi::qoi_lib::*; /// # fn main() -> Result<(), Box> { /// init().expect("Failed to initialize logger"); /// # @@ -73,7 +73,7 @@ pub mod qoi_lib { /// /// ``` /// # use std::error::Error; - /// # use crate::qoi_test::qoi_img::*; + /// # use crate::qoi::qoi_lib::*; /// # fn main() -> Result<(), ImgError> { /// match init() { /// Ok(()) => (), @@ -97,7 +97,7 @@ pub mod qoi_lib { /// Create a new image via constructor [`Image::new()`]; /// ```rust /// # use std::error::Error; - /// # use crate::qoi_test::qoi_img::*; + /// # use crate::qoi::qoi_lib::*; /// # fn main() -> Result<(), Box> { /// /// let pixels: Vec = vec![0;1024*1024*4]; @@ -129,7 +129,13 @@ pub mod qoi_lib { channels: u8, colorspace: u8, ) -> Result { - let pixels: Vec = match Image::pixels_from_bytes(data) { + let alpha: bool; + if channels == 4 { + alpha = true; + } else { + alpha = false; + } + let pixels: Vec = match Image::pixels_from_bytes(data, alpha) { Ok(out) => out, Err(error) => return Err(error), }; @@ -164,21 +170,39 @@ pub mod qoi_lib { img } - fn pixels_from_bytes(data: Vec) -> Result, ImgError> { + //Expects pixel data in order left to right, top to bottom, with values for rgba in sequential order + fn pixels_from_bytes(data: Vec, alpha: bool) -> Result, ImgError> { let mut pixels: Vec = 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], - }); + if alpha { + 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) } - Ok(pixels) } else { - Err(ImgError::DataError) + if data.len() % 4 == 0 { + for i in 0..data.len() / 3 { + pixels.push(Pixel { + r: data[i * 3], + g: data[i * 3 + 1], + b: data[i * 3 + 2], + a: 255, + }); + } + Ok(pixels) + } else { + Err(ImgError::DataError) + } } + } } @@ -378,6 +402,7 @@ pub mod qoi_lib { (store % 64) as u8 } + //Definitely broken in some way pub fn encode_from_image(img: Image) -> Vec { let mut prev_pixel: Pixel = Pixel { r: 0u8, @@ -399,7 +424,7 @@ pub mod qoi_lib { } let mut encoded_bytes: Vec = Vec::new(); - let mut run: u8 = 0; + let mut run: u64 = 0; let head = Header { magic: ['q', 'o', 'i', 'f'], @@ -414,7 +439,7 @@ pub mod qoi_lib { encoded_bytes.push(i); } - let mut counter: u32 = 0; + let mut counter: u64 = 0; for pixel in img.pixels { counter += 1; @@ -432,18 +457,21 @@ pub mod qoi_lib { 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)); + let run_remainder: u8 = run.try_into().unwrap(); + encoded_bytes.push(QOI_OP_RUN | (run_remainder - RUN_BIAS)); run = 0; } else { break; } } } else { - encoded_bytes.push(QOI_OP_RUN | (run - RUN_BIAS)); + let run8: u8 = run.try_into().unwrap(); + encoded_bytes.push(QOI_OP_RUN | (run8 - RUN_BIAS)); run = 0; } } + match chunk { (ChunkType::Index, Some((index, irr1, irr2))) => { encoded_bytes.push(QOI_OP_INDEX | index); @@ -509,14 +537,17 @@ pub mod qoi_lib { 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)); + let run_remainder: u8 = run.try_into().unwrap(); + encoded_bytes.push(QOI_OP_RUN | (run_remainder - RUN_BIAS)); run = 0; } else { break; } } } else { - encoded_bytes.push(QOI_OP_RUN | (run - RUN_BIAS)); + let run8: u8 = run.try_into().unwrap(); + encoded_bytes.push(QOI_OP_RUN | (run8 - RUN_BIAS)); + // run = 0; } } @@ -760,10 +791,13 @@ pub mod qoi_lib { #[cfg(test)] mod tests { + + use png::ColorType; + use super::*; - use std::fs::File; use std::io; use std::io::{BufReader, Read}; + use std::path::*; #[test] fn diff_test() { @@ -781,40 +815,131 @@ pub mod qoi_lib { } #[test] - fn encode_test() -> io::Result<()> { - let f: File = File::open("test.qoi")?; - let mut reader = BufReader::new(f); - let mut bytes: Vec = Vec::new(); + fn qoi_to_qoi_test() -> io::Result<()> { + //Open path to test images + let path: &Path = Path::new("./qoi_test_images/"); + let dir: ReadDir = match path.read_dir() { + Ok(d) => d, + Err(e) => panic!("Error reading path {e:?}"), + }; + //Loop over files in directory, attempt to decode .qoi images and reencode + for entry in dir { - reader.read_to_end(&mut bytes)?; + let file_path = match entry { + Ok(d) => d.path(), + Err(e) => panic!("Non-functional dir entry! \n {e:?}") + }; + let file_path_str = match file_path.to_str() { + Some(s) => s, + None => "" + }; + if !(file_path_str.contains(".qoi")) { + continue; + } + + let file = match File::open(&file_path) { + Ok(f) => f, + Err(e) => panic!("Error reading file with path {:?}", file_path_str), + }; + let mut reader = BufReader::new(file); + let mut bytes: Vec = Vec::new(); - let out_img: super::Image; + reader.read_to_end(&mut bytes)?; - match super::decode(bytes) { - Ok(img) => out_img = img, - Err(err) => panic!("wallah geht nicht :/ {:?}", err), + let output_image: super::Image; + match super::decode(bytes) { + Ok(img) => output_image = img, + Err(err) => panic!("Image decode failed for {:?}" , file_path.to_str()) + } + let mut name = match file_path.file_name() { + Some(s) => match s.to_str() { + Some(ss) => ss, + None => panic!("File Name Error!") + }, + None => panic!("File Name Error!"), + }; + name = match name.strip_suffix(".qoi") { + Some(n) => n, + None => name, + }; + write_to_file(encode_from_image(output_image), name).expect("Writing image failed!"); } - 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 = Vec::new(); + fn png_to_qoi_test() -> io::Result<()> { + //Open path to test images + let path: &Path = Path::new("./qoi_test_images/"); + let dir: ReadDir = match path.read_dir() { + Ok(d) => d, + Err(e) => panic!("Error reading path {e:?}"), + }; + //Loop over files in directory, attempt to decode png and encode as qoi, compare to qoi + for entry in dir { - reader.read_to_end(&mut bytes)?; + let file_path = match entry { + Ok(d) => d.path(), + Err(e) => panic!("Non-functional dir entry! \n {e:?}") + }; + let file_path_str = match file_path.to_str() { + Some(s) => s, + None => "" + }; + if !(file_path_str.contains(".png")) { + continue; + } + println!("{:}",file_path_str); + let file = match File::open(&file_path) { + Ok(f) => f, + Err(e) => panic!("Cannot read file! \n {e:?}") + }; + let decoder = png::Decoder::new(file); + let mut reader = match decoder.read_info() { + Ok(reader) => reader, + Err(e) => panic!("ERROR: couldn't decode file: {e:}"), + }; + //read image metadata + let width: u32 = reader.info().width; + let height: u32 = reader.info().height; + //for now: hardcoded to 4 + let channels = match reader.info().color_type { + ColorType::Rgb => 3, + ColorType::Rgba => 4, + _ => panic!("ERROR: Incompatible png file!") + }; - let out_img: super::Image; + //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:?}"), + }; + let bytes = &buf[..info.buffer_size()]; + let byte_vec: Vec = bytes.to_vec(); - match super::decode(bytes) { - Ok(img) => out_img = img, - Err(err) => panic!("Ja bruder war nicht so erfolgreich ahahahahahha {:?}", err), + //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), + }; + + let encoded_buffer = super::encode_from_image(img); + let mut name = match file_path.file_name() { + None => panic!("whoops!"), + Some(n) => match n.to_str() { + None => panic!("im shiddin"), + Some(s) => s, + }, + }; + name = match name.strip_suffix(".png") { + Some(n) => n, + None => name, + }; + write_to_file(encoded_buffer,name ).expect("Can't write resulting file!"); } - - write_to_file(encode_from_image(out_img), "testcard_new").expect("Boowomb!"); - + Ok(()) } diff --git a/src/main.rs b/src/main.rs index c91051c..4d96d82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,35 +82,6 @@ fn encode_debug() { } 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 = 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() {