commit ea18938dcf3aee56fd0583511bb9f97279e73239 Author: Nyeogmi Date: Wed Apr 10 12:04:23 2024 -0700 what the fuck help diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d21fdd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +outputs/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ec17418 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + ".\\Cargo.toml" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6e39e13 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,277 @@ +# 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 = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[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 = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rx0" +version = "0.1.0" +dependencies = [ + "png", + "quickcheck", + "quickcheck_macros", + "thiserror", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..673d7a5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rx0" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +png = "0.17.13" +thiserror = "1.0.58" + +[dev-dependencies] +quickcheck = "1.0.3" +quickcheck_macros = "1.0.0" diff --git a/inputs/avatar2.png b/inputs/avatar2.png new file mode 100644 index 0000000..bc57ab7 Binary files /dev/null and b/inputs/avatar2.png differ diff --git a/inputs/zonked.png b/inputs/zonked.png new file mode 100644 index 0000000..ce4f73c Binary files /dev/null and b/inputs/zonked.png differ diff --git a/src/decode_image.rs b/src/decode_image.rs new file mode 100644 index 0000000..cb41b1c --- /dev/null +++ b/src/decode_image.rs @@ -0,0 +1,56 @@ +use std::io::Read; + +use crate::{decode_tile, image::Image, protocol::{self, ProtocolReaderResult}}; + +pub fn decode_image( + reader: &mut protocol::ProtocolReader +) -> ProtocolReaderResult { + let width = reader.read_u16()? as usize; + let height = reader.read_u16()? as usize; + + let mut layer_quality = [0_u8; 4]; + for i in 0..4 { + layer_quality[i] = reader.read_u8()?; + } + + let mut n_layers = 0; + for i in (0..4).rev() { + if layer_quality[i] != 0 { + n_layers = i + 1; + break; + } + } + + let mut layer_pixels = vec![]; + for _ in 0..n_layers { + layer_pixels.push(vec![0_u8; width as usize * height as usize]) + } + + for y0 in (0..height).step_by(16) { + for x0 in (0..width).step_by(16) { + for layer in 0..n_layers { + let mut data = [0; 256]; + + decode_tile::decode_tile(&mut data, layer_quality[layer], reader)?; + + for y in 0..16 { + for x in 0..16 { + let src_x = x0 + x; + let src_y = y0 + y; + if src_x < width && src_y < height { + layer_pixels[layer][src_y * width + src_x] = + data[y * 16 + x]; + } + } + } + } + } + } + + let mut image = Image::new(width, height); + for (layer, pixels) in layer_pixels.into_iter().enumerate() { + image.add(layer_quality[layer], pixels) + .expect("should be correct by construction"); + } + Ok(image) +} \ No newline at end of file diff --git a/src/decode_tile.rs b/src/decode_tile.rs new file mode 100644 index 0000000..41c1833 --- /dev/null +++ b/src/decode_tile.rs @@ -0,0 +1,59 @@ +use std::io::Read; + +use crate::protocol::{ProtocolReader, ProtocolReaderResult}; + +pub fn decode_tile( + data: &mut [u8; 256], + quality: u8, + reader: &mut ProtocolReader +) -> ProtocolReaderResult<()> { + let mut coefs = [0_i32; 256]; + + for _ in 0..quality { + let ix = reader.read_u8()?; + let val = reader.read_u16()? as i32; + coefs[ix as usize] = val; + } + + let mut transform = |zero: usize, stride: usize| { + let mut ixs = [0; 16]; + ixs[0] = zero; + for i in 1..16 { ixs[i] = ixs[i - 1] + stride; } + + for bit in [8, 4, 2, 1] { + for i in 0..16 { + if i & bit == 0 { + let j = i | bit; + + let ival = coefs[ixs[i]]; + let jval = coefs[ixs[j]]; + + coefs[ixs[i]] = ival.wrapping_add(jval); + coefs[ixs[j]] = ival.wrapping_sub(jval); + } + } + } + + for i in 0..16 { + coefs[ixs[i]] /= 16; + } + }; + + for row in 0..16 { transform(row * 16, 1); } + for col in 0..16 { transform(col, 16); } + + for i in 0..256 { + data[i] = coefs[i].max(0).min(255) as u8; + } + Ok(()) +} + +pub fn decode_value(enc: u8) -> i32 { + let sign = enc & 0x80 != 0; + let exponent = (enc >> 4) & 0x7; + let mantissa = enc & 0xf; + + let magnitude = ((((mantissa as i32) << 1) | 0b100001) << exponent) - 33; + if sign { return -magnitude * 16 } + return magnitude * 16; +} \ No newline at end of file diff --git a/src/encode_image.rs b/src/encode_image.rs new file mode 100644 index 0000000..40ac597 --- /dev/null +++ b/src/encode_image.rs @@ -0,0 +1,42 @@ +use std::io::Write; + +use crate::{encode_tile, image::Image, protocol::{self, ProtocolWriterResult}}; + +pub fn encode_image( + image: Image, + writer: &mut protocol::ProtocolWriter +) -> ProtocolWriterResult<()> { + assert!(image.layers.len() <= 4); // TODO: Enforce in Image + + writer.write_u16(image.width as u16)?; + writer.write_u16(image.height as u16)?; + + for i in 0..4 { + writer.write_u8(image.layers.get(i).map(|l| l.0).unwrap_or(0))?; + } + + for y0 in (0..image.height).step_by(16) { + for x0 in (0..image.width).step_by(16) { + for (quality, layer) in image.layers.iter() { + let mut data = [0; 256]; + + for y in 0..16 { + for x in 0..16 { + let src_x = x0 + x; + let src_y = y0 + y; + data[y * 16 + x] = + if src_x < image.width && src_y < image.height { + layer[src_y * image.width + src_x] + } else { + 0 + }; + } + } + + encode_tile::encode_tile(data, *quality, writer)? + } + } + } + + Ok(()) +} \ No newline at end of file diff --git a/src/encode_tile.rs b/src/encode_tile.rs new file mode 100644 index 0000000..590149e --- /dev/null +++ b/src/encode_tile.rs @@ -0,0 +1,151 @@ +use std::io::Write; + +use crate::protocol::{ProtocolWriter, ProtocolWriterResult}; + +pub fn encode_tile( + data: [u8; 256], + quality: u8, + writer: &mut ProtocolWriter +) -> ProtocolWriterResult<()> { + let mut coefs: [i32; 256] = [0; 256]; + + for i in 0..256 { + coefs[i] = data[i] as i32; + } + + let mut transform = |zero: usize, stride: usize| { + let mut ixs = [0; 16]; + ixs[0] = zero; + for i in 1..16 { + ixs[i] = ixs[i - 1] + stride; + } + + for bit in [8, 4, 2, 1] { + for i in 0..16 { + if i & bit == 0 { + let j = i | bit; + + let ival = coefs[ixs[i]]; + let jval = coefs[ixs[j]]; + + coefs[ixs[i]] = ival.wrapping_add(jval); + coefs[ixs[j]] = ival.wrapping_sub(jval); + } + } + } + }; + + for col in 0..16 { + transform(col, 16); + } + + for row in 0..16 { + transform(row * 16, 1); + } + + // this operation can be efficiently implemented as a heapsort in C + // but the code is ugly + // so just use Rust's default sort + let mut indices = [0; 256]; + for i in 0..256 { + indices[i] = i; + } + indices.sort_by_key(|ix| -(coefs[*ix]).abs()); + + // write indices and values + // (note that the first index will almost always be zero) + for i in 0..quality { + let ix = indices[i as usize]; + writer.write_u8(ix as u8)?; + writer.write_u16(coefs[ix] as u16)?; + }; + Ok(()) +} + +pub fn encode_value(value: i32) -> u8 { + let value = value / 16; + let sign = value < 0; + let mut rescaled = value.abs() + 33; + let mut exponent = 0; + + while rescaled > 64 { + exponent += 1; + rescaled >>= 1; + } + + let mantissa = (rescaled >> 1) & 0xf; + + return ( + if sign { 128 } else { 0 } | + exponent << 4 | + mantissa + ) as u8; +} + +#[cfg(test)] +mod test { + use quickcheck_macros::quickcheck; + + use crate::decode_tile::decode_value; + + use super::encode_value; + + #[quickcheck] + fn test_encode_value_2(val: u16) -> bool { + let val = (val & (0x8000 | 0x3fff)) as i16; + let canon = decode_value(encode_value(val as i32)); + + canon == decode_value(encode_value(canon as i32)) + } + + #[test] + fn test_encode_value_1() { + // ...01abcdx cases + assert_eq!(0b00001111, encode_value(0 + (0b111110 - 33))); + assert_eq!(0b00001110, encode_value(0 + (0b111100 - 33))); + assert_eq!(0b10001111, encode_value(0 - (0b111110 - 33))); + assert_eq!(0b10001110, encode_value(0 - (0b111100 - 33))); + + // ...01abcdxx cases + assert_eq!(0b00011111, encode_value(0 + (0b1111101 - 33))); + assert_eq!(0b00011110, encode_value(0 + (0b1111001 - 33))); + assert_eq!(0b10011111, encode_value(0 - (0b1111101 - 33))); + assert_eq!(0b10011110, encode_value(0 - (0b1111001 - 33))); + + // ...01abcdxxx cases + assert_eq!(0b00101111, encode_value(0 + (0b11111000 - 33))); + assert_eq!(0b00101110, encode_value(0 + (0b11110001 - 33))); + assert_eq!(0b10101111, encode_value(0 - (0b11111010 - 33))); + assert_eq!(0b10101110, encode_value(0 - (0b11110011 - 33))); + + // ...01abcdxxxx cases + assert_eq!(0b00111111, encode_value(0 + (0b111110000 - 33))); + assert_eq!(0b00111110, encode_value(0 + (0b111100010 - 33))); + assert_eq!(0b10111111, encode_value(0 - (0b111110100 - 33))); + assert_eq!(0b10111110, encode_value(0 - (0b111100110 - 33))); + + // ...01abcdxxxxx cases + assert_eq!(0b01001111, encode_value(0 + (0b1111100000 - 33))); + assert_eq!(0b01001110, encode_value(0 + (0b1111000100 - 33))); + assert_eq!(0b11001111, encode_value(0 - (0b1111101000 - 33))); + assert_eq!(0b11001110, encode_value(0 - (0b1111001100 - 33))); + + // ...01abcdxxxxxx cases + assert_eq!(0b01011111, encode_value(0 + (0b11111000000 - 33))); + assert_eq!(0b01011110, encode_value(0 + (0b11110001000 - 33))); + assert_eq!(0b11011111, encode_value(0 - (0b11111010000 - 33))); + assert_eq!(0b11011110, encode_value(0 - (0b11110011000 - 33))); + + // ...01abcdxxxxxxx cases + assert_eq!(0b01101111, encode_value(0 + (0b111110000000 - 33))); + assert_eq!(0b01101110, encode_value(0 + (0b111100010000 - 33))); + assert_eq!(0b11101111, encode_value(0 - (0b111110100000 - 33))); + assert_eq!(0b11101110, encode_value(0 - (0b111100110000 - 33))); + + // ...01abcdxxxxxxx cases + assert_eq!(0b01111111, encode_value(0 + (0b1111100000000 - 33))); + assert_eq!(0b01111110, encode_value(0 + (0b1111000100001 - 33))); + assert_eq!(0b11111111, encode_value(0 - (0b1111101000010 - 33))); + assert_eq!(0b11111110, encode_value(0 - (0b1111001100011 - 33))); + } +} \ No newline at end of file diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..e5c04fb --- /dev/null +++ b/src/image.rs @@ -0,0 +1,45 @@ +use std::io; + +use png::ColorType; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ImageError { + #[error("can't make the layer fit {0}x{1}")] + WrongSize(usize, usize), + #[error("unsupported color type {0:?}")] + InvalidInputColorType(ColorType), + #[error("can't save an image with {0} layers as png")] + WrongNumberOfLayersToSave(usize), + + #[error("general IO error")] + IoError(#[from] io::Error), + #[error("error in encoding PNG")] + PngEncodingError(#[from] png::EncodingError), + #[error("error in decoding PNG")] + PngDecodingError(#[from] png::DecodingError), +} + +pub struct Image { + pub width: usize, + pub height: usize, + pub layers: Vec<(u8, Vec)> +} + +impl Image { + pub fn new(width: usize, height: usize) -> Self { + Image { + width, height, + layers: vec![] + } + } + + pub fn add(&mut self, quality: u8, pixels: Vec) -> Result<(), ImageError> { + if pixels.len() != self.width * self.height { + return Err(ImageError::WrongSize(self.width, self.height)); + } + self.layers.push((quality, pixels)); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5f7440a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,42 @@ +use std::{fs::File, io::{Cursor, Write}}; + +use protocol::{ProtocolReader, ProtocolWriter}; + +mod decode_image; +mod decode_tile; +mod encode_image; +mod encode_tile; +mod image; +mod png_utils; +mod protocol; + +fn main() { + // run_for("zonked".to_string(), 9, 2, Some(330000)); + run_for("zonked".to_string()); + run_for("avatar2".to_string()); +} +fn run_for(name: String) { + let image = png_utils::load_image( + format!("inputs/{}.png", name), + [5, 6, 5, 0] + ).unwrap(); + let mut writer = ProtocolWriter::new(vec![]); + + encode_image::encode_image( + image, + &mut writer + ).unwrap(); + let compressed = writer.destroy(); + + let mut output_file = File::create(format!("outputs/{}.rx0", name)).unwrap(); + output_file.write_all(&compressed).unwrap(); + + let mut reader = ProtocolReader::new(Cursor::new(compressed)); + let decompressed_image = decode_image::decode_image(&mut reader).unwrap(); + + png_utils::save_image( + format!("outputs/{}_out.png", name), + decompressed_image + ).unwrap(); +} + diff --git a/src/png_utils.rs b/src/png_utils.rs new file mode 100644 index 0000000..972e42c --- /dev/null +++ b/src/png_utils.rs @@ -0,0 +1,83 @@ +use std::fs::File; + +use png::{BitDepth, ColorType}; + +use crate::image::{Image, ImageError}; + +pub fn load_image(filename: String, quality: [u8; 4]) -> Result { + let decoder = png::Decoder::new(File::open(filename).unwrap()); + let mut reader = decoder.read_info().unwrap(); + + let mut buf = vec![0; reader.output_buffer_size()]; + let info = reader.next_frame(&mut buf).unwrap(); + let bytes = &buf[..info.buffer_size()]; + + assert_eq!(BitDepth::Eight, reader.info().bit_depth); + assert_eq!(ColorType::Rgb, reader.info().color_type); + + let width = reader.info().width; + let height = reader.info().height; + + match reader.info().color_type { + ColorType::Rgb => { + assert_eq!(3, reader.info().bytes_per_pixel()); + + let r: Vec = bytes[0..].iter().cloned().step_by(3).collect(); + let g: Vec = bytes[1..].iter().cloned().step_by(3).collect(); + let b: Vec = bytes[2..].iter().cloned().step_by(3).collect(); + + let mut image = Image::new(width as usize, height as usize); + image.add(quality[0], r)?; + image.add(quality[1], g)?; + image.add(quality[2], b)?; + return Ok(image); + } + ColorType::Rgba => { + assert_eq!(4, reader.info().bytes_per_pixel()); + + let r: Vec = bytes[0..].iter().cloned().step_by(4).collect(); + let g: Vec = bytes[1..].iter().cloned().step_by(4).collect(); + let b: Vec = bytes[2..].iter().cloned().step_by(4).collect(); + let a: Vec = bytes[3..].iter().cloned().step_by(4).collect(); + + let mut image = Image::new(width as usize, height as usize); + image.add(quality[0], r)?; + image.add(quality[1], g)?; + image.add(quality[2], b)?; + image.add(quality[3], a)?; + return Ok(image); + } + ct => { + return Err(ImageError::InvalidInputColorType(ct)) + } + } +} + +pub fn save_image(filename: String, image: Image) -> Result<(), ImageError> { + let n_layers = image.layers.len(); + + let color_type = match n_layers { + 3 => ColorType::Rgb, + 4 => ColorType::Rgba, + _ => return Err(ImageError::WrongNumberOfLayersToSave(n_layers)), + }; + + let mut encoder = png::Encoder::new( + File::create(filename)?, + image.width as u32, image.height as u32, + ); + encoder.set_color(color_type); + + let mut idata: Vec = vec![0; image.width * image.height * n_layers]; + for i in 0..n_layers { + for (dst, src) in + idata[i..].iter_mut().step_by(n_layers).zip(image.layers[i].1.iter()) + { + *dst = *src; + } + } + + let mut writer = encoder.write_header()?; + writer.write_image_data(&idata)?; + Ok(()) +} \ No newline at end of file diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..cbe4a3a --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,67 @@ +use std::io::{self, Read, Write}; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ProtocolWriterError { + #[error("error using underlying writer")] + Io(#[from] io::Error) +} + +pub type ProtocolWriterResult = Result; + +pub struct ProtocolWriter { + writer: W +} + +#[derive(Error, Debug)] +pub enum ProtocolReaderError { + #[error("error using underlying reader")] + Io(#[from] io::Error), +} + +pub type ProtocolReaderResult = Result; + +pub struct ProtocolReader { + reader: R +} + +impl ProtocolWriter { + pub fn new(writer: W) -> Self { + Self { writer } + } + + pub fn destroy(self) -> W { + self.writer + } + + pub fn write_u8(&mut self, value: u8) -> ProtocolWriterResult<()> { + self.writer.write_all(&value.to_le_bytes())?; + Ok(()) + } + + pub fn write_u16(&mut self, value: u16) -> ProtocolWriterResult<()> { + self.writer.write_all(&value.to_le_bytes())?; + Ok(()) + } + +} +impl ProtocolReader { + pub fn new(reader: R) -> Self { + Self { reader } + } + + pub fn read_u8(&mut self) -> ProtocolReaderResult { + let mut u8_buf = [0; 1]; + self.reader.read_exact(&mut u8_buf)?; + + Ok(u8::from_le_bytes(u8_buf)) + } + + pub fn read_u16(&mut self) -> ProtocolReaderResult { + let mut u16_buf = [0; 2]; + self.reader.read_exact(&mut u16_buf)?; + + Ok(u16::from_le_bytes(u16_buf)) + } +} \ No newline at end of file diff --git a/src/transform.rs.old b/src/transform.rs.old new file mode 100644 index 0000000..604bb3a --- /dev/null +++ b/src/transform.rs.old @@ -0,0 +1,66 @@ +// Ported from: +// - https://colab.research.google.com/drive/1WjtKwUcqxWafAumFO9ET74RsckZSsw6n#scrollTo=e3r9-RBpcgwH + +// Specialized for: +// +// - Input domain: 0-255 +// - Tile size: 16x16 +// +// Rather than dividing by 16 at the end of a decode phase, +// we divide by 16 at the end of the _encode_ phase +// +// The normal range would be something like -255 * 256 to 255 * 256 +// (-65280 to 65280) but this reduces it (now it's something like -4080 to 4080) +// +// This simplifies the decoder (which can be i16) and means it can be written +// using i16s instead of i32s +// +// We could probably shrink the range more by recentering to -128-127, but does +// it even matter? Reasonable encodings like A-law and mu-law can handle both. +// +// (Take my opinions on the range with a grain of salt: I've calculated it but I +// haven't measured it) +// +struct Transform<'d> { + data: &'d mut [i32], + zero: usize, + stride: usize, +} + +impl<'d> Transform<'d> { + pub fn encode(&mut self) { + self.transform(); + self.divide(16); + } + + pub fn decode(&mut self) { + self.transform(); + } + + pub fn ix(&self, i: usize) -> usize { + self.zero + t * self.stride + } + + fn transform(&mut self) { + for bit in [8, 4, 2, 1] { + for i in 0..16 { + if i & bit == 0 { + let j = i | bit; + + let ival = self.data[self.ix(i)]; + let jval = self.data[self.ix(j)]; + + self.data[self.ix(i)] = ival.wrapping_add(jval); + self.data[self.ix(j)] = ival.wrapping_sub(jval); + } + } + } + } + + fn divide(&mut self, divisor: i32) { + for i in 0..16 { + self.data[self.ix(i)] = self.data[self.ix(j)] / divisor; + } + } + +} \ No newline at end of file