Select quality
This commit is contained in:
parent
e87b406240
commit
9505b5754d
8
TODO.md
8
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
|
||||
Maybe have a second instance of the global parameters block for the alpha channel.
|
@ -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<W: Write>(
|
||||
width: u32,
|
||||
height: u32,
|
||||
// n_components: u16,
|
||||
layers: &[&[u8]],
|
||||
layers: &[(QualitySettings, &[u8])],
|
||||
writer: &mut protocol::ProtocolWriter<W>
|
||||
) -> 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<W: Write>(
|
||||
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<W: Write>(
|
||||
}
|
||||
}
|
||||
|
||||
// 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<QuantTile> = 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<W: Write>(&self, writer: &mut ProtocolWriter<W>) -> 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(())
|
||||
}
|
||||
|
||||
|
@ -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<R: Read>(
|
||||
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<QuantTile> = 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<R: Read>(&mut self, reader: &mut ProtocolReader<R>) -> ProtocolReaderResult<()> {
|
||||
self.quants[0] = reader.read_i16_wide()?;
|
||||
fn load_zero<R: Read>(
|
||||
&mut self,
|
||||
quality: QualitySettings,
|
||||
reader: &mut ProtocolReader<R>
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<W: Write> ProtocolWriter<W> {
|
||||
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<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)?;
|
||||
|
||||
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)?;
|
||||
|
40
src/quality_settings.rs
Normal file
40
src/quality_settings.rs
Normal file
@ -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] };
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user