diff --git a/TODO.md b/TODO.md index f7a8281..9126cf5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,9 @@ Support an alpha channel. Improve the PNG interface. +Actually support terminating early for "progressive" loads + +Done: +Interleave layers instead of having them consecutively. (Bad idea, breaks RLE. Skipping) Global parameters to control encoder settings: - Quantization level: quant 0 - Quantization level: quants 1-63 @@ -7,6 +11,4 @@ Global parameters to control encoder settings: - Linear term - Number of quants to keep, per tile. - Whether to use wide or packed representation for the thumbnail block -Interleave layers instead of having them consecutively. -Maybe have a second instance of the global parameters block for the alpha channel. -Actually support terminating early for "progressive" loads \ No newline at end of file +Maybe have a second instance of the global parameters block for the alpha channel. \ No newline at end of file diff --git a/src/compression.rs b/src/compression.rs index 4abd32b..bddcd5b 100644 --- a/src/compression.rs +++ b/src/compression.rs @@ -1,18 +1,20 @@ use std::io::Write; -use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, protocol::{self, ProtocolWriter, ProtocolWriterResult}, quantization, transform}; +use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, protocol::{self, ProtocolWriter, ProtocolWriterResult}, quality_settings::{self, QualitySettings}, quantization, transform}; struct PixelTile { - // i32: representation that supports Walsh-Hadamard + quality: QualitySettings, pixels: [i16; TILE_SZ2] } struct CoefTile { + quality: QualitySettings, coefs: [i16; TILE_SZ2] } #[derive(Clone, Copy)] struct QuantTile { + quality: QualitySettings, quants: [i16; TILE_SZ2] } @@ -21,13 +23,13 @@ pub fn compress( width: u32, height: u32, // n_components: u16, - layers: &[&[u8]], + layers: &[(QualitySettings, &[u8])], writer: &mut protocol::ProtocolWriter ) -> ProtocolWriterResult<()> { // validation for l in 0..layers.len() { - assert!(layers[l].len() == width as usize * height as usize); + assert!(layers[l].1.len() == width as usize * height as usize); } // write header @@ -36,12 +38,18 @@ pub fn compress( writer.write_u32_wide(height)?; writer.write_u32_wide(layers.len() as u32)?; + // write quality settings for each layout + for (quality, _) in layers.iter() { + writer.write_quality_settings(*quality)?; + } + + // build list of tiles let mut tiles = vec![]; - for layer in layers.iter() { + for (quality, 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, + x0 as usize, y0 as usize, *quality, layer, width as usize, height as usize ); let coef_tile = CoefTile::from_pixel_tile(&pixel_tile); @@ -51,13 +59,16 @@ pub fn compress( } } + // write the thumbnail block for t in tiles.iter() { t.write_zero(writer)?; } - for ti in 0..tiles.len() { - let prev = if ti > 0 { Some(tiles[ti - 1]) } else { None }; - let t = tiles[ti]; + + // write the tiles + let mut prev: Option = None; + for t in tiles { t.write_rest(prev, writer)?; + prev = Some(t); } Ok(()) @@ -67,6 +78,7 @@ impl PixelTile { fn from_layer( x0: usize, y0: usize, + quality: QualitySettings, layer: &[u8], width: usize, height: usize @@ -85,7 +97,7 @@ impl PixelTile { } } - return PixelTile { pixels }; + return PixelTile { quality, pixels }; } } @@ -103,19 +115,24 @@ impl CoefTile { transform::encode(&mut coefs, x, 8); } - return CoefTile { coefs } + return CoefTile { quality: pt.quality, coefs } } } impl QuantTile { fn from_coef_tile(pt: &CoefTile) -> QuantTile { QuantTile { - quants: quantization::to_quantized(pt.coefs) + quality: pt.quality, + quants: quantization::to_quantized(pt.quality, pt.coefs) } } fn write_zero(&self, writer: &mut ProtocolWriter) -> ProtocolWriterResult<()> { - writer.write_i16_wide(self.quants[0])?; + if self.quality.values[quality_settings::THUMBNAIL_IS_WIDE] != 0 { + writer.write_i16_wide(self.quants[0])?; + } else { + writer.write_i16_packed(self.quants[0])?; + } Ok(()) } diff --git a/src/decompression.rs b/src/decompression.rs index d74e894..554f8d6 100644 --- a/src/decompression.rs +++ b/src/decompression.rs @@ -1,9 +1,8 @@ use std::io::Read; -use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, protocol::{ProtocolReader, ProtocolReaderError, ProtocolReaderResult}, quantization, transform}; +use crate::{constants::{MAGIC, TILE_SZ, TILE_SZ2}, protocol::{ProtocolReader, ProtocolReaderError, ProtocolReaderResult}, quality_settings::{self, QualitySettings}, quantization, transform}; struct PixelTile { - // i32: representation that supports Walsh-Hadamard pixels: [i16; TILE_SZ2] } @@ -14,6 +13,7 @@ struct CoefTile { #[derive(Clone, Copy)] struct QuantTile { + quality: QualitySettings, quants: [i16; TILE_SZ2] } @@ -36,17 +36,32 @@ pub fn decompress( 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![QuantTile::new(); n_tiles]; - for i in 0..n_tiles { - tiles[i].load_zero(reader)?; + 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()?); } - for i in 0..n_tiles { - let prev = if i > 0 { Some(tiles[i - 1]) } else { None }; + + // 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 + )?; + } + } + + // 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]); } let mut tile_i = 0; @@ -110,17 +125,29 @@ impl PixelTile { impl QuantTile { fn new() -> QuantTile { - QuantTile { quants: [0; TILE_SZ2] } + QuantTile { + quality: QualitySettings { values: [0, 0, 0, 0, 0, 0, 0, 0] }, + quants: [0; TILE_SZ2] + } } fn to_coef_tile(&self) -> CoefTile { CoefTile { - coefs: quantization::from_quantized(self.quants) + coefs: quantization::from_quantized(self.quality, self.quants) } } - fn load_zero(&mut self, reader: &mut ProtocolReader) -> ProtocolReaderResult<()> { - self.quants[0] = reader.read_i16_wide()?; + fn load_zero( + &mut self, + quality: QualitySettings, + reader: &mut ProtocolReader + ) -> ProtocolReaderResult<()> { + self.quality = quality; + if quality.values[quality_settings::THUMBNAIL_IS_WIDE] != 0 { + self.quants[0] = reader.read_i16_wide()?; + } else { + self.quants[0] = reader.read_i16_packed()?; + } Ok(()) } diff --git a/src/main.rs b/src/main.rs index 6892323..b92c536 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod compression; mod constants; mod decompression; mod protocol; +mod quality_settings; mod quantization; mod transform; @@ -33,8 +34,11 @@ fn main() { fn hard_main() { let (width, height, r, g, b) = load_image(); let mut writer = ProtocolWriter::new(vec![]); + + let quality_settings = quality_settings::QualitySettings::new(3); + compression::compress( - width as u32, height as u32, &[&r, &g, &b], + width as u32, height as u32, &[(quality_settings, &r), (quality_settings, &g), (quality_settings, &b)], &mut writer ).unwrap(); let compressed = writer.destroy(); diff --git a/src/protocol.rs b/src/protocol.rs index d4bcf9b..2b546bd 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -2,6 +2,8 @@ use std::io::{self, ErrorKind, Read, Write}; use thiserror::Error; +use crate::quality_settings::QualitySettings; + #[derive(Clone, Copy)] pub struct Header { pub bytes: [u8; 3], @@ -57,6 +59,12 @@ impl ProtocolWriter { self.writer.write_all(&[value.version])?; Ok(()) } + + pub fn write_quality_settings(&mut self, quality: QualitySettings) -> ProtocolWriterResult<()> { + self.writer.write_all(&quality.values)?; + Ok(()) + } + pub fn write_u32_wide(&mut self, value: u32) -> ProtocolWriterResult<()> { self.writer.write_all(&value.to_le_bytes())?; Ok(()) @@ -100,6 +108,13 @@ impl ProtocolReader { }) } + pub fn read_quality_settings(&mut self) -> ProtocolReaderResult { + let mut qsettings_buf = [0; 8]; + self.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)?; diff --git a/src/quality_settings.rs b/src/quality_settings.rs new file mode 100644 index 0000000..56de88f --- /dev/null +++ b/src/quality_settings.rs @@ -0,0 +1,40 @@ +#[derive(Clone, Copy)] +pub struct QualitySettings { + pub values: [u8; 8], +} + +pub const QUANTIZATION_QUANT0_DIVISOR: usize = 0; +pub const QUANTIZATION_CONSTANT: usize = 1; +pub const QUANTIZATION_LINEAR: usize = 2; +pub const QUANTIZATION_N_QUANTS_TO_KEEP: usize = 3; +pub const THUMBNAIL_IS_WIDE: usize = 4; +// 5, 6, 7: reserved + + +impl QualitySettings { + pub fn new(level: u8) -> Self { + // Concept: + // Levels 0, 1, and 2 are incredibly deep fried + // Levels 3, 4, 5, 6 are usable with artifacting + // Levels 7, 8, 9 are above what you would seriously use + if level == 0 { + return QualitySettings { values: [255, 255, 32, 1, 0, 0, 0, 0] }; + } else if level == 1 { + return QualitySettings { values: [192, 192, 24, 2, 0, 0, 0, 0] }; + } else if level == 2 { + return QualitySettings { values: [128, 128, 16, 3, 0, 0, 0, 0] }; + } else if level == 3 { + return QualitySettings { values: [96, 96, 12, 4, 0, 0, 0, 0] }; + + // TODO: Levels 4, 5, 6 + + + } else if level == 7 { + return QualitySettings { values: [1, 24, 8, 24, 1, 0, 0, 0] }; + } else if level == 8 { + return QualitySettings { values: [1, 12, 4, 48, 1, 0, 0, 0] }; + } else { // level >= 9 + return QualitySettings { values: [1, 1, 1, 96, 1, 0, 0, 0] }; + } + } +} \ No newline at end of file diff --git a/src/quantization.rs b/src/quantization.rs index 46856d5..3101e27 100644 --- a/src/quantization.rs +++ b/src/quantization.rs @@ -1,4 +1,4 @@ -use crate::constants::TILE_SZ2; +use crate::{constants::TILE_SZ2, quality_settings::{self, QualitySettings}}; const ZIGZAG: [u8; TILE_SZ2] = [ 0 , 1 , 5 , 6 , 14, 15, 27, 28, @@ -12,12 +12,13 @@ const ZIGZAG: [u8; TILE_SZ2] = [ ]; pub fn to_quantized( + quality_settings: QualitySettings, coefs: [i16; TILE_SZ2] ) -> [i16; TILE_SZ2] { let mut quant: [i16; TILE_SZ2] = [0; TILE_SZ2]; for cf_ix in 0..TILE_SZ2 { - let div = divisor(cf_ix); + let div = divisor(quality_settings, cf_ix); let qval = (coefs[cf_ix] + div / 2) / div; quant[ZIGZAG[cf_ix] as usize] = qval } @@ -27,7 +28,7 @@ pub fn to_quantized( indices[i] = i; } indices.sort_by_key(|i| -quant[*i].abs()); - for i in 4..indices.len() { + for i in (quality_settings.values[quality_settings::QUANTIZATION_N_QUANTS_TO_KEEP] as usize)..indices.len() { quant[indices[i]] = 0; } @@ -35,21 +36,30 @@ pub fn to_quantized( } pub fn from_quantized( + quality_settings: QualitySettings, quant: [i16; TILE_SZ2] ) -> [i16; TILE_SZ2] { let mut coefs: [i16; TILE_SZ2] = [0; TILE_SZ2]; for cf_ix in 0..TILE_SZ2 { - let div = divisor(cf_ix); + let div = divisor(quality_settings, cf_ix); coefs[cf_ix] = quant[ZIGZAG[cf_ix] as usize].wrapping_mul(div); } coefs } -pub fn divisor(cf_ix: usize) -> i16 { - if cf_ix == 0 { return 1; } +pub fn divisor( + quality_settings: QualitySettings, + cf_ix: usize +) -> i16 { + if cf_ix == 0 { return quality_settings.values[quality_settings::QUANTIZATION_QUANT0_DIVISOR] as i16; } let x = cf_ix % 8; let y = cf_ix / 8; - let div = 32 + (x as i16 + y as i16) * 12; + let div = + (quality_settings.values[quality_settings::QUANTIZATION_CONSTANT] as i16).wrapping_add( + (x as i16 + y as i16).wrapping_mul( + quality_settings.values[quality_settings::QUANTIZATION_LINEAR] as i16 + ) + ); if div==32 && cf_ix != 0 { dbg!(cf_ix, x, y, div); }