Better PNG interface
This commit is contained in:
parent
9505b5754d
commit
de10e4b4b8
4
TODO.md
4
TODO.md
@ -1,8 +1,8 @@
|
|||||||
Support an alpha channel.
|
|
||||||
Improve the PNG interface.
|
|
||||||
Actually support terminating early for "progressive" loads
|
Actually support terminating early for "progressive" loads
|
||||||
|
|
||||||
Done:
|
Done:
|
||||||
|
Support an alpha channel.
|
||||||
|
Improve the PNG interface.
|
||||||
Interleave layers instead of having them consecutively. (Bad idea, breaks RLE. Skipping)
|
Interleave layers instead of having them consecutively. (Bad idea, breaks RLE. Skipping)
|
||||||
Global parameters to control encoder settings:
|
Global parameters to control encoder settings:
|
||||||
- Quantization level: quant 0
|
- Quantization level: quant 0
|
||||||
|
BIN
inputs/zonked.png
Normal file
BIN
inputs/zonked.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 518 KiB |
@ -1,6 +1,6 @@
|
|||||||
use std::io::Write;
|
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 {
|
struct PixelTile {
|
||||||
@ -20,37 +20,34 @@ struct QuantTile {
|
|||||||
|
|
||||||
|
|
||||||
pub fn compress<W: Write>(
|
pub fn compress<W: Write>(
|
||||||
width: u32,
|
image: Image,
|
||||||
height: u32,
|
|
||||||
// n_components: u16,
|
|
||||||
layers: &[(QualitySettings, &[u8])],
|
|
||||||
writer: &mut protocol::ProtocolWriter<W>
|
writer: &mut protocol::ProtocolWriter<W>
|
||||||
) -> ProtocolWriterResult<()> {
|
) -> ProtocolWriterResult<()> {
|
||||||
|
|
||||||
// validation
|
// validation
|
||||||
for l in 0..layers.len() {
|
for l in 0..image.layers.len() {
|
||||||
assert!(layers[l].1.len() == width as usize * height as usize);
|
assert!(image.layers[l].1.len() == image.width as usize * image.height as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write header
|
// write header
|
||||||
writer.write_header(MAGIC)?;
|
writer.write_header(MAGIC)?;
|
||||||
writer.write_u32_wide(width)?;
|
writer.write_u32_wide(image.width as u32)?;
|
||||||
writer.write_u32_wide(height)?;
|
writer.write_u32_wide(image.height as u32)?;
|
||||||
writer.write_u32_wide(layers.len() as u32)?;
|
writer.write_u32_wide(image.layers.len() as u32)?;
|
||||||
|
|
||||||
// write quality settings for each layout
|
// write quality settings for each layout
|
||||||
for (quality, _) in layers.iter() {
|
for (quality, _) in image.layers.iter() {
|
||||||
writer.write_quality_settings(*quality)?;
|
writer.write_quality_settings(*quality)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// build list of tiles
|
// build list of tiles
|
||||||
let mut tiles = vec![];
|
let mut tiles = vec![];
|
||||||
for (quality, layer) in layers.iter() {
|
for (quality, layer) in image.layers.iter() {
|
||||||
for x0 in (0..width).step_by(TILE_SZ) {
|
for x0 in (0..image.width).step_by(TILE_SZ) {
|
||||||
for y0 in (0..height).step_by(TILE_SZ) {
|
for y0 in (0..image.height).step_by(TILE_SZ) {
|
||||||
let pixel_tile = PixelTile::from_layer(
|
let pixel_tile = PixelTile::from_layer(
|
||||||
x0 as usize, y0 as usize, *quality, 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 coef_tile = CoefTile::from_pixel_tile(&pixel_tile);
|
||||||
let quant_tile = QuantTile::from_coef_tile(&coef_tile);
|
let quant_tile = QuantTile::from_coef_tile(&coef_tile);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::io::Read;
|
use std::io::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 {
|
struct PixelTile {
|
||||||
pixels: [i16; TILE_SZ2]
|
pixels: [i16; TILE_SZ2]
|
||||||
@ -19,7 +19,7 @@ struct QuantTile {
|
|||||||
|
|
||||||
pub fn decompress<R: Read>(
|
pub fn decompress<R: Read>(
|
||||||
reader: &mut ProtocolReader<R>
|
reader: &mut ProtocolReader<R>
|
||||||
) -> ProtocolReaderResult<(u32, u32, Vec<Vec<u8>>)> {
|
) -> ProtocolReaderResult<Image> {
|
||||||
// read header
|
// read header
|
||||||
let header = reader.read_header()?;
|
let header = reader.read_header()?;
|
||||||
if header.bytes != MAGIC.bytes {
|
if header.bytes != MAGIC.bytes {
|
||||||
@ -78,7 +78,14 @@ 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(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoefTile {
|
impl CoefTile {
|
||||||
|
47
src/image.rs
Normal file
47
src/image.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
96
src/main.rs
96
src/main.rs
@ -1,104 +1,44 @@
|
|||||||
use std::{fs::File, io::{Cursor, Write}};
|
use std::{fs::File, io::{Cursor, Write}};
|
||||||
|
|
||||||
use png::{BitDepth, ColorType};
|
use crate::{protocol::{ProtocolReader, ProtocolWriter}, quality_settings::QualitySettings};
|
||||||
|
|
||||||
use crate::protocol::{ProtocolReader, ProtocolWriter};
|
|
||||||
|
|
||||||
mod compression;
|
mod compression;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod decompression;
|
mod decompression;
|
||||||
|
mod png_utils;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod quality_settings;
|
mod quality_settings;
|
||||||
mod quantization;
|
mod quantization;
|
||||||
mod transform;
|
mod transform;
|
||||||
|
mod image;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
for chunk in vec![
|
run_for("zonked".to_string());
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
run_for("avatar2".to_string());
|
||||||
[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();
|
|
||||||
}
|
}
|
||||||
fn hard_main() {
|
fn run_for(name: String) {
|
||||||
let (width, height, r, g, b) = load_image();
|
let image = png_utils::load_image(
|
||||||
|
format!("inputs/{}.png", name),
|
||||||
|
QualitySettings::new(2),
|
||||||
|
QualitySettings::new(2),
|
||||||
|
).unwrap();
|
||||||
let mut writer = ProtocolWriter::new(vec![]);
|
let mut writer = ProtocolWriter::new(vec![]);
|
||||||
|
|
||||||
let quality_settings = quality_settings::QualitySettings::new(3);
|
|
||||||
|
|
||||||
compression::compress(
|
compression::compress(
|
||||||
width as u32, height as u32, &[(quality_settings, &r), (quality_settings, &g), (quality_settings, &b)],
|
image,
|
||||||
&mut writer
|
&mut writer
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let compressed = writer.destroy();
|
let compressed = writer.destroy();
|
||||||
|
|
||||||
let mut output_file = File::create("outputs/avatar2.rxi").unwrap();
|
let mut output_file = File::create(format!("outputs/{}.rxi", name)).unwrap();
|
||||||
output_file.write_all(&compressed).unwrap();
|
output_file.write_all(&compressed).unwrap();
|
||||||
|
|
||||||
let mut reader = ProtocolReader::new(Cursor::new(compressed));
|
let mut reader = ProtocolReader::new(Cursor::new(compressed));
|
||||||
let (width2, height2, decompressed) =
|
let decompressed = decompression::decompress(&mut reader).unwrap();
|
||||||
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 mut writer = encoder.write_header().unwrap();
|
|
||||||
writer.write_image_data(&idata).unwrap();
|
|
||||||
|
|
||||||
|
png_utils::save_image(
|
||||||
|
format!("outputs/{}_out.png", name),
|
||||||
|
decompressed
|
||||||
|
).unwrap();
|
||||||
}
|
}
|
83
src/png_utils.rs
Normal file
83
src/png_utils.rs
Normal 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(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user