Compare commits

...

11 commits
v0.1.0 ... main

Author SHA1 Message Date
G2
62ffdeba3c
Update SQP logo image 2024-08-27 01:21:07 -05:00
e0d118dbcd Moved test images 2024-08-26 03:41:32 -05:00
b1c2b0881c Added some test images 2024-08-26 03:37:38 -05:00
df249d02dc Merge branch 'main' of https://github.com/Dangoware/dpf 2024-08-26 00:43:55 -05:00
96fcc9c16a Fixed clippy complaints 2024-08-26 00:43:48 -05:00
G2
1b4933a45d
Update README.md 2024-08-24 03:44:52 -05:00
G2
3ce09575dc
Update README.md 2024-08-24 03:04:36 -05:00
G2
ea893e64d4
Update README.md, added some example images 2024-08-24 03:03:47 -05:00
G2
0d7e82e771
Update README.md 2024-08-23 16:13:12 -05:00
d720fa60b0 Bumped version, 0.1.1 2024-08-06 15:45:48 -05:00
0299dbeee3 100% doc coverage 2024-08-06 15:44:26 -05:00
17 changed files with 59 additions and 26 deletions

View file

@ -6,7 +6,7 @@ The squishiest image format!
repository = "https://github.com/Dangoware/sqp" repository = "https://github.com/Dangoware/sqp"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
authors = ["G2 <ke0bhogsg@gmail.com>"] authors = ["G2 <ke0bhogsg@gmail.com>"]
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
categories = ["encoding", "compression", "graphics", "multimedia::images", "multimedia::encoding"] categories = ["encoding", "compression", "graphics", "multimedia::images", "multimedia::encoding"]

View file

@ -1,8 +1,10 @@
<p align="center"> <p align="center">
<img width="400px" src="https://github.com/user-attachments/assets/98f94c1c-ed6f-49a3-b906-c328035d981e"> <img title="SQP" alt="SQP Logo" width="500px" src="https://github.com/user-attachments/assets/cf2fd7f4-f825-4bb4-9427-1b7181be4639">
</p> </p>
# SQP [![Lib.rs Version](https://img.shields.io/crates/v/sqp?style=for-the-badge&logo=rust&label=lib.rs&color=%23a68bfc)](https://lib.rs/crates/sqp)
[![docs.rs](https://img.shields.io/docsrs/cross_usb?style=for-the-badge)](https://docs.rs/sqp/)
**SQP** (**SQ**uishy **P**icture Format) is an image format designed **SQP** (**SQ**uishy **P**icture Format) is an image format designed
for ease of implementation and learning about compression and image formats for ease of implementation and learning about compression and image formats
while attaining a relatively good compression ratio. The general idea is to while attaining a relatively good compression ratio. The general idea is to
@ -19,6 +21,7 @@ speeds.
- Support for various color formats (RGBA, Grayscale, etc.) - Support for various color formats (RGBA, Grayscale, etc.)
- Decent compression ratios, the lossless compression can often beat PNG - Decent compression ratios, the lossless compression can often beat PNG
especially on images with transparency especially on images with transparency
- Lossy alpha compression!
- Relatively simple - Relatively simple
- Squishy! 🍡 - Squishy! 🍡
@ -30,3 +33,14 @@ speeds.
- Decoder-based frame interpolation - Decoder-based frame interpolation
- Floating point color - Floating point color
- Metadata? - Metadata?
## Examples
All examples are at 30% quality in both JPEG and SQP.
| Original | JPEG | SQP |
|----------|--------------|-------------|
| <img width="300px" src="https://github.com/user-attachments/assets/e4f7b620-4cf5-407d-851b-800c52c8a14d"> | <img width="300px" src="https://github.com/user-attachments/assets/84691e8c-2f73-4a1d-b979-0863066b159f"> | <img width="300px" src="https://github.com/user-attachments/assets/ccaa8770-b641-437f-80d1-3658f94c2e21"> |
| <img width="300px" src="https://github.com/user-attachments/assets/f0056e3b-8988-4d0d-88bf-bc73ac5b8be0"> | <img width="300px" src="https://github.com/user-attachments/assets/400c4072-ba69-45d7-8051-46a4e2867c7f"> | <img width="300px" src="https://github.com/user-attachments/assets/c4c84f64-7564-433a-a922-17da472578d9"> |
Images obtained from the following source:
[https://r0k.us/graphics/kodak/](https://r0k.us/graphics/kodak/)

View file

@ -100,6 +100,9 @@ pub fn idct(input: &[f32], width: usize, height: usize) -> Vec<u8> {
/// ///
/// Instead of using this, use the [`quantization_matrix`] function to /// Instead of using this, use the [`quantization_matrix`] function to
/// get a quantization matrix corresponding to the image quality value. /// get a quantization matrix corresponding to the image quality value.
///
/// TODO: In the future, it would be cool to figure out how to generate a
/// quantization matrix of any size.
const BASE_QUANTIZATION_MATRIX: [u16; 64] = [ const BASE_QUANTIZATION_MATRIX: [u16; 64] = [
16, 11, 10, 16, 24, 40, 51, 61, 16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55, 12, 12, 14, 19, 26, 58, 60, 55,

View file

@ -1,7 +1,7 @@
//! Structs and enums which are included in the header of SQP files. //! Structs and enums which are included in the header of SQP files.
use byteorder::{ReadBytesExt, WriteBytesExt, LE}; use byteorder::{ReadBytesExt, WriteBytesExt, LE};
use std::io::{Cursor, Read, Write}; use std::io::{self, Read, Write};
use crate::picture::Error; use crate::picture::Error;
@ -42,21 +42,26 @@ impl Default for Header {
} }
impl Header { impl Header {
pub fn to_bytes(&self) -> [u8; 19] { /// Write the header into a byte stream implementing [`Write`].
let mut buf = Cursor::new(Vec::new()); ///
/// Returns the number of bytes written.
buf.write_all(&self.magic).unwrap(); pub fn write_into<W: Write + WriteBytesExt>(&self, output: &mut W) -> Result<usize, io::Error> {
buf.write_u32::<LE>(self.width).unwrap(); let mut count = 0;
buf.write_u32::<LE>(self.height).unwrap(); output.write_all(&self.magic)?;
output.write_u32::<LE>(self.width)?;
output.write_u32::<LE>(self.height)?;
count += 16;
// Write compression info // Write compression info
buf.write_u8(self.compression_type.into()).unwrap(); output.write_u8(self.compression_type.into())?;
buf.write_u8(self.quality).unwrap(); output.write_u8(self.quality)?;
count += 2;
// Write color format // Write color format
buf.write_u8(self.color_format as u8).unwrap(); output.write_u8(self.color_format as u8)?;
count += 1;
buf.into_inner().try_into().unwrap() Ok(count)
} }
/// Length of the header in bytes. /// Length of the header in bytes.
@ -65,13 +70,14 @@ impl Header {
19 19
} }
/// Create a header from something implementing [`Read`]. /// Create a header from a byte stream implementing [`Read`].
pub fn read_from<T: Read + ReadBytesExt>(input: &mut T) -> Result<Self, Error> { pub fn read_from<R: Read + ReadBytesExt>(input: &mut R) -> Result<Self, Error> {
let mut magic = [0u8; 8]; let mut magic = [0u8; 8];
input.read_exact(&mut magic).unwrap(); input.read_exact(&mut magic).unwrap();
if magic != *b"dangoimg" { if magic != *b"dangoimg" {
return Err(Error::InvalidIdentifier(magic)); let bad_id = String::from_utf8_lossy(&magic).into_owned();
return Err(Error::InvalidIdentifier(bad_id));
} }
Ok(Header { Ok(Header {

View file

@ -18,8 +18,10 @@
//! let width = 2; //! let width = 2;
//! let height = 2; //! let height = 2;
//! let bitmap = vec![ //! let bitmap = vec![
//! 255, 255, 255, 255, 0, 255, 0, 128, //! 0xFF, 0xFF, 0xFF, 0xFF,
//! 255, 255, 255, 255, 0, 255, 0, 128 //! 0x00, 0x80, 0x00, 0x80,
//! 0xFF, 0xFF, 0xFF, 0xFF,
//! 0x00, 0x80, 0x00, 0x80,
//! ]; //! ];
//! //!
//! // Create a 2×2 image in memory. Nothing is compressed or encoded //! // Create a 2×2 image in memory. Nothing is compressed or encoded

View file

@ -58,9 +58,9 @@ pub fn add_rows(width: u32, height: u32, color_format: ColorFormat, data: &[u8])
// Interleave the offset alpha into the RGB bytes // Interleave the offset alpha into the RGB bytes
data[rgb_index..rgb_index + width as usize * (color_format.pbc() - 1)] data[rgb_index..rgb_index + width as usize * (color_format.pbc() - 1)]
.chunks(color_format.pbc() - 1) .chunks(color_format.pbc() - 1)
.zip(data[alpha_index..alpha_index + width as usize].into_iter()) .zip(data[alpha_index..alpha_index + width as usize].iter())
.flat_map(|(a, b)| { .flat_map(|(a, b)| {
a.into_iter().chain(vec![b]) a.iter().chain(vec![b])
}) })
.copied() .copied()
.collect() .collect()

View file

@ -13,14 +13,18 @@ use crate::{
operations::{add_rows, sub_rows}, operations::{add_rows, sub_rows},
}; };
/// An error which occured while manipulating a [`SquishyPicture`].
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[error("incorrect identifier, got {0:02X?}")] /// The file signature was invalid. Must be "dangoimg".
InvalidIdentifier([u8; 8]), #[error("incorrect signature, expected \"dangoimg\" got {0:?}")]
InvalidIdentifier(String),
/// Any I/O operation failed.
#[error("io operation failed: {0}")] #[error("io operation failed: {0}")]
IoError(#[from] io::Error), IoError(#[from] io::Error),
/// There was an error while compressing or decompressing.
#[error("compression operation failed: {0}")] #[error("compression operation failed: {0}")]
CompressionError(#[from] CompressionError), CompressionError(#[from] CompressionError),
} }
@ -146,8 +150,7 @@ impl SquishyPicture {
let mut count = 0; let mut count = 0;
// Write out the header // Write out the header
output.write_all(&self.header.to_bytes()).unwrap(); count += self.header.write_into(&mut output)?;
count += self.header.len();
// Based on the compression type, modify the data accordingly // Based on the compression type, modify the data accordingly
let modified_data = match self.header.compression_type { let modified_data = match self.header.compression_type {
@ -241,6 +244,7 @@ impl SquishyPicture {
} }
} }
/// Decode a stream encoded as varints.
fn decode_varint_stream(stream: &[u8]) -> Vec<i16> { fn decode_varint_stream(stream: &[u8]) -> Vec<i16> {
let mut output = Vec::new(); let mut output = Vec::new();
let mut offset = 0; let mut offset = 0;
@ -253,8 +257,12 @@ fn decode_varint_stream(stream: &[u8]) -> Vec<i16> {
output output
} }
/// Open an SQP from a given path. Convenience method around
/// [`SquishyPicture::decode`]. Returns a [`Result<SquishyPicture>`].
///
/// If you are loading from memory, use [`SquishyPicture::decode`] instead.
pub fn open<P: AsRef<Path>>(path: P) -> Result<SquishyPicture, Error> { pub fn open<P: AsRef<Path>>(path: P) -> Result<SquishyPicture, Error> {
let input = File::open(path)?; let input = File::open(path)?;
Ok(SquishyPicture::decode(input)?) SquishyPicture::decode(input)
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/test_images/kodim03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
src/test_images/kodim23.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
test_images/dpf_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
test_images/kodim03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
test_images/kodim23.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
test_images/sqp_text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

BIN
test_images/test-lossy.sqp Normal file

Binary file not shown.