mirror of
https://github.com/Dangoware/sqp.git
synced 2025-04-19 07:12:55 -05:00
178 lines
5.4 KiB
Rust
178 lines
5.4 KiB
Rust
use std::{fs::File, io::{self, BufWriter, Read, Write}};
|
|
|
|
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::{diff_line, line_diff},
|
|
};
|
|
|
|
pub struct DangoPicture {
|
|
pub header: Header,
|
|
pub bitmap: Vec<u8>,
|
|
}
|
|
|
|
#[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),
|
|
}
|
|
|
|
impl DangoPicture {
|
|
/// Create a DPF from raw bytes in a particular [`ColorFormat`].
|
|
///
|
|
/// ## Example
|
|
/// ```
|
|
/// let dpf_lossy = DangoPicture::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,
|
|
compression_level: Option<u8>,
|
|
bitmap: Vec<u8>,
|
|
) -> Self {
|
|
let compression_level = match compression_level {
|
|
Some(level) => {
|
|
if level < 1 || level > 100 {
|
|
panic!("Compression level out of range 1..100")
|
|
}
|
|
level as i8
|
|
},
|
|
None => -1,
|
|
};
|
|
|
|
let header = Header {
|
|
magic: *b"dangoimg",
|
|
|
|
width,
|
|
height,
|
|
|
|
compression_type,
|
|
compression_level,
|
|
|
|
color_format,
|
|
};
|
|
|
|
DangoPicture {
|
|
header,
|
|
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 => {
|
|
&diff_line(self.header.width, self.header.height, &self.bitmap)
|
|
},
|
|
CompressionType::LossyDct => {
|
|
&dct_compress(
|
|
&self.bitmap,
|
|
DctParameters {
|
|
quality: self.header.compression_level 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()
|
|
},
|
|
};
|
|
|
|
// 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<DangoPicture, 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 => {
|
|
line_diff(header.width, header.height, &pre_bitmap)
|
|
},
|
|
CompressionType::LossyDct => {
|
|
let mut decoded = Vec::new();
|
|
let mut offset = 0;
|
|
loop {
|
|
if offset > pre_bitmap.len() {
|
|
break;
|
|
}
|
|
|
|
if let Some(num) = i16::decode_var(&pre_bitmap[offset..]) {
|
|
offset += num.1;
|
|
decoded.push(num.0 as i16);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
dct_decompress(
|
|
&decoded,
|
|
DctParameters {
|
|
quality: header.compression_level as u32,
|
|
format: header.color_format,
|
|
width: header.width as usize,
|
|
height: header.height as usize,
|
|
}
|
|
)
|
|
},
|
|
};
|
|
|
|
Ok(DangoPicture { header, bitmap })
|
|
}
|
|
}
|