diff --git a/TODO.md b/TODO.md index 5622bb1..cec14bc 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,11 @@ -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) diff --git a/src/compression.rs b/src/compression.rs index f68b0be..4020649 100644 --- a/src/compression.rs +++ b/src/compression.rs @@ -43,8 +43,8 @@ pub fn compress( // build list of tiles let mut tiles = vec![]; for (quality, layer) in image.layers.iter() { - for x0 in (0..image.width).step_by(TILE_SZ) { - for y0 in (0..image.height).step_by(TILE_SZ) { + 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, image.width as usize, image.height as usize @@ -81,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] = diff --git a/src/decompression.rs b/src/decompression.rs index 6ac57d9..9723572 100644 --- a/src/decompression.rs +++ b/src/decompression.rs @@ -1,4 +1,4 @@ -use std::io::Read; +use std::io::{ErrorKind, Read}; use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, image::Image, protocol::{ProtocolReader, ProtocolReaderError, ProtocolReaderResult}, quality_settings::{self, QualitySettings}, quantization, transform}; @@ -17,58 +17,98 @@ struct QuantTile { quants: [i16; TILE_SZ2] } +pub enum ProgressiveImage { + Present { image: Image, complete: bool }, + NotHereYet +} + pub fn decompress( reader: &mut ProtocolReader -) -> ProtocolReaderResult { +) -> ProtocolReaderResult { // 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 = 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 = 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( @@ -81,11 +121,10 @@ pub fn decompress( 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"); + image.add(quality_settings[l], layer).expect("by construction, this layer should be OK"); } - return Ok(image) + return Ok(ProgressiveImage::Present { image: image, complete }); } impl CoefTile { @@ -108,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 { diff --git a/src/main.rs b/src/main.rs index 0db6758..be74f80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,14 +14,15 @@ mod image; fn main() { - run_for("zonked".to_string()); - run_for("avatar2".to_string()); + // 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 run_for(name: String) { +fn run_for(name: String, quality_rgb: u8, quality_alpha: u8, bytes_to_chop: Option) { let image = png_utils::load_image( format!("inputs/{}.png", name), - QualitySettings::new(2), - QualitySettings::new(2), + QualitySettings::new(quality_rgb), + QualitySettings::new(quality_alpha), ).unwrap(); let mut writer = ProtocolWriter::new(vec![]); @@ -29,7 +30,11 @@ fn run_for(name: String) { image, &mut writer ).unwrap(); - let compressed = writer.destroy(); + let mut compressed = writer.destroy(); + + 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(); @@ -37,8 +42,19 @@ fn run_for(name: String) { let mut reader = ProtocolReader::new(Cursor::new(compressed)); let decompressed = decompression::decompress(&mut reader).unwrap(); + let decompressed_image = match decompressed { + decompression::ProgressiveImage::Present { image, complete: _ } => { + image + } + decompression::ProgressiveImage::NotHereYet => { + // image not here yet + println!("not here yet: {}", name); + return + } + }; + png_utils::save_image( format!("outputs/{}_out.png", name), - decompressed + decompressed_image ).unwrap(); } diff --git a/src/protocol.rs b/src/protocol.rs index 2b546bd..f8d8718 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -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 { #[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 ProtocolReader { pub fn read_header(&mut self) -> ProtocolReaderResult
{ 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 ProtocolReader { pub fn read_quality_settings(&mut self) -> ProtocolReaderResult { 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 { 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 { 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 { 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 { 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)); - } - } - } } \ No newline at end of file