From 8bfeb6d2f3fbfb43472f872e8e20d82b1f7293a9 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Fri, 12 Apr 2024 18:47:40 +0200 Subject: [PATCH] lossy compression --- encoder/src/dec.rs | 61 ++++++++++++++++++++++++++++++++++--- encoder/src/enc.rs | 74 ++++++++++++++++++++++++++++++++++++++------- encoder/src/main.rs | 62 ++++++++++++++++++++++++++----------- encoder/src/util.rs | 34 +++++++++++++++++++-- 4 files changed, 194 insertions(+), 37 deletions(-) diff --git a/encoder/src/dec.rs b/encoder/src/dec.rs index 244c3ea..aae6178 100644 --- a/encoder/src/dec.rs +++ b/encoder/src/dec.rs @@ -1,5 +1,56 @@ use crate::*; +pub fn cell_diff_4_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { + const CELLS_X: usize = WIDTH / 4; + const CELLS_Y: usize = HEIGHT / 4; + + let mut modified_cells = [[false; CELLS_Y]; CELLS_X]; + + let (mut cell_runs, modified_cells_flat) = + rle_255_decode(&unpack_nybbles(encoded), CELLS_X * CELLS_Y); + + if cell_runs % 2 == 1 { + cell_runs += 1; + } + *reader += cell_runs / 2; + let encoded = &encoded[(cell_runs / 2)..]; + + let mut changed_cell_count = 0; + let mut i = 0; + for cellx in 0..CELLS_X { + for celly in 0..CELLS_Y { + let cell = modified_cells_flat[i] == 1; + modified_cells[cellx][celly] = cell; + if cell { + changed_cell_count += 1; + } + i += 1; + } + } + + let mut frame = *prev_frame; + + let changed_pixel_count = changed_cell_count * 4 * 4; + let (runs, new_pixels) = rle_255_decode(encoded, changed_pixel_count); + *reader += runs; + + let mut index = 0; + + for x in 0..WIDTH { + for y in 0..HEIGHT { + let cellx = x / 4; + let celly = y / 4; + let is_changed = modified_cells[cellx][celly]; + if is_changed { + frame[x][y] = new_pixels[index]; + index += 1; + } + } + } + + frame +} + pub fn cell_diff_8_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { let bitmap = { *reader += 3; @@ -11,7 +62,7 @@ pub fn cell_diff_8_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usi let mut frame = *prev_frame; let changed_pixel_count = bitmap.count_ones() as usize * 8 * 8; - let (runs, new_pixels) = rle_255_decode_until(encoded, changed_pixel_count); + let (runs, new_pixels) = rle_255_decode(encoded, changed_pixel_count); *reader += runs; let cells_x = WIDTH / 8; @@ -54,7 +105,7 @@ pub fn bg_strips_horizontal(_prev_frame: &Frame, encoded: &[u8], reader: &mut us } pub fn rle_diff_horizontal(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { - let (runs, decoded) = rle_255_decode_until(encoded, FRAME_SIZE); + let (runs, decoded) = rle_255_decode(encoded, FRAME_SIZE); *reader += runs; let mut frame = *prev_frame; let mut i = 0; @@ -68,7 +119,7 @@ pub fn rle_diff_horizontal(prev_frame: &Frame, encoded: &[u8], reader: &mut usiz } pub fn rle_diff_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { - let (runs, decoded) = rle_255_decode_until(encoded, FRAME_SIZE); + let (runs, decoded) = rle_255_decode(encoded, FRAME_SIZE); *reader += runs; let mut frame = *prev_frame; let mut dbg_frame = FRAME_0; @@ -86,7 +137,7 @@ pub fn rle_diff_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) } pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { - let (runs, pixels) = rle_255_decode_until(encoded, FRAME_SIZE); + let (runs, pixels) = rle_255_decode(encoded, FRAME_SIZE); *reader += runs; let mut frame = FRAME_0; let mut i = 0; @@ -100,7 +151,7 @@ pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) - } pub fn rle_vertical(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame { - let (runs, pixels) = rle_255_decode_until(encoded, FRAME_SIZE); + let (runs, pixels) = rle_255_decode(encoded, FRAME_SIZE); *reader += runs; let mut frame = FRAME_0; let mut i = 0; diff --git a/encoder/src/enc.rs b/encoder/src/enc.rs index c45caea..49992d7 100644 --- a/encoder/src/enc.rs +++ b/encoder/src/enc.rs @@ -1,15 +1,66 @@ use crate::*; -pub fn cell_diff_8_vertical(prev_frame: &Frame, frame: &Frame) -> EncodedFrame { - let cells_x = WIDTH / 8; - let cells_y = HEIGHT / 8; - let loss = 0; +pub fn cell_diff_4_vertical(prev_frame: &Frame, frame: &Frame, loss: usize) -> EncodedFrame { + let loss = loss / 4; + const CELLS_X: usize = WIDTH / 4; + const CELLS_Y: usize = HEIGHT / 4; + + let mut modified_cells = [[0; CELLS_Y]; CELLS_X]; + + for cellx in 0..CELLS_X { + for celly in 0..CELLS_Y { + let mut changed = 0; + for dx in 0..4 { + for dy in 0..4 { + let x = cellx * 4 + dx; + let y = celly * 4 + dy; + let pixel = frame[x][y]; + + if pixel != prev_frame[x][y] { + changed += 1; + } + } + } + if changed > loss { + modified_cells[cellx][celly] = 1; + } + } + } + let mut changed_pixels = Vec::new(); + for x in 0..WIDTH { + for y in 0..HEIGHT { + let cellx = x / 4; + let celly = y / 4; + + if modified_cells[cellx][celly] != 0 { + changed_pixels.push(frame[x][y]); + } + } + } + let mut modified_cells_flat = Vec::new(); + for x in 0..CELLS_X { + for y in 0..CELLS_Y { + modified_cells_flat.push(modified_cells[x][y]); + } + } + let mut data = Vec::new(); + data.extend_from_slice(&pack_nybbles(rle_encode(&modified_cells_flat, 15))); + data.extend_from_slice(&rle_255_encode(&changed_pixels)); + + EncodedFrame { + encoding: Encoding::CellDiff4VV, + data, + } +} + +pub fn cell_diff_8_vertical(prev_frame: &Frame, frame: &Frame, loss: usize) -> EncodedFrame { + const CELLS_X: usize = WIDTH / 8; + const CELLS_Y: usize = HEIGHT / 8; let mut bitmap: u32 = 0; - let mut changed_pixels = Vec::new(); - for cellx in 0..cells_x { - for celly in 0..cells_y { + for cellx in 0..CELLS_X { + for celly in 0..CELLS_Y { let mut changed = 0; for dx in 0..8 { for dy in 0..8 { @@ -23,15 +74,16 @@ pub fn cell_diff_8_vertical(prev_frame: &Frame, frame: &Frame) -> EncodedFrame { } } if changed > loss { - bitmap |= 1 << (cellx + cells_x * celly); + bitmap |= 1 << (cellx + CELLS_X * celly); } } } + let mut changed_pixels = Vec::new(); for x in 0..WIDTH { for y in 0..HEIGHT { let cellx = x / 8; let celly = y / 8; - let bit = 1 << (cellx + cells_x * celly); + let bit = 1 << (cellx + CELLS_X * celly); if (bitmap & bit) != 0 { changed_pixels.push(frame[x][y]); } @@ -150,14 +202,14 @@ pub fn rle_vertical(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame { } } -pub fn fill_white(_prev_frame: &Frame, _frame: &Frame) -> EncodedFrame { +pub fn fill_white(_prev_frame: &Frame, _frame: &Frame, _loss: usize) -> EncodedFrame { EncodedFrame { encoding: Encoding::FillWhite, data: Vec::new(), } } -pub fn fill_black(_prev_frame: &Frame, _frame: &Frame) -> EncodedFrame { +pub fn fill_black(_prev_frame: &Frame, _frame: &Frame, _loss: usize) -> EncodedFrame { EncodedFrame { encoding: Encoding::FillBlack, data: Vec::new(), diff --git a/encoder/src/main.rs b/encoder/src/main.rs index 4eb64bc..3577eb8 100644 --- a/encoder/src/main.rs +++ b/encoder/src/main.rs @@ -8,13 +8,13 @@ pub use util::*; const INSPECT_ENC: bool = false; const INSPECT_DEC: bool = false; -const MAX_ERROR: usize = 0; +const MAX_ERROR: usize = 4; // max wrong pixels +const MAX_LOSS: usize = 16; // highest "loss" value tried for all lossy encodings 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; @@ -49,34 +49,59 @@ fn main() { fn encode(frames: &[Frame]) -> Vec { let mut out = Vec::new(); - let encodings: Vec = vec![ - enc::fill_white, - enc::fill_black, + let lossless_encodings: Vec = vec![ enc::rle_horizontal, enc::rle_vertical, enc::rle_diff_horizontal, enc::rle_diff_vertical, enc::bg_strips_horizontal, + ]; + let lossy_encodings: Vec = vec![ + enc::fill_white, + enc::fill_black, enc::cell_diff_8_vertical, + enc::cell_diff_4_vertical, ]; let mut last_frame = FRAME_0; - for (i, frame) in frames.iter().enumerate() { + for (_i, frame) in frames.iter().enumerate() { let mut options = Vec::new(); - for encode in &encodings { + for encode in &lossless_encodings { let encoded = encode(&last_frame, frame); 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 { + if error == 0 { options.push(encoded); } else { - // dbg!(&encoded); - // println!("{:?}, error: {error}, frame: {i}", encoded.encoding); - // render_images(&frame, &decoded); - // panic!("loss in compression"); + dbg!(&encoded); + println!("{:?}, error: {error}, frame: {_i}", encoded.encoding); + render_images(&frame, &decoded); + panic!("error in lossless compression"); } } + for encode in &lossy_encodings { + for loss in 0..MAX_LOSS { + let encoded = encode(&last_frame, frame, loss); + let decode = get_matching_decoder(encoded.encoding); + let decoded = decode(&last_frame, &encoded.data, &mut 0); + let error = frame_error(frame, &decoded); + + // if error > 0 && loss == 0 { + // dbg!(&encoded); + // println!("{:?}, error: {error}, frame: {_i}", encoded.encoding); + // render_images(&frame, &decoded); + // panic!("error in 'loss 0' compression"); + // } + if error < MAX_ERROR { + options.push(encoded); + } else { + // higher loss value will mean more error so can be skipped + break; + } + } + } + options.sort_by_key(|b| b.data.len()); let best_encoding = options.into_iter().next().unwrap(); if INSPECT_ENC { @@ -99,10 +124,10 @@ fn encode(frames: &[Frame]) -> Vec { #[derive(Debug, TryFromPrimitive, Enum, Copy, Clone)] #[repr(u8)] enum Encoding { - FillWhite, - FillBlack, - RLEHorizontal, - RLEVertical, + FillBlack = 0, + FillWhite = 1, + RLEHorizontal = 2, + RLEVertical = 3, RLEDiffHorizontal, RLEDiffVertical, BGStripsH, @@ -114,7 +139,7 @@ enum Encoding { // CellDiff4HH, // CellDiff4HV, // CellDiff4VH, - // CellDiff4VV, + CellDiff4VV, } fn get_matching_decoder(encoding: Encoding) -> FrameDecoder { @@ -134,11 +159,12 @@ fn get_matching_decoder(encoding: Encoding) -> FrameDecoder { // Encoding::CellDiff4HH => todo!(), // Encoding::CellDiff4HV => todo!(), // Encoding::CellDiff4VH => todo!(), - // Encoding::CellDiff4VV => todo!(), + Encoding::CellDiff4VV => dec::cell_diff_4_vertical, } } type FrameEncoder = fn(previous_frame: &Frame, new_frame: &Frame) -> EncodedFrame; +type FrameEncoderLossy = fn(previous_frame: &Frame, new_frame: &Frame, loss: usize) -> EncodedFrame; type FrameDecoder = fn(previous_frame: &Frame, encoded_bytes: &[u8], reader: &mut usize) -> Frame; #[derive(Debug)] diff --git a/encoder/src/util.rs b/encoder/src/util.rs index 0c81fe3..506b3f4 100644 --- a/encoder/src/util.rs +++ b/encoder/src/util.rs @@ -107,13 +107,17 @@ pub fn render_images(left: &Frame, right: &Frame) { } pub fn rle_255_encode(raw: &[u8]) -> Vec { + rle_encode(raw, 255) +} + +pub fn rle_encode(raw: &[u8], max: u8) -> Vec { let mut encoded = Vec::new(); let mut last_val = 0; let mut run = 0; for &val in raw { - if val != last_val || run == 255 { + if val != last_val || run == max { encoded.push(run); - if run == 255 && val == last_val { + if run == max && val == last_val { encoded.push(0); } run = 1; @@ -126,7 +130,31 @@ pub fn rle_255_encode(raw: &[u8]) -> Vec { encoded } -pub fn rle_255_decode_until(encoded: &[u8], max_size: usize) -> (usize, Vec) { +pub fn pack_nybbles(mut nybbles: Vec) -> Vec { + if nybbles.len() % 2 == 1 { + nybbles.push(0); + } + assert!(nybbles.iter().all(|&n| n < 16)); + let mut packed = Vec::with_capacity(nybbles.len() / 2); + for i in 0..(nybbles.len() / 2) { + let upper = nybbles[i * 2] << 4; + let lower = nybbles[i * 2 + 1] & 15; + let byte = upper | lower; + packed.push(byte); + } + packed +} + +pub fn unpack_nybbles(packed: &[u8]) -> Vec { + let mut nybbles = Vec::with_capacity(packed.len() * 2); + for &p in packed { + nybbles.push(p >> 4); + nybbles.push(p & 15); + } + nybbles +} + +pub fn rle_255_decode(encoded: &[u8], max_size: usize) -> (usize, Vec) { let mut raw = Vec::new(); let mut val = 0; let mut consumed_bytes = 0;