mirror of
https://github.com/Dangoware/sqp.git
synced 2025-04-19 07:12:55 -05:00
Cleaned up code and applied clippy
suggestions
This commit is contained in:
parent
a74e5b2e95
commit
5502578bb2
7 changed files with 101 additions and 77 deletions
|
@ -42,11 +42,7 @@ impl<'a, O: Write + WriteBytesExt> BitWriter<'a, O> {
|
|||
|
||||
/// Check if the stream is aligned to a byte.
|
||||
pub fn aligned(&self) -> bool {
|
||||
if self.bit_offset() == 0 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
self.bit_offset() == 0
|
||||
}
|
||||
|
||||
/// Align the writer to the nearest byte by padding with zero bits.
|
||||
|
|
|
@ -81,7 +81,7 @@ pub fn idct(input: &[f32], width: usize, height: usize) -> Vec<u8> {
|
|||
sqrt_height
|
||||
};
|
||||
|
||||
let idct = input[u * width + v] as f32 *
|
||||
let idct = input[u * width + v] *
|
||||
f32::cos((2.0 * x as f32 + 1.0) * u as f32 * PI / (2.0 * width as f32)) *
|
||||
f32::cos((2.0 * y as f32 + 1.0) * v as f32 * PI / (2.0 * height as f32));
|
||||
|
||||
|
@ -137,7 +137,7 @@ pub fn quantize(input: &[f32], quant_matrix: [u16; 64]) -> Vec<i16> {
|
|||
pub fn dequantize(input: &[i16], quant_matrix: [u16; 64]) -> Vec<f32> {
|
||||
input.iter()
|
||||
.zip(quant_matrix)
|
||||
.map(|(v, q)| (*v as i16 * q as i16) as f32)
|
||||
.map(|(v, q)| (*v * q as i16) as f32)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,12 @@ pub fn dct_compress(input: &[u8], parameters: DctParameters) -> Vec<Vec<i16>> {
|
|||
.collect();
|
||||
|
||||
// Create 2d array of the channel for ease of processing
|
||||
let mut img_2d: Vec<Vec<u8>> = channel.windows(parameters.width).step_by(parameters.width).map(|r| r.to_vec()).collect();
|
||||
let mut img_2d: Vec<Vec<u8>> =
|
||||
channel.windows(parameters.width)
|
||||
.step_by(parameters.width)
|
||||
.map(|r| r.to_vec())
|
||||
.collect();
|
||||
|
||||
img_2d.iter_mut().for_each(|r| r.resize(new_width, 0));
|
||||
img_2d.resize(new_height, vec![0u8; new_width]);
|
||||
|
||||
|
@ -170,7 +175,7 @@ pub fn dct_compress(input: &[u8], parameters: DctParameters) -> Vec<Vec<i16>> {
|
|||
let mut chunk = Vec::new();
|
||||
for i in 0..8 {
|
||||
let row = &img_2d[(h * 8) + i][w * 8..(w * 8) + 8];
|
||||
chunk.extend_from_slice(&row);
|
||||
chunk.extend_from_slice(row);
|
||||
}
|
||||
|
||||
// Perform the DCT on the image section
|
||||
|
@ -201,7 +206,7 @@ pub fn dct_decompress(input: &[i16], parameters: DctParameters) -> Vec<u8> {
|
|||
input.par_chunks(new_width * new_height).enumerate().for_each(|(chan_num, channel)| {
|
||||
let decoded_image = Arc::new(Mutex::new(vec![0u8; parameters.width * parameters.height]));
|
||||
channel.par_chunks(64).enumerate().for_each(|(i, chunk)| {
|
||||
let dequantized_dct = dequantize(&chunk, quantization_matrix);
|
||||
let dequantized_dct = dequantize(chunk, quantization_matrix);
|
||||
let original = idct(&dequantized_dct, 8, 8);
|
||||
|
||||
// Write rows of blocks
|
||||
|
@ -261,25 +266,13 @@ impl Default for DctParameters {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
quality: 80,
|
||||
format: ColorFormat::Rgba32,
|
||||
format: ColorFormat::Rgba8,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The results of DCT compression
|
||||
pub struct DctImage {
|
||||
/// The DCT encoded version of each channel.
|
||||
pub channels: Vec<Vec<i16>>,
|
||||
|
||||
/// New width after padding.
|
||||
pub width: u32,
|
||||
|
||||
/// New height after padding.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -111,7 +111,7 @@ pub fn compress(data: &[u8]) -> Result<(Vec<u8>, CompressionInfo), CompressionEr
|
|||
|
||||
fn compress_lzw(data: &[u8], last: Vec<u8>) -> (usize, Vec<u8>, Vec<u8>) {
|
||||
let mut count = 0;
|
||||
let mut dictionary: HashMap<Vec<u8>, u64> = HashMap::from_iter((0..=255).into_iter().map(|i| (vec![i], i as u64)));
|
||||
let mut dictionary: HashMap<Vec<u8>, u64> = HashMap::from_iter((0..=255).map(|i| (vec![i], i as u64)));
|
||||
let mut dictionary_count = (dictionary.len() + 1) as u64;
|
||||
|
||||
let mut element = Vec::new();
|
||||
|
@ -233,7 +233,7 @@ fn decompress_lzw(input_data: &[u8], size: usize) -> Result<Vec<u8>, Compression
|
|||
let data_size = input_data.len();
|
||||
|
||||
let mut bit_io = BitReader::new(&mut data);
|
||||
let mut w = dictionary.get(0).unwrap().clone();
|
||||
let mut w = dictionary.first().unwrap().clone();
|
||||
|
||||
let mut element;
|
||||
loop {
|
||||
|
|
|
@ -19,8 +19,8 @@ pub struct Header {
|
|||
pub compression_type: CompressionType,
|
||||
|
||||
/// Level of compression. Only applies in Lossy mode, otherwise this value
|
||||
/// should be set to -1.
|
||||
pub compression_level: i8,
|
||||
/// should be set to 0, and ignored.
|
||||
pub quality: u8,
|
||||
|
||||
/// Format of color data in the image.
|
||||
pub color_format: ColorFormat,
|
||||
|
@ -33,8 +33,8 @@ impl Default for Header {
|
|||
width: 0,
|
||||
height: 0,
|
||||
compression_type: CompressionType::Lossless,
|
||||
compression_level: -1,
|
||||
color_format: ColorFormat::Rgba32,
|
||||
quality: 0,
|
||||
color_format: ColorFormat::Rgba8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ impl Header {
|
|||
|
||||
// Write compression info
|
||||
buf.write_u8(self.compression_type.into()).unwrap();
|
||||
buf.write_i8(self.compression_level).unwrap();
|
||||
buf.write_u8(self.quality).unwrap();
|
||||
|
||||
// Write color format
|
||||
buf.write_u8(self.color_format as u8).unwrap();
|
||||
|
@ -57,10 +57,13 @@ impl Header {
|
|||
buf.into_inner().try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Length of the header in bytes.
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
19
|
||||
}
|
||||
|
||||
/// Create a header from something implementing [`Read`].
|
||||
pub fn read_from<T: Read + ReadBytesExt>(input: &mut T) -> Result<Self, Error> {
|
||||
let mut magic = [0u8; 8];
|
||||
input.read_exact(&mut magic).unwrap();
|
||||
|
@ -75,7 +78,7 @@ impl Header {
|
|||
height: input.read_u32::<LE>()?,
|
||||
|
||||
compression_type: input.read_u8()?.try_into().unwrap(),
|
||||
compression_level: input.read_i8()?,
|
||||
quality: input.read_u8()?,
|
||||
color_format: input.read_u8()?.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
@ -86,10 +89,10 @@ impl Header {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ColorFormat {
|
||||
/// RGBA, 8 bits per channel
|
||||
Rgba32 = 0,
|
||||
Rgba8 = 0,
|
||||
|
||||
/// RGB, 8 bits per channel
|
||||
Rgb24 = 1,
|
||||
Rgb8 = 1,
|
||||
}
|
||||
|
||||
impl ColorFormat {
|
||||
|
@ -98,8 +101,8 @@ impl ColorFormat {
|
|||
/// Ex. Rgba32 has `8bpc`
|
||||
pub fn bpc(&self) -> u8 {
|
||||
match self {
|
||||
ColorFormat::Rgba32 => 8,
|
||||
ColorFormat::Rgb24 => 8,
|
||||
ColorFormat::Rgba8 => 8,
|
||||
ColorFormat::Rgb8 => 8,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,8 +111,8 @@ impl ColorFormat {
|
|||
/// Ex. Rgba32 has `32bpp`
|
||||
pub fn bpp(&self) -> u16 {
|
||||
match self {
|
||||
ColorFormat::Rgba32 => 32,
|
||||
ColorFormat::Rgb24 => 24,
|
||||
ColorFormat::Rgba8 => 32,
|
||||
ColorFormat::Rgb8 => 24,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,8 +121,8 @@ impl ColorFormat {
|
|||
/// Ex. Rgba32 has `4` channels
|
||||
pub fn channels(self) -> u16 {
|
||||
match self {
|
||||
ColorFormat::Rgba32 => 4,
|
||||
ColorFormat::Rgb24 => 3,
|
||||
ColorFormat::Rgba8 => 4,
|
||||
ColorFormat::Rgb8 => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,8 +132,8 @@ impl TryFrom<u8> for ColorFormat {
|
|||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
0 => Self::Rgba32,
|
||||
1 => Self::Rgb24,
|
||||
0 => Self::Rgba8,
|
||||
1 => Self::Rgb8,
|
||||
v => return Err(format!("invalid color format {v}")),
|
||||
})
|
||||
}
|
||||
|
@ -163,9 +166,9 @@ impl TryFrom<u8> for CompressionType {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for CompressionType {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
impl From<CompressionType> for u8 {
|
||||
fn from(val: CompressionType) -> Self {
|
||||
match val {
|
||||
CompressionType::None => 0,
|
||||
CompressionType::Lossless => 1,
|
||||
CompressionType::LossyDct => 2,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! SQP (SQuishy Picture Format) is an image format. It can be used to store
|
||||
//! image data in lossless or lossy compressed form, while remaining relatively
|
||||
//! simple.
|
||||
//! image data in lossless or lossy compressed form.
|
||||
|
||||
mod compression {
|
||||
pub mod dct;
|
||||
|
|
|
@ -77,7 +77,7 @@ pub fn diff_line(width: u32, height: u32, input: &[u8]) -> Vec<u8> {
|
|||
.copied()
|
||||
.collect();
|
||||
|
||||
if y % block_height as u32 != 0 {
|
||||
if y % block_height != 0 {
|
||||
for x in 0..width as usize * 3 {
|
||||
curr_line[x] = curr_line[x].wrapping_sub(prev_line[x]);
|
||||
prev_line[x] = prev_line[x].wrapping_add(curr_line[x]);
|
||||
|
|
|
@ -31,6 +31,9 @@ pub enum Error {
|
|||
impl DangoPicture {
|
||||
/// 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
|
||||
/// ```
|
||||
/// let dpf_lossy = DangoPicture::from_raw(
|
||||
|
@ -47,18 +50,12 @@ impl DangoPicture {
|
|||
height: u32,
|
||||
color_format: ColorFormat,
|
||||
compression_type: CompressionType,
|
||||
compression_level: Option<u8>,
|
||||
quality: 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,
|
||||
};
|
||||
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",
|
||||
|
@ -67,7 +64,10 @@ impl DangoPicture {
|
|||
height,
|
||||
|
||||
compression_type,
|
||||
compression_level,
|
||||
quality: match quality {
|
||||
Some(level) => level.clamp(1, 100),
|
||||
None => 0,
|
||||
},
|
||||
|
||||
color_format,
|
||||
};
|
||||
|
@ -78,6 +78,42 @@ impl DangoPicture {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convenience method over [`DangoPicture::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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
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> {
|
||||
|
@ -97,7 +133,7 @@ impl DangoPicture {
|
|||
&dct_compress(
|
||||
&self.bitmap,
|
||||
DctParameters {
|
||||
quality: self.header.compression_level as u32,
|
||||
quality: self.header.quality as u32,
|
||||
format: self.header.color_format,
|
||||
width: self.header.width as usize,
|
||||
height: self.header.height as usize,
|
||||
|
@ -111,7 +147,7 @@ impl DangoPicture {
|
|||
};
|
||||
|
||||
// Compress the final image data using the basic LZW scheme
|
||||
let (compressed_data, compression_info) = compress(&modified_data)?;
|
||||
let (compressed_data, compression_info) = compress(modified_data)?;
|
||||
|
||||
// Write out compression info
|
||||
count += compression_info.write_into(&mut output).unwrap();
|
||||
|
@ -146,25 +182,10 @@ impl DangoPicture {
|
|||
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,
|
||||
&decode_varint_stream(&pre_bitmap),
|
||||
DctParameters {
|
||||
quality: header.compression_level as u32,
|
||||
quality: header.quality as u32,
|
||||
format: header.color_format,
|
||||
width: header.width as usize,
|
||||
height: header.height as usize,
|
||||
|
@ -176,3 +197,15 @@ impl DangoPicture {
|
|||
Ok(DangoPicture { 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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue