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 signature, expected \"dangoimg\" got {0:?}")]
InvalidIdentifier(String),
#[error("io operation failed: {0}")]
IoError(#[from] io::Error),
#[error("compression operation failed: {0}")]
CompressionError(#[from] CompressionError),
}
pub struct SquishyPicture {
header: Header,
bitmap: Vec<u8>,
}
impl SquishyPicture {
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,
}
}
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,
)
}
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,
)
}
pub fn encode<O: Write + WriteBytesExt>(&self, mut output: O) -> Result<usize, Error> {
let mut count = 0;
count += self.header.write_into(&mut output)?;
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 (compressed_data, compression_info) = compress(modified_data)?;
count += compression_info.write_into(&mut output).unwrap();
output.write_all(&compressed_data).unwrap();
count += compressed_data.len();
Ok(count)
}
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(())
}
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 })
}
pub fn as_raw(&self) -> &Vec<u8> {
&self.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)?)
}