Compare commits

...

2 Commits

Author SHA1 Message Date
f047642f37 TODOs complete. More TODOs? 2024-04-07 23:31:22 -07:00
de10e4b4b8 Better PNG interface 2024-04-07 22:52:20 -07:00
8 changed files with 281 additions and 165 deletions

11
TODO.md
View File

@@ -1,8 +1,13 @@
Support an alpha channel.
Improve the PNG interface.
Actually support terminating early for "progressive" loads
TODO:
I still think we can interleave layers to get better progressive loading. But we need FC, FD, FE, and FF commands for "rerun 4/3/2/1 tiles ago."
Make a super simple one corresponding to quality 3 or so, controlled by constants?
Generally improve code quality.
Done:
Actually support terminating early for "progressive" loads
Support an alpha channel.
Improve the PNG interface.
Interleave layers instead of having them consecutively. (Bad idea, breaks RLE. Skipping)
Global parameters to control encoder settings:
- Quantization level: quant 0

BIN
inputs/zonked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View File

@@ -1,6 +1,6 @@
use std::io::Write;
use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, protocol::{self, ProtocolWriter, ProtocolWriterResult}, quality_settings::{self, QualitySettings}, quantization, transform};
use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, image::Image, protocol::{self, ProtocolWriter, ProtocolWriterResult}, quality_settings::{self, QualitySettings}, quantization, transform};
struct PixelTile {
@@ -20,37 +20,34 @@ struct QuantTile {
pub fn compress<W: Write>(
width: u32,
height: u32,
// n_components: u16,
layers: &[(QualitySettings, &[u8])],
image: Image,
writer: &mut protocol::ProtocolWriter<W>
) -> ProtocolWriterResult<()> {
// validation
for l in 0..layers.len() {
assert!(layers[l].1.len() == width as usize * height as usize);
for l in 0..image.layers.len() {
assert!(image.layers[l].1.len() == image.width as usize * image.height as usize);
}
// write header
writer.write_header(MAGIC)?;
writer.write_u32_wide(width)?;
writer.write_u32_wide(height)?;
writer.write_u32_wide(layers.len() as u32)?;
writer.write_u32_wide(image.width as u32)?;
writer.write_u32_wide(image.height as u32)?;
writer.write_u32_wide(image.layers.len() as u32)?;
// write quality settings for each layout
for (quality, _) in layers.iter() {
for (quality, _) in image.layers.iter() {
writer.write_quality_settings(*quality)?;
}
// build list of tiles
let mut tiles = vec![];
for (quality, layer) in layers.iter() {
for x0 in (0..width).step_by(TILE_SZ) {
for y0 in (0..height).step_by(TILE_SZ) {
for (quality, layer) in image.layers.iter() {
for y0 in (0..image.height).step_by(TILE_SZ) {
for x0 in (0..image.width).step_by(TILE_SZ) {
let pixel_tile = PixelTile::from_layer(
x0 as usize, y0 as usize, *quality, layer,
width as usize, height as usize
image.width as usize, image.height as usize
);
let coef_tile = CoefTile::from_pixel_tile(&pixel_tile);
let quant_tile = QuantTile::from_coef_tile(&coef_tile);
@@ -84,8 +81,8 @@ impl PixelTile {
height: usize
) -> PixelTile {
let mut pixels = [0; TILE_SZ2];
for x in 0..TILE_SZ {
for y in 0..TILE_SZ {
for y in 0..TILE_SZ {
for x in 0..TILE_SZ {
let src_x = x0 + x;
let src_y = y0 + y;
pixels[y * TILE_SZ + x] =

View File

@@ -1,6 +1,6 @@
use std::io::Read;
use std::io::{ErrorKind, Read};
use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, protocol::{ProtocolReader, ProtocolReaderError, ProtocolReaderResult}, quality_settings::{self, QualitySettings}, quantization, transform};
use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, image::Image, protocol::{ProtocolReader, ProtocolReaderError, ProtocolReaderResult}, quality_settings::{self, QualitySettings}, quantization, transform};
struct PixelTile {
pixels: [i16; TILE_SZ2]
@@ -17,58 +17,98 @@ struct QuantTile {
quants: [i16; TILE_SZ2]
}
pub enum ProgressiveImage {
Present { image: Image, complete: bool },
NotHereYet
}
pub fn decompress<R: Read>(
reader: &mut ProtocolReader<R>
) -> ProtocolReaderResult<(u32, u32, Vec<Vec<u8>>)> {
) -> ProtocolReaderResult<ProgressiveImage> {
// read header
let header = reader.read_header()?;
if header.bytes != MAGIC.bytes {
return Err(ProtocolReaderError::WrongHeader);
}
if header.version != MAGIC.version {
return Err(ProtocolReaderError::WrongVersion);
}
let read_header_and_stuff: Result<_, ProtocolReaderError> = (|| {
let header = reader.read_header()?;
if header.bytes != MAGIC.bytes {
return Err(ProtocolReaderError::WrongHeader);
}
if header.version != MAGIC.version {
return Err(ProtocolReaderError::WrongVersion);
}
let width = reader.read_u32_wide()?;
let height = reader.read_u32_wide()?;
let n_layers = reader.read_u32_wide()?;
let width = reader.read_u32_wide()?;
let height = reader.read_u32_wide()?;
let n_layers = reader.read_u32_wide()?;
// read quality settings for each layout
let mut quality_settings = vec![];
for _ in 0..n_layers {
quality_settings.push(reader.read_quality_settings()?);
}
Ok((width, height, n_layers, quality_settings))
})();
let Ok((width, height, n_layers, quality_settings)) = read_header_and_stuff else {
match read_header_and_stuff {
Ok(_) => unreachable!(),
Err(ProtocolReaderError::Io(io)) => {
if io.kind() == ErrorKind::UnexpectedEof {
return ProtocolReaderResult::Ok(ProgressiveImage::NotHereYet)
}
return Err(ProtocolReaderError::Io(io))
}
Err(e) => return Err(e),
}
};
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_per_layer = width_in_tiles * height_in_tiles;
// read quality settings for each layout
let mut quality_settings = vec![];
for _ in 0..n_layers {
quality_settings.push(reader.read_quality_settings()?);
}
// read thumbnail block
let mut tiles = vec![QuantTile::new(); n_tiles_per_layer * n_layers as usize];
for l in 0..n_layers {
for i in 0..n_tiles_per_layer {
tiles[l as usize * n_tiles_per_layer + i].load_zero(
quality_settings[l as usize], reader
)?;
let read_tiles_and_stuff: Result<(), ProtocolReaderError> = (|tiles: &mut [QuantTile]| {
// read thumbnail block
for l in 0..n_layers {
for i in 0..n_tiles_per_layer {
tiles[l as usize * n_tiles_per_layer + i].load_zero(
quality_settings[l as usize], reader
)?;
}
}
}
// read remaining tiles
let mut prev: Option<QuantTile> = None;
for i in 0..n_tiles_per_layer * n_layers as usize {
tiles[i].load_rest(
prev,
reader
)?;
prev = Some(tiles[i]);
}
// read remaining tiles
let mut prev: Option<QuantTile> = None;
for i in 0..n_tiles_per_layer * n_layers as usize {
tiles[i].load_rest(
prev,
reader
)?;
prev = Some(tiles[i]);
}
Ok(())
})(&mut tiles);
let complete;
match read_tiles_and_stuff {
Err(ProtocolReaderError::Io(i)) => {
if i.kind() == ErrorKind::UnexpectedEof {
complete = false
} else {
return Err(ProtocolReaderError::Io(i))
}
}
Err(e) => return Err(e),
Ok(()) => {
complete = true;
}
};
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) {
for y0 in (0..height).step_by(TILE_SZ) {
for x0 in (0..width).step_by(TILE_SZ) {
let pixel_tile = tiles[tile_i].to_coef_tile().to_pixel_tile();
tile_i += 1;
pixel_tile.to_layer(
@@ -78,7 +118,13 @@ pub fn decompress<R: Read>(
}
}
}
return Ok((width, height, layers))
let mut image = Image::new(width as usize, height as usize);
for (l, layer) in layers.into_iter().enumerate() {
image.add(quality_settings[l], layer).expect("by construction, this layer should be OK");
}
return Ok(ProgressiveImage::Present { image: image, complete });
}
impl CoefTile {
@@ -101,8 +147,8 @@ impl CoefTile {
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 {
for y in 0..TILE_SZ {
for x in 0..TILE_SZ {
let dst_x = x0 + x;
let dst_y = y0 + y;
if dst_x < width && dst_y < height {

47
src/image.rs Normal file
View File

@@ -0,0 +1,47 @@
use std::io;
use png::ColorType;
use thiserror::Error;
use crate::quality_settings::QualitySettings;
#[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<(QualitySettings, Vec<u8>)>
}
impl Image {
pub fn new(width: usize, height: usize) -> Self {
Image {
width, height,
layers: vec![]
}
}
pub fn add(&mut self, quality: QualitySettings, 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(())
}
}

View File

@@ -1,104 +1,60 @@
use std::{fs::File, io::{Cursor, Write}};
use png::{BitDepth, ColorType};
use crate::protocol::{ProtocolReader, ProtocolWriter};
use crate::{protocol::{ProtocolReader, ProtocolWriter}, quality_settings::QualitySettings};
mod compression;
mod constants;
mod decompression;
mod png_utils;
mod protocol;
mod quality_settings;
mod quantization;
mod transform;
mod image;
fn main() {
for chunk in vec![
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 2, 3, 4, 5, 6, 7],
[255, 255, 255, 255, 255, 255, 255, 80],
[255, 255, 255, 255, 255, 255, 255, 0],
[255, 253, 254, 252, 251, 252, 255, 254]
] {
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();
// run_for("zonked".to_string(), 9, 2, Some(330000));
run_for("zonked".to_string(), 9, 2, Some(37000));
run_for("avatar2".to_string(), 9, 2, Some(100000));
}
fn hard_main() {
let (width, height, r, g, b) = load_image();
fn run_for(name: String, quality_rgb: u8, quality_alpha: u8, bytes_to_chop: Option<usize>) {
let image = png_utils::load_image(
format!("inputs/{}.png", name),
QualitySettings::new(quality_rgb),
QualitySettings::new(quality_alpha),
).unwrap();
let mut writer = ProtocolWriter::new(vec![]);
let quality_settings = quality_settings::QualitySettings::new(3);
compression::compress(
width as u32, height as u32, &[(quality_settings, &r), (quality_settings, &g), (quality_settings, &b)],
image,
&mut writer
).unwrap();
let compressed = writer.destroy();
let mut compressed = writer.destroy();
let mut output_file = File::create("outputs/avatar2.rxi").unwrap();
if let Some(b2c) = bytes_to_chop {
compressed.drain(b2c..);
}
let mut output_file = File::create(format!("outputs/{}.rxi", name)).unwrap();
output_file.write_all(&compressed).unwrap();
let mut reader = ProtocolReader::new(Cursor::new(compressed));
let (width2, height2, decompressed) =
decompression::decompress(&mut reader).unwrap();
let decompressed = decompression::decompress(&mut reader).unwrap();
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_out.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 decompressed_image = match decompressed {
decompression::ProgressiveImage::Present { image, complete: _ } => {
image
}
}
decompression::ProgressiveImage::NotHereYet => {
// image not here yet
println!("not here yet: {}", name);
return
}
};
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&idata).unwrap();
}
png_utils::save_image(
format!("outputs/{}_out.png", name),
decompressed_image
).unwrap();
}

83
src/png_utils.rs Normal file
View File

@@ -0,0 +1,83 @@
use std::fs::File;
use png::{BitDepth, ColorType};
use crate::{image::{Image, ImageError}, quality_settings::QualitySettings};
pub fn load_image(filename: String, quality_rgb: QualitySettings, quality_alpha: QualitySettings) -> 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_rgb, r)?;
image.add(quality_rgb, g)?;
image.add(quality_rgb, 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_rgb, r)?;
image.add(quality_rgb, g)?;
image.add(quality_rgb, b)?;
image.add(quality_alpha, 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(())
}

View File

@@ -1,4 +1,4 @@
use std::io::{self, ErrorKind, Read, Write};
use std::io::{self, Read, Write};
use thiserror::Error;
@@ -25,10 +25,6 @@ pub struct ProtocolWriter<W: Write> {
#[derive(Error, Debug)]
pub enum ProtocolReaderError {
// this is explicitly supported: rxi images are progressive
#[error("EOF before end of image")]
EarlyEof,
#[error("wrong header")]
WrongHeader,
@@ -97,10 +93,10 @@ impl<R: Read> ProtocolReader<R> {
pub fn read_header(&mut self) -> ProtocolReaderResult<Header> {
let mut bytes_buf = [0; 3];
self.read_exact(&mut bytes_buf)?;
self.reader.read_exact(&mut bytes_buf)?;
let mut version_buf = [0; 1];
self.read_exact(&mut version_buf)?;
self.reader.read_exact(&mut version_buf)?;
Ok(Header {
bytes: bytes_buf,
@@ -110,55 +106,41 @@ impl<R: Read> ProtocolReader<R> {
pub fn read_quality_settings(&mut self) -> ProtocolReaderResult<QualitySettings> {
let mut qsettings_buf = [0; 8];
self.read_exact(&mut qsettings_buf)?;
self.reader.read_exact(&mut qsettings_buf)?;
Ok(QualitySettings { values: qsettings_buf })
}
pub fn read_u32_wide(&mut self) -> ProtocolReaderResult<u32> {
let mut u32_buf = [0; 4];
self.read_exact(&mut u32_buf)?;
self.reader.read_exact(&mut u32_buf)?;
Ok(u32::from_le_bytes(u32_buf))
}
pub fn read_i16_wide(&mut self) -> ProtocolReaderResult<i16> {
let mut i16_buf = [0; 2];
self.read_exact(&mut i16_buf)?;
self.reader.read_exact(&mut i16_buf)?;
Ok(i16::from_le_bytes(i16_buf))
}
pub fn read_u8_wide(&mut self) -> ProtocolReaderResult<u8> {
let mut u8_buf = [0; 1];
self.read_exact(&mut u8_buf)?;
self.reader.read_exact(&mut u8_buf)?;
Ok(u8::from_le_bytes(u8_buf))
}
pub fn read_i16_packed(&mut self) -> ProtocolReaderResult<i16> {
let mut i8_buf = [0; 1];
self.read_exact(&mut i8_buf)?;
self.reader.read_exact(&mut i8_buf)?;
let i8_value = i8::from_le_bytes(i8_buf);
if i8_value != i8::MAX { return Ok(i8_value as i16) }
let mut i16_buf = [0; 2];
self.read_exact(&mut i16_buf)?;
self.reader.read_exact(&mut i16_buf)?;
let i16_value = i16::from_le_bytes(i16_buf);
Ok(i16_value as i16)
}
// wrap UnexpectedEof, since that's an error for other Io clients,
// but it's specifically not an error for us
fn read_exact(&mut self, buf: &mut [u8]) -> ProtocolReaderResult<()> {
match self.reader.read_exact(buf) {
Ok(_) => Ok(()),
Err(e) => {
if e.kind() == ErrorKind::UnexpectedEof {
return Err(ProtocolReaderError::EarlyEof)
}
return Err(ProtocolReaderError::Io(e));
}
}
}
}