diff --git a/encoder/Cargo.lock b/encoder/Cargo.lock index 2928603..49cda42 100644 --- a/encoder/Cargo.lock +++ b/encoder/Cargo.lock @@ -38,6 +38,13 @@ 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" @@ -47,41 +54,6 @@ 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" @@ -101,12 +73,6 @@ 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" @@ -119,22 +85,6 @@ 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" @@ -154,27 +104,6 @@ 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" @@ -188,78 +117,8 @@ 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 92e57d5..1bb3b8b 100644 --- a/encoder/Cargo.toml +++ b/encoder/Cargo.toml @@ -6,6 +6,4 @@ 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 3e73c27..51acb5f 100644 --- a/encoder/src/dec.rs +++ b/encoder/src/dec.rs @@ -1,61 +1,7 @@ use crate::*; -pub fn bg_strips_horizontal(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { - *reader += 1; - let bg = encoded[0] >> 7; - let fg = 1 - bg; - let mut frame = [[bg; HEIGHT]; WIDTH]; - let count = (encoded[0] & 0x7f) as usize; - *reader += count * 2; - for i in 0..count { - let upper = (encoded[i * 2 + 1] as u16) << 8; - let lower = encoded[i * 2 + 2] as u16; - let y_x_w: u16 = upper | lower; - let y = (y_x_w >> 11) as usize; - let x_start = ((y_x_w >> 5) & 0x3f) as usize; - let w = (y_x_w & 0x1f) as usize; - for x in x_start..(x_start + w) { - frame[x][y] = fg; - } - } - frame -} - -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; - 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; - let mut dbg_frame = FRAME_0; - let mut i = 0; - for x in 0..WIDTH { - for y in 0..HEIGHT { - frame[x][y] ^= decoded[i]; - frame[x][y] &= 1; - dbg_frame[x][y] ^= decoded[i]; - dbg_frame[x][y] &= 1; - 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; +pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8]) -> Frame { + let pixels = rle_255_decode(encoded); let mut frame = FRAME_0; let mut i = 0; for y in 0..HEIGHT { @@ -67,9 +13,8 @@ pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) - frame } -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; +pub fn rle_vertical(_prev_frame: &Frame, encoded: &[u8]) -> Frame { + let pixels = rle_255_decode(encoded); let mut frame = FRAME_0; let mut i = 0; for x in 0..WIDTH { @@ -81,10 +26,10 @@ pub fn rle_vertical(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> frame } -pub fn fill_white(_prev_frame: &Frame, _encoded: &[u8], _reader: &mut usize) -> Frame { +pub fn fill_white(_prev_frame: &Frame, _encoded: &[u8]) -> Frame { FRAME_1 } -pub fn fill_black(_prev_frame: &Frame, _encoded: &[u8], _reader: &mut usize) -> Frame { +pub fn fill_black(_prev_frame: &Frame, _encoded: &[u8]) -> Frame { FRAME_0 } diff --git a/encoder/src/enc.rs b/encoder/src/enc.rs index e7e0f92..0a5126c 100644 --- a/encoder/src/enc.rs +++ b/encoder/src/enc.rs @@ -1,84 +1,5 @@ use crate::*; -pub fn bg_strips_horizontal(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { - let bg = most_common_pixel(frame); - fn pack_strip(x: usize, y: usize, width: usize) -> [u8; 2] { - // Y is 0..31 so will only need 5 bits - // x is 0..42 so needs 6 bits - // 5 bits remain for width - // y: 1111100000000000 - let mut y_x_w: u16 = (y as u16) << 11; - // x: 0000011111100000 - y_x_w |= (x as u16) << 5; - // w: 0000000000011111 - y_x_w |= width as u16; - [(y_x_w >> 8) as u8, (y_x_w as u8)] - } - - let mut strips = Vec::new(); - 'outer: for y in 0..HEIGHT { - let mut strip_start = 0; - let mut in_strip = false; - for x in 0..WIDTH { - let pixel = frame[x][y]; - if !in_strip && pixel != bg { - in_strip = true; - strip_start = x; - } - if in_strip { - if pixel == bg { - strips.push((strip_start, y, x - strip_start)); - in_strip = false; - } else if x - strip_start == 31 { - strips.push((strip_start, y, x - strip_start)); - strip_start = x; - } - if strips.len() == 127 { - break 'outer; - } - } - } - if in_strip { - strips.push((strip_start, y, WIDTH - strip_start)); - } - } - let mut frame_bytes = Vec::with_capacity(1 + strips.len() * 2); - frame_bytes.push(bg << 7 | (strips.len() as u8)); - for (x, y, width) in strips { - frame_bytes.extend_from_slice(&pack_strip(x, y, width)); - } - EncodedFrame { - encoding: Encoding::BGStripsH, - data: frame_bytes, - } -} - -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 { @@ -88,6 +9,7 @@ pub fn rle_horizontal(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { } EncodedFrame { encoding: Encoding::RLEHorizontal, + head_u4: 0, data: rle_255_encode(&pixels), } } @@ -101,6 +23,7 @@ pub fn rle_vertical(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { } EncodedFrame { encoding: Encoding::RLEVertical, + head_u4: 0, data: rle_255_encode(&pixels), } } @@ -108,6 +31,7 @@ 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(), } } @@ -115,6 +39,7 @@ 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 daadb9c..8b3916f 100644 --- a/encoder/src/main.rs +++ b/encoder/src/main.rs @@ -1,84 +1,41 @@ -use enum_map::{Enum, EnumMap}; -use num_enum::TryFromPrimitive; - mod dec; mod enc; mod util; pub use util::*; -const INSPECT_ENC: bool = false; -const INSPECT_DEC: bool = false; +const INTERACTIVE: bool = false; fn main() { let frames = get_all_frames("../video/frames/"); let encoded = encode(&frames); - - // 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:?}, error: {}", - frame_error(&frames[frame_index], &last_frame) - ); - 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() - ); + println!("{} frames, total {} bytes", frames.len(), encoded.len()); + // } fn encode(frames: &[Frame]) -> Vec { let mut out = Vec::new(); - let encodings: Vec = vec![ - enc::fill_white, - enc::fill_black, - enc::rle_horizontal, - enc::rle_vertical, - enc::rle_diff_horizontal, - enc::rle_diff_vertical, - enc::bg_strips_horizontal, + 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 max_error = 0; let mut last_frame = FRAME_0; - for (i, frame) in frames.iter().enumerate() { + for frame in frames { let mut options = Vec::new(); - for encode in &encodings { + for (encode, decode) in &encodings { let encoded = encode(&last_frame, frame); - let decode = get_matching_decoder(encoded.encoding); - let decoded = decode(&last_frame, &encoded.data, &mut 0); + let decoded = decode(&last_frame, &encoded.data); let error = frame_error(frame, &decoded); if error <= max_error { options.push(encoded); - } else { - // dbg!(&encoded); - // println!("{:?}, error: {error}, frame: {i}", encoded.encoding); - // render_images(&frame, &decoded); - // panic!("loss in compression"); } } options.sort_by_key(|b| b.data.len()); let best_encoding = options.into_iter().next().unwrap(); - if INSPECT_ENC { + if INTERACTIVE { println!(); println!( "{:?}, {} bytes", @@ -86,69 +43,48 @@ fn encode(frames: &[Frame]) -> Vec { best_encoding.data.len() + 1 ); render_image(frame); - wait_for_input(); + let mut a = String::new(); + std::io::stdin().read_line(&mut a).unwrap(); } let best_encoding = best_encoding.into_bytes(); out.extend_from_slice(&best_encoding); - last_frame = *frame; + last_frame = frame.clone(); } out } -#[derive(Debug, TryFromPrimitive, Enum, Copy, Clone)] +#[derive(Debug)] #[repr(u8)] enum Encoding { FillWhite, FillBlack, RLEHorizontal, RLEVertical, - RLEDiffHorizontal, - RLEDiffVertical, - BGStripsH, - // BGStripsV, - // QuadTree, - // DrawCommands, - // CellDiff8H, - // CellDiff8V, - // CellDiff4HH, - // CellDiff4HV, - // CellDiff4VH, - // CellDiff4VV, + BGStrips, + CellDiff8Horizontal, + CellDiff8Vertical, + 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 => dec::bg_strips_horizontal, - // 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], reader: &mut usize) -> Frame; +type FrameDecoder = fn(previous_frame: &Frame, encoded_bytes: &[u8]) -> Frame; -#[derive(Debug)] struct EncodedFrame { encoding: Encoding, + head_u4: u8, data: Vec, } impl EncodedFrame { - fn into_bytes(mut self) -> Vec { - self.data.insert(0, self.encoding as u8); - self.data + 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 } } diff --git a/encoder/src/util.rs b/encoder/src/util.rs index 0c81fe3..51c40e8 100644 --- a/encoder/src/util.rs +++ b/encoder/src/util.rs @@ -1,22 +1,18 @@ use std::{ fs::{self, File}, - io::{stdin, BufReader}, + io::BufReader, }; use image::{self, DynamicImage, GenericImageView, ImageFormat, Rgba}; pub const WIDTH: usize = 40; pub const HEIGHT: usize = 32; -pub const FRAME_SIZE: usize = WIDTH * HEIGHT; +pub const 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() { - stdin().read_line(&mut String::new()).unwrap(); -} - fn convert_pixel(rgba: Rgba) -> u8 { (rgba.0[0] > 128) as u8 } @@ -55,23 +51,6 @@ pub fn frame_error(real: &Frame, decoded: &Frame) -> usize { error } -pub fn most_common_pixel(frame: &Frame) -> u8 { - let mut white_pixels = 0; - let mut black_pixels = 0; - for col in frame { - for &p in col { - let p = p as u32; - white_pixels += p; - black_pixels += 1 - p; - } - } - if white_pixels > black_pixels { - 1 - } else { - 0 - } -} - fn render_pixel_pair(img: &Frame, x: usize, y: usize) { let char = match (img[x][y * 2], img[x][y * 2 + 1]) { (0, 0) => " ", @@ -94,15 +73,12 @@ pub fn render_image(img: &Frame) { pub fn render_images(left: &Frame, right: &Frame) { for y in 0..(HEIGHT / 2) { - print!("|"); for x in 0..WIDTH { render_pixel_pair(left, x, y); - } - print!("|"); - for x in 0..WIDTH { + print!(" "); render_pixel_pair(right, x, y); } - println!("|"); + println!(); } } @@ -113,7 +89,7 @@ pub fn rle_255_encode(raw: &[u8]) -> Vec { for &val in raw { if val != last_val || run == 255 { encoded.push(run); - if run == 255 && val == last_val { + if run == 255 { encoded.push(0); } run = 1; @@ -126,19 +102,14 @@ pub fn rle_255_encode(raw: &[u8]) -> Vec { encoded } -pub fn rle_255_decode_until(encoded: &[u8], max_size: usize) -> (usize, Vec) { +pub fn rle_255_decode(encoded: &[u8]) -> 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; } - (consumed_bytes, raw) + raw }