Cleaned up code and applied clippy suggestions

This commit is contained in:
G2-Games 2024-07-28 22:20:18 -05:00
parent a74e5b2e95
commit 5502578bb2
7 changed files with 101 additions and 77 deletions

View file

@ -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.

View file

@ -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::*;

View file

@ -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 {

View file

@ -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,

View file

@ -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;

View file

@ -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]);

View file

@ -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
}