lossy compression

This commit is contained in:
Crispy 2024-04-12 18:47:40 +02:00
parent 242724d7fd
commit 8bfeb6d2f3
4 changed files with 194 additions and 37 deletions

View file

@ -1,5 +1,56 @@
use crate::*; 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 { pub fn cell_diff_8_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
let bitmap = { let bitmap = {
*reader += 3; *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 mut frame = *prev_frame;
let changed_pixel_count = bitmap.count_ones() as usize * 8 * 8; 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; *reader += runs;
let cells_x = WIDTH / 8; 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 { 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; *reader += runs;
let mut frame = *prev_frame; let mut frame = *prev_frame;
let mut i = 0; 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 { 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; *reader += runs;
let mut frame = *prev_frame; let mut frame = *prev_frame;
let mut dbg_frame = FRAME_0; 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 { 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; *reader += runs;
let mut frame = FRAME_0; let mut frame = FRAME_0;
let mut i = 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 { 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; *reader += runs;
let mut frame = FRAME_0; let mut frame = FRAME_0;
let mut i = 0; let mut i = 0;

View file

@ -1,15 +1,66 @@
use crate::*; use crate::*;
pub fn cell_diff_8_vertical(prev_frame: &Frame, frame: &Frame) -> EncodedFrame { pub fn cell_diff_4_vertical(prev_frame: &Frame, frame: &Frame, loss: usize) -> EncodedFrame {
let cells_x = WIDTH / 8; let loss = loss / 4;
let cells_y = HEIGHT / 8; const CELLS_X: usize = WIDTH / 4;
let loss = 0; 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 bitmap: u32 = 0;
let mut changed_pixels = Vec::new();
for cellx in 0..cells_x { for cellx in 0..CELLS_X {
for celly in 0..cells_y { for celly in 0..CELLS_Y {
let mut changed = 0; let mut changed = 0;
for dx in 0..8 { for dx in 0..8 {
for dy 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 { 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 x in 0..WIDTH {
for y in 0..HEIGHT { for y in 0..HEIGHT {
let cellx = x / 8; let cellx = x / 8;
let celly = y / 8; let celly = y / 8;
let bit = 1 << (cellx + cells_x * celly); let bit = 1 << (cellx + CELLS_X * celly);
if (bitmap & bit) != 0 { if (bitmap & bit) != 0 {
changed_pixels.push(frame[x][y]); 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 { EncodedFrame {
encoding: Encoding::FillWhite, encoding: Encoding::FillWhite,
data: Vec::new(), 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 { EncodedFrame {
encoding: Encoding::FillBlack, encoding: Encoding::FillBlack,
data: Vec::new(), data: Vec::new(),

View file

@ -8,13 +8,13 @@ pub use util::*;
const INSPECT_ENC: bool = false; const INSPECT_ENC: bool = false;
const INSPECT_DEC: 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() { fn main() {
let frames = get_all_frames("../video/frames/"); let frames = get_all_frames("../video/frames/");
let encoded = encode(&frames); let encoded = encode(&frames);
// todo print average bytes per frame for each encoding type
let mut stats: EnumMap<Encoding, u32> = EnumMap::default(); let mut stats: EnumMap<Encoding, u32> = EnumMap::default();
let mut reader = 0; let mut reader = 0;
let mut last_frame = FRAME_0; let mut last_frame = FRAME_0;
@ -49,34 +49,59 @@ fn main() {
fn encode(frames: &[Frame]) -> Vec<u8> { fn encode(frames: &[Frame]) -> Vec<u8> {
let mut out = Vec::new(); let mut out = Vec::new();
let encodings: Vec<FrameEncoder> = vec![ let lossless_encodings: Vec<FrameEncoder> = vec![
enc::fill_white,
enc::fill_black,
enc::rle_horizontal, enc::rle_horizontal,
enc::rle_vertical, enc::rle_vertical,
enc::rle_diff_horizontal, enc::rle_diff_horizontal,
enc::rle_diff_vertical, enc::rle_diff_vertical,
enc::bg_strips_horizontal, enc::bg_strips_horizontal,
];
let lossy_encodings: Vec<FrameEncoderLossy> = vec![
enc::fill_white,
enc::fill_black,
enc::cell_diff_8_vertical, enc::cell_diff_8_vertical,
enc::cell_diff_4_vertical,
]; ];
let mut last_frame = FRAME_0; 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(); let mut options = Vec::new();
for encode in &encodings { for encode in &lossless_encodings {
let encoded = encode(&last_frame, frame); let encoded = encode(&last_frame, frame);
let decode = get_matching_decoder(encoded.encoding); let decode = get_matching_decoder(encoded.encoding);
let decoded = decode(&last_frame, &encoded.data, &mut 0); let decoded = decode(&last_frame, &encoded.data, &mut 0);
let error = frame_error(frame, &decoded); let error = frame_error(frame, &decoded);
if error <= MAX_ERROR { if error == 0 {
options.push(encoded); options.push(encoded);
} else { } else {
// dbg!(&encoded); dbg!(&encoded);
// println!("{:?}, error: {error}, frame: {i}", encoded.encoding); println!("{:?}, error: {error}, frame: {_i}", encoded.encoding);
// render_images(&frame, &decoded); render_images(&frame, &decoded);
// panic!("loss in compression"); 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()); options.sort_by_key(|b| b.data.len());
let best_encoding = options.into_iter().next().unwrap(); let best_encoding = options.into_iter().next().unwrap();
if INSPECT_ENC { if INSPECT_ENC {
@ -99,10 +124,10 @@ fn encode(frames: &[Frame]) -> Vec<u8> {
#[derive(Debug, TryFromPrimitive, Enum, Copy, Clone)] #[derive(Debug, TryFromPrimitive, Enum, Copy, Clone)]
#[repr(u8)] #[repr(u8)]
enum Encoding { enum Encoding {
FillWhite, FillBlack = 0,
FillBlack, FillWhite = 1,
RLEHorizontal, RLEHorizontal = 2,
RLEVertical, RLEVertical = 3,
RLEDiffHorizontal, RLEDiffHorizontal,
RLEDiffVertical, RLEDiffVertical,
BGStripsH, BGStripsH,
@ -114,7 +139,7 @@ enum Encoding {
// CellDiff4HH, // CellDiff4HH,
// CellDiff4HV, // CellDiff4HV,
// CellDiff4VH, // CellDiff4VH,
// CellDiff4VV, CellDiff4VV,
} }
fn get_matching_decoder(encoding: Encoding) -> FrameDecoder { fn get_matching_decoder(encoding: Encoding) -> FrameDecoder {
@ -134,11 +159,12 @@ fn get_matching_decoder(encoding: Encoding) -> FrameDecoder {
// Encoding::CellDiff4HH => todo!(), // Encoding::CellDiff4HH => todo!(),
// Encoding::CellDiff4HV => todo!(), // Encoding::CellDiff4HV => todo!(),
// Encoding::CellDiff4VH => 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 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; type FrameDecoder = fn(previous_frame: &Frame, encoded_bytes: &[u8], reader: &mut usize) -> Frame;
#[derive(Debug)] #[derive(Debug)]

View file

@ -107,13 +107,17 @@ pub fn render_images(left: &Frame, right: &Frame) {
} }
pub fn rle_255_encode(raw: &[u8]) -> Vec<u8> { pub fn rle_255_encode(raw: &[u8]) -> Vec<u8> {
rle_encode(raw, 255)
}
pub fn rle_encode(raw: &[u8], max: u8) -> Vec<u8> {
let mut encoded = Vec::new(); let mut encoded = Vec::new();
let mut last_val = 0; let mut last_val = 0;
let mut run = 0; let mut run = 0;
for &val in raw { for &val in raw {
if val != last_val || run == 255 { if val != last_val || run == max {
encoded.push(run); encoded.push(run);
if run == 255 && val == last_val { if run == max && val == last_val {
encoded.push(0); encoded.push(0);
} }
run = 1; run = 1;
@ -126,7 +130,31 @@ pub fn rle_255_encode(raw: &[u8]) -> Vec<u8> {
encoded encoded
} }
pub fn rle_255_decode_until(encoded: &[u8], max_size: usize) -> (usize, Vec<u8>) { pub fn pack_nybbles(mut nybbles: Vec<u8>) -> Vec<u8> {
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<u8> {
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<u8>) {
let mut raw = Vec::new(); let mut raw = Vec::new();
let mut val = 0; let mut val = 0;
let mut consumed_bytes = 0; let mut consumed_bytes = 0;