diff --git a/encoder/Cargo.lock b/encoder/Cargo.lock index 49cda42..2928603 100644 --- a/encoder/Cargo.lock +++ b/encoder/Cargo.lock @@ -38,13 +38,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "compressor" -version = "0.1.0" -dependencies = [ - "image", -] - [[package]] name = "crc32fast" version = "1.4.0" @@ -54,6 +47,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "encoder" +version = "0.1.0" +dependencies = [ + "enum-map", + "image", + "num_enum", +] + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "fdeflate" version = "0.3.4" @@ -73,6 +101,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "image" version = "0.25.1" @@ -85,6 +119,22 @@ dependencies = [ "png", ] +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[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" @@ -104,6 +154,27 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "png" version = "0.17.13" @@ -117,8 +188,78 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[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 = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[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 = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/encoder/Cargo.toml b/encoder/Cargo.toml index 1bb3b8b..92e57d5 100644 --- a/encoder/Cargo.toml +++ b/encoder/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +enum-map = "2.7.3" image = { version = "0.25.1", default-features = false, features = ["png"] } +num_enum = "0.7.2" diff --git a/encoder/src/dec.rs b/encoder/src/dec.rs index 51acb5f..90f7a73 100644 --- a/encoder/src/dec.rs +++ b/encoder/src/dec.rs @@ -1,7 +1,36 @@ use crate::*; -pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8]) -> Frame { - let pixels = rle_255_decode(encoded); +pub fn rle_diff_horizontal(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { + let (runs, decoded) = rle_255_decode_until(encoded, FRAME_SIZE); + *reader += runs; + let mut frame = prev_frame.clone(); + let mut i = 0; + for y in 0..HEIGHT { + for x in 0..WIDTH { + frame[x][y] ^= decoded[i]; + i += 1; + } + } + frame +} + +pub fn rle_diff_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { + let (runs, decoded) = rle_255_decode_until(encoded, FRAME_SIZE); + *reader += runs; + let mut frame = prev_frame.clone(); + let mut i = 0; + for x in 0..WIDTH { + for y in 0..HEIGHT { + frame[x][y] ^= decoded[i]; + i += 1; + } + } + frame +} + +pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { + let (runs, pixels) = rle_255_decode_until(encoded, FRAME_SIZE); + *reader += runs; let mut frame = FRAME_0; let mut i = 0; for y in 0..HEIGHT { @@ -13,8 +42,9 @@ pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8]) -> Frame { frame } -pub fn rle_vertical(_prev_frame: &Frame, encoded: &[u8]) -> Frame { - let pixels = rle_255_decode(encoded); +pub fn rle_vertical(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { + let (runs, pixels) = rle_255_decode_until(encoded, FRAME_SIZE); + *reader += runs; let mut frame = FRAME_0; let mut i = 0; for x in 0..WIDTH { @@ -26,10 +56,10 @@ pub fn rle_vertical(_prev_frame: &Frame, encoded: &[u8]) -> Frame { frame } -pub fn fill_white(_prev_frame: &Frame, _encoded: &[u8]) -> Frame { +pub fn fill_white(_prev_frame: &Frame, _encoded: &[u8], _reader: &mut usize) -> Frame { FRAME_1 } -pub fn fill_black(_prev_frame: &Frame, _encoded: &[u8]) -> Frame { +pub fn fill_black(_prev_frame: &Frame, _encoded: &[u8], _reader: &mut usize) -> Frame { FRAME_0 } diff --git a/encoder/src/enc.rs b/encoder/src/enc.rs index 0a5126c..6aec066 100644 --- a/encoder/src/enc.rs +++ b/encoder/src/enc.rs @@ -1,5 +1,31 @@ use crate::*; +pub fn rle_diff_horizontal(prev_frame: &Frame, frame: &Frame) -> EncodedFrame { + let mut pixels = Vec::new(); + for y in 0..HEIGHT { + for x in 0..WIDTH { + pixels.push(frame[x][y] ^ prev_frame[x][y]); + } + } + EncodedFrame { + encoding: Encoding::RLEDiffHorizontal, + data: rle_255_encode(&pixels), + } +} + +pub fn rle_diff_vertical(prev_frame: &Frame, frame: &Frame) -> EncodedFrame { + let mut pixels = Vec::new(); + for x in 0..WIDTH { + for y in 0..HEIGHT { + pixels.push(frame[x][y] ^ prev_frame[x][y]); + } + } + EncodedFrame { + encoding: Encoding::RLEDiffVertical, + data: rle_255_encode(&pixels), + } +} + pub fn rle_horizontal(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { let mut pixels = Vec::new(); for y in 0..HEIGHT { @@ -9,7 +35,6 @@ pub fn rle_horizontal(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { } EncodedFrame { encoding: Encoding::RLEHorizontal, - head_u4: 0, data: rle_255_encode(&pixels), } } @@ -23,7 +48,6 @@ pub fn rle_vertical(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { } EncodedFrame { encoding: Encoding::RLEVertical, - head_u4: 0, data: rle_255_encode(&pixels), } } @@ -31,7 +55,6 @@ pub fn rle_vertical(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { pub fn fill_white(_prev_frame: &Frame, _frame: &Frame) -> EncodedFrame { EncodedFrame { encoding: Encoding::FillWhite, - head_u4: 0, data: Vec::new(), } } @@ -39,7 +62,6 @@ pub fn fill_white(_prev_frame: &Frame, _frame: &Frame) -> EncodedFrame { pub fn fill_black(_prev_frame: &Frame, _frame: &Frame) -> EncodedFrame { EncodedFrame { encoding: Encoding::FillBlack, - head_u4: 0, data: Vec::new(), } } diff --git a/encoder/src/main.rs b/encoder/src/main.rs index 8b3916f..b8c46ff 100644 --- a/encoder/src/main.rs +++ b/encoder/src/main.rs @@ -1,33 +1,67 @@ +use enum_map::{Enum, EnumMap}; +use num_enum::TryFromPrimitive; + mod dec; mod enc; mod util; pub use util::*; -const INTERACTIVE: bool = false; +const INSPECT_ENC: bool = false; +const INSPECT_DEC: bool = true; fn main() { let frames = get_all_frames("../video/frames/"); let encoded = encode(&frames); - println!("{} frames, total {} bytes", frames.len(), encoded.len()); - // + + // todo print average bytes per frame for each encoding type + let mut stats: EnumMap = EnumMap::default(); + let mut reader = 0; + let mut last_frame = FRAME_0; + let mut frame_index = 0; + while reader < encoded.len() { + let frame_type: Encoding = encoded[reader].try_into().unwrap(); + stats[frame_type] += 1; + reader += 1; + let decoder = get_matching_decoder(frame_type); + last_frame = decoder(&last_frame, &encoded[reader..], &mut reader); + if INSPECT_DEC { + println!("\n{frame_type:?}"); + render_images(&frames[frame_index], &last_frame); + wait_for_input(); + frame_index += 1; + } + } + for (encoding, frames) in stats.iter() { + println!("{encoding:?} - {frames} frames"); + } + println!(); + println!( + "{} frames, total {} bytes (~{} bpf)", + frames.len(), + encoded.len(), + encoded.len() / frames.len() + ); } fn encode(frames: &[Frame]) -> Vec { let mut out = Vec::new(); - let encodings: Vec<(FrameEncoder, FrameDecoder)> = vec![ - (enc::fill_white, dec::fill_white), - (enc::fill_black, dec::fill_black), - (enc::rle_horizontal, dec::rle_horizontal), - (enc::rle_vertical, dec::rle_vertical), + let encodings: Vec = vec![ + enc::fill_white, + enc::fill_black, + enc::rle_horizontal, + enc::rle_vertical, + enc::rle_diff_horizontal, + enc::rle_diff_vertical, ]; let max_error = 0; let mut last_frame = FRAME_0; for frame in frames { let mut options = Vec::new(); - for (encode, decode) in &encodings { + for encode in &encodings { let encoded = encode(&last_frame, frame); - let decoded = decode(&last_frame, &encoded.data); + let decode = get_matching_decoder(encoded.encoding); + let decoded = decode(&last_frame, &encoded.data, &mut 0); let error = frame_error(frame, &decoded); if error <= max_error { options.push(encoded); @@ -35,7 +69,7 @@ fn encode(frames: &[Frame]) -> Vec { } options.sort_by_key(|b| b.data.len()); let best_encoding = options.into_iter().next().unwrap(); - if INTERACTIVE { + if INSPECT_ENC { println!(); println!( "{:?}, {} bytes", @@ -43,8 +77,7 @@ fn encode(frames: &[Frame]) -> Vec { best_encoding.data.len() + 1 ); render_image(frame); - let mut a = String::new(); - std::io::stdin().read_line(&mut a).unwrap(); + wait_for_input(); } let best_encoding = best_encoding.into_bytes(); out.extend_from_slice(&best_encoding); @@ -53,38 +86,59 @@ fn encode(frames: &[Frame]) -> Vec { out } -#[derive(Debug)] +#[derive(Debug, TryFromPrimitive, Enum, Copy, Clone)] #[repr(u8)] enum Encoding { FillWhite, FillBlack, RLEHorizontal, RLEVertical, - BGStrips, - CellDiff8Horizontal, - CellDiff8Vertical, + RLEDiffHorizontal, + RLEDiffVertical, + BGStripsH, + BGStripsV, + QuadTree, + DrawCommands, + CellDiff8H, + CellDiff8V, CellDiff4HH, CellDiff4HV, CellDiff4VH, CellDiff4VV, } + +fn get_matching_decoder(encoding: Encoding) -> FrameDecoder { + match encoding { + Encoding::FillWhite => dec::fill_white, + Encoding::FillBlack => dec::fill_black, + Encoding::RLEHorizontal => dec::rle_horizontal, + Encoding::RLEVertical => dec::rle_vertical, + Encoding::RLEDiffHorizontal => dec::rle_diff_horizontal, + Encoding::RLEDiffVertical => dec::rle_diff_vertical, + Encoding::BGStripsH => todo!(), + Encoding::BGStripsV => todo!(), + Encoding::QuadTree => todo!(), + Encoding::DrawCommands => todo!(), + Encoding::CellDiff8H => todo!(), + Encoding::CellDiff8V => todo!(), + Encoding::CellDiff4HH => todo!(), + Encoding::CellDiff4HV => todo!(), + Encoding::CellDiff4VH => todo!(), + Encoding::CellDiff4VV => todo!(), + } +} + type FrameEncoder = fn(previous_frame: &Frame, new_frame: &Frame) -> EncodedFrame; -type FrameDecoder = fn(previous_frame: &Frame, encoded_bytes: &[u8]) -> Frame; +type FrameDecoder = fn(previous_frame: &Frame, encoded_bytes: &[u8], reader: &mut usize) -> Frame; struct EncodedFrame { encoding: Encoding, - head_u4: u8, data: Vec, } impl EncodedFrame { - fn into_bytes(self) -> Vec { - let head = (self.encoding as u8) << 4; - let head = head | (self.head_u4 & 15); - - let mut out = Vec::with_capacity(self.data.len() + 1); - out.push(head); - out.extend(self.data.into_iter()); - out + fn into_bytes(mut self) -> Vec { + self.data.insert(0, self.encoding as u8); + self.data } } diff --git a/encoder/src/util.rs b/encoder/src/util.rs index 51c40e8..f68526f 100644 --- a/encoder/src/util.rs +++ b/encoder/src/util.rs @@ -7,12 +7,16 @@ use image::{self, DynamicImage, GenericImageView, ImageFormat, Rgba}; pub const WIDTH: usize = 40; pub const HEIGHT: usize = 32; -pub const SIZE: usize = WIDTH * HEIGHT; +pub const FRAME_SIZE: usize = WIDTH * HEIGHT; pub type Frame = [[u8; HEIGHT]; WIDTH]; pub const FRAME_0: Frame = [[0; HEIGHT]; WIDTH]; pub const FRAME_1: Frame = [[1; HEIGHT]; WIDTH]; +pub fn wait_for_input() { + std::io::stdin().read_line(&mut String::new()).unwrap(); +} + fn convert_pixel(rgba: Rgba) -> u8 { (rgba.0[0] > 128) as u8 } @@ -75,7 +79,9 @@ pub fn render_images(left: &Frame, right: &Frame) { for y in 0..(HEIGHT / 2) { for x in 0..WIDTH { render_pixel_pair(left, x, y); - print!(" "); + } + print!(" "); + for x in 0..WIDTH { render_pixel_pair(right, x, y); } println!(); @@ -102,14 +108,19 @@ pub fn rle_255_encode(raw: &[u8]) -> Vec { encoded } -pub fn rle_255_decode(encoded: &[u8]) -> Vec { +pub fn rle_255_decode_until(encoded: &[u8], max_size: usize) -> (usize, Vec) { let mut raw = Vec::new(); let mut val = 0; + let mut consumed_bytes = 0; for &run in encoded { + consumed_bytes += 1; for _ in 0..run { raw.push(val); } + if raw.len() >= max_size { + break; + } val = 1 - val; } - raw + (consumed_bytes, raw) }