First technically working code
This commit is contained in:
commit
d04ec371d9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/outputs
|
85
Cargo.lock
generated
Normal file
85
Cargo.lock
generated
Normal file
@ -0,0 +1,85 @@
|
||||
# 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 = "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 = "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 = "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 = "rximg"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "rximg"
|
||||
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"
|
BIN
inputs/avatar2.png
Normal file
BIN
inputs/avatar2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 761 KiB |
111
src/compression.rs
Normal file
111
src/compression.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::{constants::{MAGIC, TILE_SZ}, transform};
|
||||
|
||||
|
||||
struct PixelTile {
|
||||
// i32: representation that supports Walsh-Hadamard
|
||||
pixels: [i32; TILE_SZ * TILE_SZ]
|
||||
}
|
||||
struct CoefTile {
|
||||
coefs: [i32; TILE_SZ * TILE_SZ]
|
||||
}
|
||||
|
||||
|
||||
pub fn compress(
|
||||
width: u16,
|
||||
height: u16,
|
||||
// n_components: u16,
|
||||
layers: &[&[u8]],
|
||||
) -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
|
||||
// validation
|
||||
for l in 0..layers.len() {
|
||||
assert!(layers[l].len() == width as usize * height as usize);
|
||||
}
|
||||
|
||||
// write header
|
||||
output.write(&MAGIC).unwrap();
|
||||
output.write(&(width as u16).to_le_bytes()).unwrap();
|
||||
output.write(&(height as u16).to_le_bytes()).unwrap();
|
||||
// output.write(&n_components.to_le_bytes()).unwrap();
|
||||
output.write(&(layers.len() as u16).to_le_bytes()).unwrap();
|
||||
|
||||
let mut tiles = vec![];
|
||||
for layer in layers.iter() {
|
||||
for x0 in (0..width).step_by(TILE_SZ) {
|
||||
for y0 in (0..height).step_by(TILE_SZ) {
|
||||
let pixel_tile = PixelTile::from_layer(
|
||||
x0 as usize, y0 as usize, layer,
|
||||
width as usize, height as usize
|
||||
);
|
||||
let coef_tile = CoefTile::from_pixel_tile(&pixel_tile);
|
||||
tiles.push(coef_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for t in tiles.iter() {
|
||||
t.write_zero(&mut output);
|
||||
}
|
||||
for t in tiles.iter() {
|
||||
t.write_rest(&mut output);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
impl PixelTile {
|
||||
fn from_layer(
|
||||
x0: usize,
|
||||
y0: usize,
|
||||
layer: &[u8],
|
||||
width: usize,
|
||||
height: usize
|
||||
) -> PixelTile {
|
||||
let mut pixels = [0; TILE_SZ * TILE_SZ];
|
||||
for x in 0..TILE_SZ {
|
||||
for y in 0..TILE_SZ {
|
||||
let src_x = x0 + x;
|
||||
let src_y = y0 + y;
|
||||
pixels[y * TILE_SZ + x] =
|
||||
if src_x < width && src_y < height {
|
||||
layer[src_y * width + src_x] as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return PixelTile { pixels };
|
||||
}
|
||||
}
|
||||
|
||||
impl CoefTile {
|
||||
fn from_pixel_tile(pt: &PixelTile) -> CoefTile {
|
||||
let mut coefs = pt.pixels.clone();
|
||||
|
||||
// rows
|
||||
for y in 0..TILE_SZ {
|
||||
transform::encode(&mut coefs, y * TILE_SZ, 1);
|
||||
}
|
||||
|
||||
// columns
|
||||
for x in 0..TILE_SZ {
|
||||
transform::encode(&mut coefs, x * TILE_SZ, 1);
|
||||
}
|
||||
|
||||
return CoefTile { coefs }
|
||||
}
|
||||
|
||||
fn write_zero(&self, output: &mut Vec<u8>) {
|
||||
output.write(&self.coefs[0].to_le_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn write_rest(&self, output: &mut Vec<u8>) {
|
||||
for i in 1..self.coefs.len() {
|
||||
output.write(&self.coefs[i].to_le_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
2
src/constants.rs
Normal file
2
src/constants.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub const MAGIC: [u8; 4] = *b"rxi\0";
|
||||
pub const TILE_SZ: usize = 8;
|
111
src/decompression.rs
Normal file
111
src/decompression.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use std::io::{BufReader, Read};
|
||||
|
||||
use crate::{constants::{MAGIC, TILE_SZ}, transform};
|
||||
|
||||
struct PixelTile {
|
||||
// i32: representation that supports Walsh-Hadamard
|
||||
pixels: [i32; TILE_SZ * TILE_SZ]
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CoefTile {
|
||||
coefs: [i32; TILE_SZ * TILE_SZ]
|
||||
}
|
||||
|
||||
pub fn decompress(
|
||||
input: &[u8]
|
||||
) -> (u16, u16, Vec<Vec<u8>>) {
|
||||
let mut reader = BufReader::new(input);
|
||||
|
||||
// read header
|
||||
let mut magic_buffer = [0; MAGIC.len()];
|
||||
reader.read_exact(&mut magic_buffer).unwrap();
|
||||
|
||||
assert_eq!(MAGIC, magic_buffer);
|
||||
|
||||
let mut u16_buffer = [0; 2];
|
||||
reader.read_exact(&mut u16_buffer).unwrap();
|
||||
let width = u16::from_le_bytes(u16_buffer);
|
||||
reader.read_exact(&mut u16_buffer).unwrap();
|
||||
let height = u16::from_le_bytes(u16_buffer);
|
||||
reader.read_exact(&mut u16_buffer).unwrap();
|
||||
let n_layers = u16::from_le_bytes(u16_buffer);
|
||||
|
||||
let width_in_tiles = (width as usize + TILE_SZ - 1) / TILE_SZ;
|
||||
let height_in_tiles = (height as usize + TILE_SZ - 1) / TILE_SZ;
|
||||
|
||||
let n_tiles = width_in_tiles * height_in_tiles * (n_layers as usize);
|
||||
let mut tiles = vec![CoefTile::new(); n_tiles];
|
||||
for i in 0..n_tiles {
|
||||
tiles[i].load_zero(&mut reader);
|
||||
}
|
||||
for i in 0..n_tiles {
|
||||
tiles[i].load_rest(&mut reader);
|
||||
}
|
||||
|
||||
let mut tile_i = 0;
|
||||
let mut layers = vec![vec![0; width as usize * height as usize]; n_layers as usize];
|
||||
for layer in 0..n_layers {
|
||||
for x0 in (0..width).step_by(TILE_SZ) {
|
||||
for y0 in (0..height).step_by(TILE_SZ) {
|
||||
let pixel_tile = tiles[tile_i].to_pixel_tile();
|
||||
tile_i += 1;
|
||||
pixel_tile.to_layer(
|
||||
x0 as usize, y0 as usize, &mut layers[layer as usize],
|
||||
width as usize, height as usize
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (width, height, layers)
|
||||
}
|
||||
|
||||
impl CoefTile {
|
||||
fn new() -> CoefTile {
|
||||
CoefTile { coefs: [0; TILE_SZ * TILE_SZ] }
|
||||
}
|
||||
|
||||
fn load_zero(&mut self, reader: &mut BufReader<&[u8]>) {
|
||||
let mut coef_zero_buffer = [0; 4];
|
||||
reader.read_exact(&mut coef_zero_buffer).unwrap();
|
||||
self.coefs[0] = i32::from_le_bytes(coef_zero_buffer);
|
||||
}
|
||||
|
||||
fn load_rest(&mut self, reader: &mut BufReader<&[u8]>) {
|
||||
let mut coef_buffer = [0; 4];
|
||||
for i in 1..self.coefs.len() {
|
||||
reader.read_exact(&mut coef_buffer).unwrap();
|
||||
self.coefs[i] = i32::from_le_bytes(coef_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
fn to_pixel_tile(&self) -> PixelTile {
|
||||
let mut pixels = self.coefs.clone();
|
||||
|
||||
// columns
|
||||
for x in 0..TILE_SZ {
|
||||
transform::decode(&mut pixels, x * TILE_SZ, 1);
|
||||
}
|
||||
|
||||
// rows
|
||||
for y in 0..TILE_SZ {
|
||||
transform::decode(&mut pixels, y * TILE_SZ, 1);
|
||||
}
|
||||
|
||||
return PixelTile { pixels };
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTile {
|
||||
fn to_layer(&self, x0: usize, y0: usize, layer: &mut [u8], width: usize, height: usize) {
|
||||
for x in 0..TILE_SZ {
|
||||
for y in 0..TILE_SZ {
|
||||
let dst_x = x0 + x;
|
||||
let dst_y = y0 + y;
|
||||
if dst_x < width && dst_y < height {
|
||||
layer[dst_y * width + dst_x] = self.pixels[y * TILE_SZ + x] as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
src/main.rs
Normal file
90
src/main.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use std::fs::File;
|
||||
|
||||
use png::{BitDepth, ColorType};
|
||||
|
||||
mod compression;
|
||||
mod constants;
|
||||
mod decompression;
|
||||
mod transform;
|
||||
|
||||
|
||||
fn main() {
|
||||
for chunk in vec![
|
||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7]
|
||||
] {
|
||||
let orig = chunk;
|
||||
let mut enc = chunk.clone();
|
||||
transform::encode(&mut enc, 0, 1);
|
||||
let mut dec = enc.clone();
|
||||
transform::decode(&mut dec, 0, 1);
|
||||
dbg!(orig, enc, dec);
|
||||
}
|
||||
|
||||
hard_main();
|
||||
}
|
||||
fn hard_main() {
|
||||
let (width, height, r, g, b) = load_image();
|
||||
let compressed =
|
||||
compression::compress(width as u16, height as u16, &[&r, &g, &b]);
|
||||
|
||||
/*
|
||||
let mut output_file = File::create("outputs/avatar2.rxi").unwrap();
|
||||
output_file.write_all(&compressed).unwrap();
|
||||
*/
|
||||
|
||||
let (width2, height2, decompressed) =
|
||||
decompression::decompress(&compressed);
|
||||
|
||||
assert_eq!(3, decompressed.len());
|
||||
save_image(width2 as usize, height2 as usize, decompressed);
|
||||
}
|
||||
|
||||
|
||||
fn load_image() -> (usize, usize, Vec<u8>, Vec<u8>, Vec<u8>) {
|
||||
let decoder = png::Decoder::new(File::open("inputs/avatar2.png").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()];
|
||||
|
||||
let width = reader.info().width;
|
||||
let height = reader.info().height;
|
||||
assert_eq!(BitDepth::Eight, reader.info().bit_depth);
|
||||
assert_eq!(3, reader.info().bytes_per_pixel());
|
||||
assert_eq!(ColorType::Rgb, reader.info().color_type);
|
||||
|
||||
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();
|
||||
|
||||
assert_eq!(r.len(), (width * height) as usize);
|
||||
assert_eq!(g.len(), (width * height) as usize);
|
||||
assert_eq!(b.len(), (width * height) as usize);
|
||||
|
||||
(width as usize, height as usize, r, g, b)
|
||||
}
|
||||
|
||||
fn save_image(width: usize, height: usize, image: Vec<Vec<u8>>) {
|
||||
assert_eq!(image.len(), 3);
|
||||
|
||||
let mut encoder = png::Encoder::new(
|
||||
File::create("outputs/avatar2.png").unwrap(),
|
||||
width as u32, height as u32,
|
||||
);
|
||||
encoder.set_color(ColorType::Rgb);
|
||||
|
||||
let mut idata: Vec<u8> = vec![0; width * height * 3];
|
||||
for i in [0, 1, 2] {
|
||||
for (dst, src) in
|
||||
idata[i..].iter_mut().step_by(3).zip(image[i].iter())
|
||||
{
|
||||
*dst = *src;
|
||||
}
|
||||
}
|
||||
|
||||
let mut writer = encoder.write_header().unwrap();
|
||||
writer.write_image_data(&idata).unwrap();
|
||||
|
||||
}
|
40
src/transform.rs
Normal file
40
src/transform.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use crate::constants::TILE_SZ;
|
||||
|
||||
// This is the Walsh-Hadamard transform, specialized to 8px
|
||||
// Ported from:
|
||||
// - https://colab.research.google.com/drive/1WjtKwUcqxWafAumFO9ET74RsckZSsw6n#scrollTo=e3r9-RBpcgwH
|
||||
pub fn encode(data: &mut [i32], zero: usize, stride: usize) {
|
||||
transform(data, zero, stride, 1)
|
||||
}
|
||||
|
||||
pub fn decode(data: &mut [i32], zero: usize, stride: usize) {
|
||||
transform(data, zero, stride, 8)
|
||||
}
|
||||
|
||||
fn transform(data: &mut [i32], zero: usize, stride: usize, divisor: i32) {
|
||||
let ix = |t| zero + t * stride;
|
||||
|
||||
let mut row = [0; TILE_SZ];
|
||||
for i in 0..TILE_SZ {
|
||||
row[i] = data[ix(i)];
|
||||
}
|
||||
|
||||
for bit in [4, 2, 1] {
|
||||
for i in 0..TILE_SZ {
|
||||
if i & bit == 0 {
|
||||
let j = i | bit;
|
||||
|
||||
let tmp = row[i];
|
||||
row[i] = row[i].wrapping_add(row[j]);
|
||||
row[j] = tmp.wrapping_sub(row[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reorder according to gray code
|
||||
// basically, this moves important stuff towards the left
|
||||
const GRAY: [usize; TILE_SZ] = [0, 4, 6, 2, 3, 7, 5, 1];
|
||||
for i in 0..TILE_SZ {
|
||||
data[ix(GRAY[i])] = row[i] / divisor;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user