add diff rle and run decoding on final byte array

This commit is contained in:
Crispy 2024-04-11 20:52:12 +02:00
parent eca5344c66
commit 3af9be328c
6 changed files with 308 additions and 48 deletions

155
encoder/Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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
}

View file

@ -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(),
}
}

View file

@ -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<Encoding, u32> = 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<u8> {
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<FrameEncoder> = 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<u8> {
}
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<u8> {
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<u8> {
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<u8>,
}
impl EncodedFrame {
fn into_bytes(self) -> Vec<u8> {
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<u8> {
self.data.insert(0, self.encoding as u8);
self.data
}
}

View file

@ -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>) -> 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<u8> {
encoded
}
pub fn rle_255_decode(encoded: &[u8]) -> Vec<u8> {
pub fn rle_255_decode_until(encoded: &[u8], max_size: usize) -> (usize, Vec<u8>) {
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)
}