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