lossy compression
This commit is contained in:
parent
242724d7fd
commit
8bfeb6d2f3
4 changed files with 194 additions and 37 deletions
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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<Encoding, u32> = EnumMap::default();
|
||||
let mut reader = 0;
|
||||
let mut last_frame = FRAME_0;
|
||||
|
@ -49,34 +49,59 @@ fn main() {
|
|||
|
||||
fn encode(frames: &[Frame]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
let encodings: Vec<FrameEncoder> = vec![
|
||||
enc::fill_white,
|
||||
enc::fill_black,
|
||||
let lossless_encodings: Vec<FrameEncoder> = vec![
|
||||
enc::rle_horizontal,
|
||||
enc::rle_vertical,
|
||||
enc::rle_diff_horizontal,
|
||||
enc::rle_diff_vertical,
|
||||
enc::bg_strips_horizontal,
|
||||
];
|
||||
let lossy_encodings: Vec<FrameEncoderLossy> = 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!("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);
|
||||
// println!("{:?}, error: {error}, frame: {_i}", encoded.encoding);
|
||||
// render_images(&frame, &decoded);
|
||||
// panic!("loss in compression");
|
||||
// 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<u8> {
|
|||
#[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)]
|
||||
|
|
|
@ -107,13 +107,17 @@ pub fn render_images(left: &Frame, right: &Frame) {
|
|||
}
|
||||
|
||||
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 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<u8> {
|
|||
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 val = 0;
|
||||
let mut consumed_bytes = 0;
|
||||
|
|
Loading…
Reference in a new issue