mirror of
https://github.com/Dangoware/sqp.git
synced 2025-04-19 07:12:55 -05:00
230 lines
6.7 KiB
Rust
230 lines
6.7 KiB
Rust
//! Functions and other utilities surrounding the [`SquishyPicture`] type.
|
|
|
|
use std::{fs::File, io::{self, BufWriter, Read, Write}, path::Path};
|
|
|
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
|
use integer_encoding::VarInt;
|
|
use thiserror::Error;
|
|
|
|
use crate::{
|
|
compression::{dct::{dct_compress, dct_decompress, DctParameters},
|
|
lossless::{compress, decompress, CompressionError, CompressionInfo}},
|
|
header::{ColorFormat, CompressionType, Header},
|
|
operations::{add_rows, sub_rows},
|
|
};
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum Error {
|
|
#[error("incorrect identifier, got {0:02X?}")]
|
|
InvalidIdentifier([u8; 8]),
|
|
|
|
#[error("io operation failed: {0}")]
|
|
IoError(#[from] io::Error),
|
|
|
|
#[error("compression operation failed: {0}")]
|
|
CompressionError(#[from] CompressionError),
|
|
}
|
|
|
|
/// The basic Squishy Picture type for manipulation in-memory.
|
|
pub struct SquishyPicture {
|
|
pub header: Header,
|
|
pub bitmap: Vec<u8>,
|
|
}
|
|
|
|
impl SquishyPicture {
|
|
/// Create a DPF from raw bytes in a particular [`ColorFormat`].
|
|
///
|
|
/// The quality parameter does nothing if the compression type is not
|
|
/// lossy, so it should be set to None.
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// let dpf_lossy = SquishyPicture::from_raw(
|
|
/// input.width(),
|
|
/// input.height(),
|
|
/// ColorFormat::Rgba32,
|
|
/// CompressionType::LossyDct,
|
|
/// Some(80),
|
|
/// input.as_raw().clone()
|
|
/// );
|
|
/// ```
|
|
pub fn from_raw(
|
|
width: u32,
|
|
height: u32,
|
|
color_format: ColorFormat,
|
|
compression_type: CompressionType,
|
|
quality: Option<u8>,
|
|
bitmap: Vec<u8>,
|
|
) -> Self {
|
|
if quality.is_none() && compression_type == CompressionType::LossyDct {
|
|
panic!("compression level must not be `None` when compression type is lossy")
|
|
}
|
|
|
|
let header = Header {
|
|
magic: *b"dangoimg",
|
|
|
|
width,
|
|
height,
|
|
|
|
compression_type,
|
|
quality: match quality {
|
|
Some(level) => level.clamp(1, 100),
|
|
None => 0,
|
|
},
|
|
|
|
color_format,
|
|
};
|
|
|
|
Self {
|
|
header,
|
|
bitmap,
|
|
}
|
|
}
|
|
|
|
/// Convenience method over [`SquishyPicture::from_raw`] which creates a
|
|
/// lossy image with a given quality.
|
|
pub fn from_raw_lossy(
|
|
width: u32,
|
|
height: u32,
|
|
color_format: ColorFormat,
|
|
quality: u8,
|
|
bitmap: Vec<u8>,
|
|
) -> Self {
|
|
Self::from_raw(
|
|
width,
|
|
height,
|
|
color_format,
|
|
CompressionType::LossyDct,
|
|
Some(quality),
|
|
bitmap,
|
|
)
|
|
}
|
|
|
|
/// Convenience method over [`SquishyPicture::from_raw`] which creates a
|
|
/// lossless image.
|
|
pub fn from_raw_lossless(
|
|
width: u32,
|
|
height: u32,
|
|
color_format: ColorFormat,
|
|
bitmap: Vec<u8>,
|
|
) -> Self {
|
|
Self::from_raw(
|
|
width,
|
|
height,
|
|
color_format,
|
|
CompressionType::Lossless,
|
|
None,
|
|
bitmap,
|
|
)
|
|
}
|
|
|
|
/// Encode the image into anything that implements [`Write`].
|
|
///
|
|
/// Returns the number of bytes written.
|
|
pub fn encode<O: Write + WriteBytesExt>(&self, mut output: O) -> Result<usize, Error> {
|
|
let mut count = 0;
|
|
|
|
// Write out the header
|
|
output.write_all(&self.header.to_bytes()).unwrap();
|
|
count += self.header.len();
|
|
|
|
// Based on the compression type, modify the data accordingly
|
|
let modified_data = match self.header.compression_type {
|
|
CompressionType::None => &self.bitmap,
|
|
CompressionType::Lossless => {
|
|
&sub_rows(
|
|
self.header.width,
|
|
self.header.height,
|
|
self.header.color_format,
|
|
&self.bitmap
|
|
)
|
|
},
|
|
CompressionType::LossyDct => {
|
|
&dct_compress(
|
|
&self.bitmap,
|
|
DctParameters {
|
|
quality: self.header.quality as u32,
|
|
format: self.header.color_format,
|
|
width: self.header.width as usize,
|
|
height: self.header.height as usize,
|
|
}
|
|
)
|
|
.concat()
|
|
.into_iter()
|
|
.flat_map(VarInt::encode_var_vec)
|
|
.collect()
|
|
},
|
|
};
|
|
|
|
let mut inspection_file = File::create("raw_data").unwrap();
|
|
inspection_file.write_all(&modified_data).unwrap();
|
|
|
|
// Compress the final image data using the basic LZW scheme
|
|
let (compressed_data, compression_info) = compress(modified_data)?;
|
|
|
|
// Write out compression info
|
|
count += compression_info.write_into(&mut output).unwrap();
|
|
|
|
// Write out compressed data
|
|
output.write_all(&compressed_data).unwrap();
|
|
count += compressed_data.len();
|
|
|
|
Ok(count)
|
|
}
|
|
|
|
/// Encode and write the image out to a file.
|
|
pub fn save<P: ?Sized + AsRef<std::path::Path>>(&self, path: &P) -> Result<(), Error> {
|
|
let mut out_file = BufWriter::new(File::create(path.as_ref())?);
|
|
|
|
self.encode(&mut out_file)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Decode the image from anything that implements [`Read`]
|
|
pub fn decode<I: Read + ReadBytesExt>(mut input: I) -> Result<Self, Error> {
|
|
let header = Header::read_from(&mut input)?;
|
|
|
|
let compression_info = CompressionInfo::read_from(&mut input);
|
|
|
|
let pre_bitmap = decompress(&mut input, &compression_info);
|
|
|
|
let bitmap = match header.compression_type {
|
|
CompressionType::None => pre_bitmap,
|
|
CompressionType::Lossless => {
|
|
add_rows(header.width, header.height, header.color_format, &pre_bitmap)
|
|
},
|
|
CompressionType::LossyDct => {
|
|
dct_decompress(
|
|
&decode_varint_stream(&pre_bitmap),
|
|
DctParameters {
|
|
quality: header.quality as u32,
|
|
format: header.color_format,
|
|
width: header.width as usize,
|
|
height: header.height as usize,
|
|
}
|
|
)
|
|
},
|
|
};
|
|
|
|
Ok(Self { header, bitmap })
|
|
}
|
|
}
|
|
|
|
fn decode_varint_stream(stream: &[u8]) -> Vec<i16> {
|
|
let mut output = Vec::new();
|
|
let mut offset = 0;
|
|
|
|
while let Some(num) = i16::decode_var(&stream[offset..]) {
|
|
offset += num.1;
|
|
output.push(num.0);
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<SquishyPicture, Error> {
|
|
let input = File::open(path)?;
|
|
|
|
Ok(SquishyPicture::decode(input)?)
|
|
}
|