Compare commits
10 Commits
ef977d7990
...
0b2408bda1
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b2408bda1 | |||
| a6f18e5ea9 | |||
| 7ba2e7760b | |||
| e31aa5decb | |||
| 5ae4aa5823 | |||
| ef5d962621 | |||
| 9cffd665ca | |||
| 431c808b72 | |||
| f4eef2f863 | |||
| d5e7b0c818 |
@@ -5,4 +5,5 @@ edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
[dev-dependencies]
|
||||
png = "0.17.14"
|
||||
|
||||
@@ -35,6 +35,7 @@ fn try_read_u32<I: Iterator<Item=u8>>(bytes: &mut I) -> Result<u32, DecodeError>
|
||||
}
|
||||
|
||||
mod codec_utils {
|
||||
use super::DecodeError;
|
||||
use super::PixelRGBA;
|
||||
pub(crate) fn hash(pixel: PixelRGBA) -> u8 {
|
||||
pixel
|
||||
@@ -45,10 +46,24 @@ mod codec_utils {
|
||||
.wrapping_add(pixel.a.wrapping_mul(11))
|
||||
% 64
|
||||
}
|
||||
|
||||
pub (crate) fn read_u8_rewrap<I: Iterator<Item = u8>>(bytes: &mut I) -> Result<u8, DecodeError> {
|
||||
if let Some(byte) = bytes.next() {
|
||||
return Ok(byte);
|
||||
} else {
|
||||
return Err(DecodeError::EarlyIteratorExhaustion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Decoder<I: Iterator<Item = u8>> {
|
||||
pub struct Decoder<I: Iterator<Item = u8>> {
|
||||
// Image metadata
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub channels: u8,
|
||||
pub colorspace: u8,
|
||||
|
||||
// QOI codec state information
|
||||
back_buffer: [PixelRGBA; 64],
|
||||
prev_pixel: PixelRGBA,
|
||||
@@ -61,18 +76,18 @@ impl<I> Decoder<I>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
fn new(bytes: I) -> Self {
|
||||
Self {
|
||||
pub fn try_new(mut bytes: I) -> Result<Self, DecodeError> {
|
||||
let _ = try_read_magic(&mut bytes)?;
|
||||
Ok(Self {
|
||||
width: try_read_u32(&mut bytes)?,
|
||||
height: try_read_u32(&mut bytes)?,
|
||||
channels: codec_utils::read_u8_rewrap(&mut bytes)?,
|
||||
colorspace: codec_utils::read_u8_rewrap(&mut bytes)?,
|
||||
back_buffer: [PixelRGBA::zero(); 64],
|
||||
prev_pixel: PixelRGBA {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
},
|
||||
prev_pixel: PixelRGBA { r:0, g:0, b:0, a: 255},
|
||||
bytes,
|
||||
run_len: 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +193,10 @@ mod test {
|
||||
// op codes to demonstrate the indexing operations.
|
||||
fn new_with_backbuffer(bytes: I, back_buffer: [PixelRGBA; 64]) -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
channels: 4,
|
||||
colorspace: 0,
|
||||
back_buffer,
|
||||
prev_pixel: PixelRGBA {
|
||||
r: 0,
|
||||
@@ -194,6 +213,10 @@ mod test {
|
||||
// function, but for testing a run of pixels.
|
||||
fn new_with_previous_pixel(bytes: I, prev_pixel: PixelRGBA) -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
channels: 4,
|
||||
colorspace: 0,
|
||||
back_buffer: [PixelRGBA::zero(); 64],
|
||||
prev_pixel,
|
||||
bytes,
|
||||
@@ -201,6 +224,23 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
// The decoder includes image metadata, now, and construction attempts
|
||||
// to extract it from the input iterator. For the decoder tests, this
|
||||
// needs to be skipped. Thus, we get *another* magic constructor
|
||||
// available only to the test module.
|
||||
fn new_with_no_metadata(bytes: I) -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
channels: 4,
|
||||
colorspace: 0,
|
||||
back_buffer: [PixelRGBA::zero(); 64],
|
||||
prev_pixel: PixelRGBA { r: 0, g: 0, b: 0, a: 255 },
|
||||
bytes,
|
||||
run_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_prev_pixel(&self) -> &PixelRGBA {
|
||||
&self.prev_pixel
|
||||
}
|
||||
@@ -252,7 +292,7 @@ mod test {
|
||||
},
|
||||
];
|
||||
|
||||
let decoder = Decoder::new(compressed.into_iter());
|
||||
let decoder = Decoder::new_with_no_metadata(compressed.into_iter());
|
||||
let result = decoder.collect::<Vec<PixelRGBA>>();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
@@ -298,7 +338,7 @@ mod test {
|
||||
},
|
||||
];
|
||||
|
||||
let decoder = Decoder::new(compressed.into_iter());
|
||||
let decoder = Decoder::new_with_no_metadata(compressed.into_iter());
|
||||
let result: Vec<PixelRGBA> = decoder.collect();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
@@ -364,7 +404,7 @@ mod test {
|
||||
PixelRGBA::new(0, 0, 0, 255),
|
||||
];
|
||||
|
||||
let decoder = Decoder::new(compressed.into_iter());
|
||||
let decoder = Decoder::new_with_no_metadata(compressed.into_iter());
|
||||
let result: Vec<PixelRGBA> = decoder.collect();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
@@ -430,7 +470,7 @@ mod test {
|
||||
PixelRGBA::new(37, 19, 28, 255),
|
||||
];
|
||||
|
||||
let decoder = Decoder::new(compressed.into_iter());
|
||||
let decoder = Decoder::new_with_no_metadata(compressed.into_iter());
|
||||
let result: Vec<PixelRGBA> = decoder.collect();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
@@ -523,7 +563,7 @@ mod test {
|
||||
PixelRGBA::new(0xFF, 0xFF, 0xFF, 0xFF),
|
||||
];
|
||||
|
||||
let mut decoder = Decoder::new(compressed.into_iter());
|
||||
let mut decoder = Decoder::new_with_no_metadata(compressed.into_iter());
|
||||
|
||||
let mut result = Vec::<PixelRGBA>::new();
|
||||
loop {
|
||||
@@ -592,7 +632,7 @@ mod test {
|
||||
39, // run x3
|
||||
38, // final RGBA
|
||||
];
|
||||
let mut decoder = Decoder::new(compressed.into_iter());
|
||||
let mut decoder = Decoder::new_with_no_metadata(compressed.into_iter());
|
||||
let mut iters = 0;
|
||||
loop {
|
||||
if let Some(pixel) = decoder.next() {
|
||||
|
||||
25
src/main.rs
25
src/main.rs
@@ -1,25 +0,0 @@
|
||||
|
||||
use qoicodec::DecodeError;
|
||||
use qoicodec::try_read_magic;
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
fn main() {
|
||||
let file = File::open("qoi_test_images/dice.qoi").unwrap();
|
||||
let mut bytes = file.bytes()
|
||||
.map(|maybe_bytes|
|
||||
{
|
||||
match maybe_bytes {
|
||||
Ok(byte) => byte,
|
||||
Err(oops) => panic!("Oops, the file failed to load: {}", oops)
|
||||
}
|
||||
});
|
||||
if let Err(e) = try_read_magic(&mut bytes) {
|
||||
match e {
|
||||
DecodeError::Magic => panic!("QOI Magic bytes are bad!"),
|
||||
DecodeError::EarlyIteratorExhaustion => panic!("File iterator exhausted earlier than expected."),
|
||||
_ => panic!("Unhandled error reading magic: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
todo!("The rest of the main function");
|
||||
}
|
||||
108
tests/codec.rs
Normal file
108
tests/codec.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
use qoicodec::DecodeError;
|
||||
use qoicodec::Decoder;
|
||||
use qoicodec::PixelRGBA;
|
||||
|
||||
use std::io::BufWriter;
|
||||
use std::path::Path;
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TestError {
|
||||
SetupFailure, // an error during test initialization (missing file, no write permission for output)
|
||||
CodecFailure(DecodeError),
|
||||
}
|
||||
|
||||
impl From<DecodeError> for TestError {
|
||||
fn from(err: DecodeError) -> Self {
|
||||
Self::CodecFailure(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// There are test images provided by the QOI spec author: https://qoiformat.org/qoi_test_images.zip
|
||||
/// This function looks for them in `./qoi_test_images/` and turns them all
|
||||
/// into PNGs. Manual inspection is required to evaluate the test result.
|
||||
fn decode_qoi_samples() -> Result<(), TestError> {
|
||||
let file = File::open("qoi_test_images/dice.qoi").map_err(|_io_err| TestError::SetupFailure)?;
|
||||
|
||||
// TODO: feed the Result<> forward, don't let a panic path be given to an unaware caller.
|
||||
let mut bytes = file.bytes()
|
||||
.map(|maybe_bytes|
|
||||
{
|
||||
match maybe_bytes {
|
||||
Ok(byte) => byte,
|
||||
Err(oops) => panic!("Oops, the file failed to load: {}", oops)
|
||||
}
|
||||
});
|
||||
let decoder = Decoder::try_new(bytes)?;
|
||||
|
||||
let test_out_path = Path::new("test_output.png");
|
||||
let file = File::create(test_out_path).unwrap();
|
||||
let ref mut w = BufWriter::new(file);
|
||||
|
||||
let mut encoder = png::Encoder::new(w, decoder.width, decoder.height);
|
||||
encoder.set_color(match decoder.channels {
|
||||
3 => png::ColorType::Rgb,
|
||||
4 => png::ColorType::Rgba,
|
||||
_ => panic!("Bad colorspace in qoicodec::Decoder!")
|
||||
});
|
||||
encoder.set_depth(png::BitDepth::Eight);
|
||||
encoder.set_source_gamma(png::ScaledFloat::from_scaled(45455));
|
||||
encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2));
|
||||
let source_chromaticities = png::SourceChromaticities::new(
|
||||
(0.31270, 0.32900),
|
||||
(0.64000, 0.33000),
|
||||
(0.30000, 0.60000),
|
||||
(0.15000, 0.06000),
|
||||
);
|
||||
encoder.set_source_chromaticities(source_chromaticities);
|
||||
let mut writer = encoder.write_header().unwrap();
|
||||
|
||||
let data: Vec<u8> = PixelIterExpander::try_new(decoder).unwrap().collect();
|
||||
let result = writer.write_image_data(&data);
|
||||
if let Err(res) = result {
|
||||
panic!("{}", res);
|
||||
}
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
struct PixelIterExpander <I: Iterator<Item = qoicodec::PixelRGBA>> {
|
||||
wrapped_iterator: I,
|
||||
current_pixel: qoicodec::PixelRGBA,
|
||||
color_element_idx: u8,
|
||||
}
|
||||
|
||||
impl <I: std::iter::Iterator<Item = PixelRGBA>> PixelIterExpander <I> {
|
||||
fn try_new(mut pixels: I) -> Option<Self> {
|
||||
let px = pixels.next()?;
|
||||
|
||||
Some(Self {
|
||||
wrapped_iterator: pixels,
|
||||
current_pixel: px,
|
||||
color_element_idx: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl <I: Iterator<Item=PixelRGBA>> Iterator for PixelIterExpander<I> {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// at index 4, reset counter and get next pixel
|
||||
if self.color_element_idx == 4 {
|
||||
self.color_element_idx = 0;
|
||||
self.current_pixel = self.wrapped_iterator.next()?;
|
||||
}
|
||||
|
||||
// increment pixel value, match over index and return that byte
|
||||
self.color_element_idx += 1;
|
||||
match self.color_element_idx {
|
||||
1 => Some(self.current_pixel.r),
|
||||
2 => Some(self.current_pixel.g),
|
||||
3 => Some(self.current_pixel.b),
|
||||
4 => Some(self.current_pixel.a),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user