what the fuck help
This commit is contained in:
commit
ea18938dcf
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
outputs/
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
".\\Cargo.toml"
|
||||||
|
]
|
||||||
|
}
|
277
Cargo.lock
generated
Normal file
277
Cargo.lock
generated
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.153"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
|
[[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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.17.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "quickcheck"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quickcheck_macros"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rx0"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"png",
|
||||||
|
"quickcheck",
|
||||||
|
"quickcheck_macros",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "thiserror"
|
||||||
|
version = "1.0.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "rx0"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
png = "0.17.13"
|
||||||
|
thiserror = "1.0.58"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
quickcheck = "1.0.3"
|
||||||
|
quickcheck_macros = "1.0.0"
|
BIN
inputs/avatar2.png
Normal file
BIN
inputs/avatar2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 761 KiB |
BIN
inputs/zonked.png
Normal file
BIN
inputs/zonked.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 518 KiB |
56
src/decode_image.rs
Normal file
56
src/decode_image.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use crate::{decode_tile, image::Image, protocol::{self, ProtocolReaderResult}};
|
||||||
|
|
||||||
|
pub fn decode_image<R: Read>(
|
||||||
|
reader: &mut protocol::ProtocolReader<R>
|
||||||
|
) -> ProtocolReaderResult<Image> {
|
||||||
|
let width = reader.read_u16()? as usize;
|
||||||
|
let height = reader.read_u16()? as usize;
|
||||||
|
|
||||||
|
let mut layer_quality = [0_u8; 4];
|
||||||
|
for i in 0..4 {
|
||||||
|
layer_quality[i] = reader.read_u8()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut n_layers = 0;
|
||||||
|
for i in (0..4).rev() {
|
||||||
|
if layer_quality[i] != 0 {
|
||||||
|
n_layers = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut layer_pixels = vec![];
|
||||||
|
for _ in 0..n_layers {
|
||||||
|
layer_pixels.push(vec![0_u8; width as usize * height as usize])
|
||||||
|
}
|
||||||
|
|
||||||
|
for y0 in (0..height).step_by(16) {
|
||||||
|
for x0 in (0..width).step_by(16) {
|
||||||
|
for layer in 0..n_layers {
|
||||||
|
let mut data = [0; 256];
|
||||||
|
|
||||||
|
decode_tile::decode_tile(&mut data, layer_quality[layer], reader)?;
|
||||||
|
|
||||||
|
for y in 0..16 {
|
||||||
|
for x in 0..16 {
|
||||||
|
let src_x = x0 + x;
|
||||||
|
let src_y = y0 + y;
|
||||||
|
if src_x < width && src_y < height {
|
||||||
|
layer_pixels[layer][src_y * width + src_x] =
|
||||||
|
data[y * 16 + x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut image = Image::new(width, height);
|
||||||
|
for (layer, pixels) in layer_pixels.into_iter().enumerate() {
|
||||||
|
image.add(layer_quality[layer], pixels)
|
||||||
|
.expect("should be correct by construction");
|
||||||
|
}
|
||||||
|
Ok(image)
|
||||||
|
}
|
59
src/decode_tile.rs
Normal file
59
src/decode_tile.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use crate::protocol::{ProtocolReader, ProtocolReaderResult};
|
||||||
|
|
||||||
|
pub fn decode_tile<R: Read>(
|
||||||
|
data: &mut [u8; 256],
|
||||||
|
quality: u8,
|
||||||
|
reader: &mut ProtocolReader<R>
|
||||||
|
) -> ProtocolReaderResult<()> {
|
||||||
|
let mut coefs = [0_i32; 256];
|
||||||
|
|
||||||
|
for _ in 0..quality {
|
||||||
|
let ix = reader.read_u8()?;
|
||||||
|
let val = reader.read_u16()? as i32;
|
||||||
|
coefs[ix as usize] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut transform = |zero: usize, stride: usize| {
|
||||||
|
let mut ixs = [0; 16];
|
||||||
|
ixs[0] = zero;
|
||||||
|
for i in 1..16 { ixs[i] = ixs[i - 1] + stride; }
|
||||||
|
|
||||||
|
for bit in [8, 4, 2, 1] {
|
||||||
|
for i in 0..16 {
|
||||||
|
if i & bit == 0 {
|
||||||
|
let j = i | bit;
|
||||||
|
|
||||||
|
let ival = coefs[ixs[i]];
|
||||||
|
let jval = coefs[ixs[j]];
|
||||||
|
|
||||||
|
coefs[ixs[i]] = ival.wrapping_add(jval);
|
||||||
|
coefs[ixs[j]] = ival.wrapping_sub(jval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..16 {
|
||||||
|
coefs[ixs[i]] /= 16;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for row in 0..16 { transform(row * 16, 1); }
|
||||||
|
for col in 0..16 { transform(col, 16); }
|
||||||
|
|
||||||
|
for i in 0..256 {
|
||||||
|
data[i] = coefs[i].max(0).min(255) as u8;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_value(enc: u8) -> i32 {
|
||||||
|
let sign = enc & 0x80 != 0;
|
||||||
|
let exponent = (enc >> 4) & 0x7;
|
||||||
|
let mantissa = enc & 0xf;
|
||||||
|
|
||||||
|
let magnitude = ((((mantissa as i32) << 1) | 0b100001) << exponent) - 33;
|
||||||
|
if sign { return -magnitude * 16 }
|
||||||
|
return magnitude * 16;
|
||||||
|
}
|
42
src/encode_image.rs
Normal file
42
src/encode_image.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::{encode_tile, image::Image, protocol::{self, ProtocolWriterResult}};
|
||||||
|
|
||||||
|
pub fn encode_image<W: Write>(
|
||||||
|
image: Image,
|
||||||
|
writer: &mut protocol::ProtocolWriter<W>
|
||||||
|
) -> ProtocolWriterResult<()> {
|
||||||
|
assert!(image.layers.len() <= 4); // TODO: Enforce in Image
|
||||||
|
|
||||||
|
writer.write_u16(image.width as u16)?;
|
||||||
|
writer.write_u16(image.height as u16)?;
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
writer.write_u8(image.layers.get(i).map(|l| l.0).unwrap_or(0))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for y0 in (0..image.height).step_by(16) {
|
||||||
|
for x0 in (0..image.width).step_by(16) {
|
||||||
|
for (quality, layer) in image.layers.iter() {
|
||||||
|
let mut data = [0; 256];
|
||||||
|
|
||||||
|
for y in 0..16 {
|
||||||
|
for x in 0..16 {
|
||||||
|
let src_x = x0 + x;
|
||||||
|
let src_y = y0 + y;
|
||||||
|
data[y * 16 + x] =
|
||||||
|
if src_x < image.width && src_y < image.height {
|
||||||
|
layer[src_y * image.width + src_x]
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encode_tile::encode_tile(data, *quality, writer)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
151
src/encode_tile.rs
Normal file
151
src/encode_tile.rs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::protocol::{ProtocolWriter, ProtocolWriterResult};
|
||||||
|
|
||||||
|
pub fn encode_tile<W: Write>(
|
||||||
|
data: [u8; 256],
|
||||||
|
quality: u8,
|
||||||
|
writer: &mut ProtocolWriter<W>
|
||||||
|
) -> ProtocolWriterResult<()> {
|
||||||
|
let mut coefs: [i32; 256] = [0; 256];
|
||||||
|
|
||||||
|
for i in 0..256 {
|
||||||
|
coefs[i] = data[i] as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut transform = |zero: usize, stride: usize| {
|
||||||
|
let mut ixs = [0; 16];
|
||||||
|
ixs[0] = zero;
|
||||||
|
for i in 1..16 {
|
||||||
|
ixs[i] = ixs[i - 1] + stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
for bit in [8, 4, 2, 1] {
|
||||||
|
for i in 0..16 {
|
||||||
|
if i & bit == 0 {
|
||||||
|
let j = i | bit;
|
||||||
|
|
||||||
|
let ival = coefs[ixs[i]];
|
||||||
|
let jval = coefs[ixs[j]];
|
||||||
|
|
||||||
|
coefs[ixs[i]] = ival.wrapping_add(jval);
|
||||||
|
coefs[ixs[j]] = ival.wrapping_sub(jval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for col in 0..16 {
|
||||||
|
transform(col, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in 0..16 {
|
||||||
|
transform(row * 16, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this operation can be efficiently implemented as a heapsort in C
|
||||||
|
// but the code is ugly
|
||||||
|
// so just use Rust's default sort
|
||||||
|
let mut indices = [0; 256];
|
||||||
|
for i in 0..256 {
|
||||||
|
indices[i] = i;
|
||||||
|
}
|
||||||
|
indices.sort_by_key(|ix| -(coefs[*ix]).abs());
|
||||||
|
|
||||||
|
// write indices and values
|
||||||
|
// (note that the first index will almost always be zero)
|
||||||
|
for i in 0..quality {
|
||||||
|
let ix = indices[i as usize];
|
||||||
|
writer.write_u8(ix as u8)?;
|
||||||
|
writer.write_u16(coefs[ix] as u16)?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_value(value: i32) -> u8 {
|
||||||
|
let value = value / 16;
|
||||||
|
let sign = value < 0;
|
||||||
|
let mut rescaled = value.abs() + 33;
|
||||||
|
let mut exponent = 0;
|
||||||
|
|
||||||
|
while rescaled > 64 {
|
||||||
|
exponent += 1;
|
||||||
|
rescaled >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mantissa = (rescaled >> 1) & 0xf;
|
||||||
|
|
||||||
|
return (
|
||||||
|
if sign { 128 } else { 0 } |
|
||||||
|
exponent << 4 |
|
||||||
|
mantissa
|
||||||
|
) as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
use crate::decode_tile::decode_value;
|
||||||
|
|
||||||
|
use super::encode_value;
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn test_encode_value_2(val: u16) -> bool {
|
||||||
|
let val = (val & (0x8000 | 0x3fff)) as i16;
|
||||||
|
let canon = decode_value(encode_value(val as i32));
|
||||||
|
|
||||||
|
canon == decode_value(encode_value(canon as i32))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_value_1() {
|
||||||
|
// ...01abcdx cases
|
||||||
|
assert_eq!(0b00001111, encode_value(0 + (0b111110 - 33)));
|
||||||
|
assert_eq!(0b00001110, encode_value(0 + (0b111100 - 33)));
|
||||||
|
assert_eq!(0b10001111, encode_value(0 - (0b111110 - 33)));
|
||||||
|
assert_eq!(0b10001110, encode_value(0 - (0b111100 - 33)));
|
||||||
|
|
||||||
|
// ...01abcdxx cases
|
||||||
|
assert_eq!(0b00011111, encode_value(0 + (0b1111101 - 33)));
|
||||||
|
assert_eq!(0b00011110, encode_value(0 + (0b1111001 - 33)));
|
||||||
|
assert_eq!(0b10011111, encode_value(0 - (0b1111101 - 33)));
|
||||||
|
assert_eq!(0b10011110, encode_value(0 - (0b1111001 - 33)));
|
||||||
|
|
||||||
|
// ...01abcdxxx cases
|
||||||
|
assert_eq!(0b00101111, encode_value(0 + (0b11111000 - 33)));
|
||||||
|
assert_eq!(0b00101110, encode_value(0 + (0b11110001 - 33)));
|
||||||
|
assert_eq!(0b10101111, encode_value(0 - (0b11111010 - 33)));
|
||||||
|
assert_eq!(0b10101110, encode_value(0 - (0b11110011 - 33)));
|
||||||
|
|
||||||
|
// ...01abcdxxxx cases
|
||||||
|
assert_eq!(0b00111111, encode_value(0 + (0b111110000 - 33)));
|
||||||
|
assert_eq!(0b00111110, encode_value(0 + (0b111100010 - 33)));
|
||||||
|
assert_eq!(0b10111111, encode_value(0 - (0b111110100 - 33)));
|
||||||
|
assert_eq!(0b10111110, encode_value(0 - (0b111100110 - 33)));
|
||||||
|
|
||||||
|
// ...01abcdxxxxx cases
|
||||||
|
assert_eq!(0b01001111, encode_value(0 + (0b1111100000 - 33)));
|
||||||
|
assert_eq!(0b01001110, encode_value(0 + (0b1111000100 - 33)));
|
||||||
|
assert_eq!(0b11001111, encode_value(0 - (0b1111101000 - 33)));
|
||||||
|
assert_eq!(0b11001110, encode_value(0 - (0b1111001100 - 33)));
|
||||||
|
|
||||||
|
// ...01abcdxxxxxx cases
|
||||||
|
assert_eq!(0b01011111, encode_value(0 + (0b11111000000 - 33)));
|
||||||
|
assert_eq!(0b01011110, encode_value(0 + (0b11110001000 - 33)));
|
||||||
|
assert_eq!(0b11011111, encode_value(0 - (0b11111010000 - 33)));
|
||||||
|
assert_eq!(0b11011110, encode_value(0 - (0b11110011000 - 33)));
|
||||||
|
|
||||||
|
// ...01abcdxxxxxxx cases
|
||||||
|
assert_eq!(0b01101111, encode_value(0 + (0b111110000000 - 33)));
|
||||||
|
assert_eq!(0b01101110, encode_value(0 + (0b111100010000 - 33)));
|
||||||
|
assert_eq!(0b11101111, encode_value(0 - (0b111110100000 - 33)));
|
||||||
|
assert_eq!(0b11101110, encode_value(0 - (0b111100110000 - 33)));
|
||||||
|
|
||||||
|
// ...01abcdxxxxxxx cases
|
||||||
|
assert_eq!(0b01111111, encode_value(0 + (0b1111100000000 - 33)));
|
||||||
|
assert_eq!(0b01111110, encode_value(0 + (0b1111000100001 - 33)));
|
||||||
|
assert_eq!(0b11111111, encode_value(0 - (0b1111101000010 - 33)));
|
||||||
|
assert_eq!(0b11111110, encode_value(0 - (0b1111001100011 - 33)));
|
||||||
|
}
|
||||||
|
}
|
45
src/image.rs
Normal file
45
src/image.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
use png::ColorType;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ImageError {
|
||||||
|
#[error("can't make the layer fit {0}x{1}")]
|
||||||
|
WrongSize(usize, usize),
|
||||||
|
#[error("unsupported color type {0:?}")]
|
||||||
|
InvalidInputColorType(ColorType),
|
||||||
|
#[error("can't save an image with {0} layers as png")]
|
||||||
|
WrongNumberOfLayersToSave(usize),
|
||||||
|
|
||||||
|
#[error("general IO error")]
|
||||||
|
IoError(#[from] io::Error),
|
||||||
|
#[error("error in encoding PNG")]
|
||||||
|
PngEncodingError(#[from] png::EncodingError),
|
||||||
|
#[error("error in decoding PNG")]
|
||||||
|
PngDecodingError(#[from] png::DecodingError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Image {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub layers: Vec<(u8, Vec<u8>)>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
pub fn new(width: usize, height: usize) -> Self {
|
||||||
|
Image {
|
||||||
|
width, height,
|
||||||
|
layers: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, quality: u8, pixels: Vec<u8>) -> Result<(), ImageError> {
|
||||||
|
if pixels.len() != self.width * self.height {
|
||||||
|
return Err(ImageError::WrongSize(self.width, self.height));
|
||||||
|
}
|
||||||
|
self.layers.push((quality, pixels));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
42
src/main.rs
Normal file
42
src/main.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use std::{fs::File, io::{Cursor, Write}};
|
||||||
|
|
||||||
|
use protocol::{ProtocolReader, ProtocolWriter};
|
||||||
|
|
||||||
|
mod decode_image;
|
||||||
|
mod decode_tile;
|
||||||
|
mod encode_image;
|
||||||
|
mod encode_tile;
|
||||||
|
mod image;
|
||||||
|
mod png_utils;
|
||||||
|
mod protocol;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// run_for("zonked".to_string(), 9, 2, Some(330000));
|
||||||
|
run_for("zonked".to_string());
|
||||||
|
run_for("avatar2".to_string());
|
||||||
|
}
|
||||||
|
fn run_for(name: String) {
|
||||||
|
let image = png_utils::load_image(
|
||||||
|
format!("inputs/{}.png", name),
|
||||||
|
[5, 6, 5, 0]
|
||||||
|
).unwrap();
|
||||||
|
let mut writer = ProtocolWriter::new(vec![]);
|
||||||
|
|
||||||
|
encode_image::encode_image(
|
||||||
|
image,
|
||||||
|
&mut writer
|
||||||
|
).unwrap();
|
||||||
|
let compressed = writer.destroy();
|
||||||
|
|
||||||
|
let mut output_file = File::create(format!("outputs/{}.rx0", name)).unwrap();
|
||||||
|
output_file.write_all(&compressed).unwrap();
|
||||||
|
|
||||||
|
let mut reader = ProtocolReader::new(Cursor::new(compressed));
|
||||||
|
let decompressed_image = decode_image::decode_image(&mut reader).unwrap();
|
||||||
|
|
||||||
|
png_utils::save_image(
|
||||||
|
format!("outputs/{}_out.png", name),
|
||||||
|
decompressed_image
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
83
src/png_utils.rs
Normal file
83
src/png_utils.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use png::{BitDepth, ColorType};
|
||||||
|
|
||||||
|
use crate::image::{Image, ImageError};
|
||||||
|
|
||||||
|
pub fn load_image(filename: String, quality: [u8; 4]) -> Result<Image, ImageError> {
|
||||||
|
let decoder = png::Decoder::new(File::open(filename).unwrap());
|
||||||
|
let mut reader = decoder.read_info().unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; reader.output_buffer_size()];
|
||||||
|
let info = reader.next_frame(&mut buf).unwrap();
|
||||||
|
let bytes = &buf[..info.buffer_size()];
|
||||||
|
|
||||||
|
assert_eq!(BitDepth::Eight, reader.info().bit_depth);
|
||||||
|
assert_eq!(ColorType::Rgb, reader.info().color_type);
|
||||||
|
|
||||||
|
let width = reader.info().width;
|
||||||
|
let height = reader.info().height;
|
||||||
|
|
||||||
|
match reader.info().color_type {
|
||||||
|
ColorType::Rgb => {
|
||||||
|
assert_eq!(3, reader.info().bytes_per_pixel());
|
||||||
|
|
||||||
|
let r: Vec<u8> = bytes[0..].iter().cloned().step_by(3).collect();
|
||||||
|
let g: Vec<u8> = bytes[1..].iter().cloned().step_by(3).collect();
|
||||||
|
let b: Vec<u8> = bytes[2..].iter().cloned().step_by(3).collect();
|
||||||
|
|
||||||
|
let mut image = Image::new(width as usize, height as usize);
|
||||||
|
image.add(quality[0], r)?;
|
||||||
|
image.add(quality[1], g)?;
|
||||||
|
image.add(quality[2], b)?;
|
||||||
|
return Ok(image);
|
||||||
|
}
|
||||||
|
ColorType::Rgba => {
|
||||||
|
assert_eq!(4, reader.info().bytes_per_pixel());
|
||||||
|
|
||||||
|
let r: Vec<u8> = bytes[0..].iter().cloned().step_by(4).collect();
|
||||||
|
let g: Vec<u8> = bytes[1..].iter().cloned().step_by(4).collect();
|
||||||
|
let b: Vec<u8> = bytes[2..].iter().cloned().step_by(4).collect();
|
||||||
|
let a: Vec<u8> = bytes[3..].iter().cloned().step_by(4).collect();
|
||||||
|
|
||||||
|
let mut image = Image::new(width as usize, height as usize);
|
||||||
|
image.add(quality[0], r)?;
|
||||||
|
image.add(quality[1], g)?;
|
||||||
|
image.add(quality[2], b)?;
|
||||||
|
image.add(quality[3], a)?;
|
||||||
|
return Ok(image);
|
||||||
|
}
|
||||||
|
ct => {
|
||||||
|
return Err(ImageError::InvalidInputColorType(ct))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_image(filename: String, image: Image) -> Result<(), ImageError> {
|
||||||
|
let n_layers = image.layers.len();
|
||||||
|
|
||||||
|
let color_type = match n_layers {
|
||||||
|
3 => ColorType::Rgb,
|
||||||
|
4 => ColorType::Rgba,
|
||||||
|
_ => return Err(ImageError::WrongNumberOfLayersToSave(n_layers)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut encoder = png::Encoder::new(
|
||||||
|
File::create(filename)?,
|
||||||
|
image.width as u32, image.height as u32,
|
||||||
|
);
|
||||||
|
encoder.set_color(color_type);
|
||||||
|
|
||||||
|
let mut idata: Vec<u8> = vec![0; image.width * image.height * n_layers];
|
||||||
|
for i in 0..n_layers {
|
||||||
|
for (dst, src) in
|
||||||
|
idata[i..].iter_mut().step_by(n_layers).zip(image.layers[i].1.iter())
|
||||||
|
{
|
||||||
|
*dst = *src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut writer = encoder.write_header()?;
|
||||||
|
writer.write_image_data(&idata)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
67
src/protocol.rs
Normal file
67
src/protocol.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ProtocolWriterError {
|
||||||
|
#[error("error using underlying writer")]
|
||||||
|
Io(#[from] io::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ProtocolWriterResult<T> = Result<T, ProtocolWriterError>;
|
||||||
|
|
||||||
|
pub struct ProtocolWriter<W: Write> {
|
||||||
|
writer: W
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ProtocolReaderError {
|
||||||
|
#[error("error using underlying reader")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ProtocolReaderResult<T> = Result<T, ProtocolReaderError>;
|
||||||
|
|
||||||
|
pub struct ProtocolReader<R: Read> {
|
||||||
|
reader: R
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> ProtocolWriter<W> {
|
||||||
|
pub fn new(writer: W) -> Self {
|
||||||
|
Self { writer }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self) -> W {
|
||||||
|
self.writer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u8(&mut self, value: u8) -> ProtocolWriterResult<()> {
|
||||||
|
self.writer.write_all(&value.to_le_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u16(&mut self, value: u16) -> ProtocolWriterResult<()> {
|
||||||
|
self.writer.write_all(&value.to_le_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
impl<R: Read> ProtocolReader<R> {
|
||||||
|
pub fn new(reader: R) -> Self {
|
||||||
|
Self { reader }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_u8(&mut self) -> ProtocolReaderResult<u8> {
|
||||||
|
let mut u8_buf = [0; 1];
|
||||||
|
self.reader.read_exact(&mut u8_buf)?;
|
||||||
|
|
||||||
|
Ok(u8::from_le_bytes(u8_buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_u16(&mut self) -> ProtocolReaderResult<u16> {
|
||||||
|
let mut u16_buf = [0; 2];
|
||||||
|
self.reader.read_exact(&mut u16_buf)?;
|
||||||
|
|
||||||
|
Ok(u16::from_le_bytes(u16_buf))
|
||||||
|
}
|
||||||
|
}
|
66
src/transform.rs.old
Normal file
66
src/transform.rs.old
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Ported from:
|
||||||
|
// - https://colab.research.google.com/drive/1WjtKwUcqxWafAumFO9ET74RsckZSsw6n#scrollTo=e3r9-RBpcgwH
|
||||||
|
|
||||||
|
// Specialized for:
|
||||||
|
//
|
||||||
|
// - Input domain: 0-255
|
||||||
|
// - Tile size: 16x16
|
||||||
|
//
|
||||||
|
// Rather than dividing by 16 at the end of a decode phase,
|
||||||
|
// we divide by 16 at the end of the _encode_ phase
|
||||||
|
//
|
||||||
|
// The normal range would be something like -255 * 256 to 255 * 256
|
||||||
|
// (-65280 to 65280) but this reduces it (now it's something like -4080 to 4080)
|
||||||
|
//
|
||||||
|
// This simplifies the decoder (which can be i16) and means it can be written
|
||||||
|
// using i16s instead of i32s
|
||||||
|
//
|
||||||
|
// We could probably shrink the range more by recentering to -128-127, but does
|
||||||
|
// it even matter? Reasonable encodings like A-law and mu-law can handle both.
|
||||||
|
//
|
||||||
|
// (Take my opinions on the range with a grain of salt: I've calculated it but I
|
||||||
|
// haven't measured it)
|
||||||
|
//
|
||||||
|
struct Transform<'d> {
|
||||||
|
data: &'d mut [i32],
|
||||||
|
zero: usize,
|
||||||
|
stride: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Transform<'d> {
|
||||||
|
pub fn encode(&mut self) {
|
||||||
|
self.transform();
|
||||||
|
self.divide(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(&mut self) {
|
||||||
|
self.transform();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ix(&self, i: usize) -> usize {
|
||||||
|
self.zero + t * self.stride
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform(&mut self) {
|
||||||
|
for bit in [8, 4, 2, 1] {
|
||||||
|
for i in 0..16 {
|
||||||
|
if i & bit == 0 {
|
||||||
|
let j = i | bit;
|
||||||
|
|
||||||
|
let ival = self.data[self.ix(i)];
|
||||||
|
let jval = self.data[self.ix(j)];
|
||||||
|
|
||||||
|
self.data[self.ix(i)] = ival.wrapping_add(jval);
|
||||||
|
self.data[self.ix(j)] = ival.wrapping_sub(jval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn divide(&mut self, divisor: i32) {
|
||||||
|
for i in 0..16 {
|
||||||
|
self.data[self.ix(i)] = self.data[self.ix(j)] / divisor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user