summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2023-09-03 23:10:03 -0400
committerpommicket <pommicket@gmail.com>2023-09-03 23:10:03 -0400
commitfa4e07f8de55d149639ab4c2a1669545c6a914f7 (patch)
tree4e1246ed7c978d6589d830138bd1f32088326d40
parent0e1bda128a4277296cb1f620cdf12d35482823f4 (diff)
basic test passing
-rw-r--r--Cargo.toml9
-rw-r--r--examples/test.rs4
-rw-r--r--src/lib.rs571
3 files changed, 521 insertions, 63 deletions
diff --git a/Cargo.toml b/Cargo.toml
index ca5205b..28afab4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,11 @@ name = "tiny-png"
version = "0.1.0"
edition = "2021"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
[dependencies]
+
+[dev-dependencies]
+png = "0.17.10"
+
+[features]
+std = []
+default = ["std"]
diff --git a/examples/test.rs b/examples/test.rs
index 1217492..8e2a317 100644
--- a/examples/test.rs
+++ b/examples/test.rs
@@ -1,7 +1,7 @@
fn main() {
let mut data = &include_bytes!("test.png")[..];
let header = tiny_png::read_png_header(&mut data).unwrap();
- let mut buf = [];
- let result = tiny_png::read_png(&mut data, Some(header), &mut buf);
+ let mut buf = vec![0; header.required_bytes()];
+ let result = tiny_png::read_png(&mut data, Some(&header), &mut buf);
println!("{result:?}");
}
diff --git a/src/lib.rs b/src/lib.rs
index a801f9a..7bbd80b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,19 +1,10 @@
-//#![cfg_attr(not(feature = "std"), no_std)]
+#![cfg_attr(not(feature = "std"), no_std)]
use core::cmp::min;
use core::fmt::{self, Debug, Display};
-pub trait IOError: Sized + Display + Debug {
-}
-impl<T: Sized + Display + Debug> IOError for T {
-}
-
-#[cfg(feature = "std")]
-impl IOError for std::io::Error {
- fn unexpected_eof() -> Self {
- std::io::ErrorKind::UnexpectedEof.into()
- }
-}
+pub trait IOError: Sized + Display + Debug {}
+impl<T: Sized + Display + Debug> IOError for T {}
#[derive(Debug)]
#[non_exhaustive]
@@ -23,9 +14,16 @@ pub enum Error<I: IOError> {
BadIhdr,
UnrecognizedChunk([u8; 4]),
BadBlockType,
+ TooLargeForUsize,
TooMuchData,
UnexpectedEob,
BadZlibHeader,
+ BadCode,
+ BadHuffmanCodes,
+ BadBackReference,
+ UnsupportedInterlace,
+ UnsupportedPalette,
+ BadFilter,
}
impl<I: IOError> From<I> for Error<I> {
@@ -40,11 +38,22 @@ impl<I: IOError> Display for Error<I> {
Self::IO(e) => write!(f, "{e}"),
Self::NotPng => write!(f, "not a png file"),
Self::BadIhdr => write!(f, "bad IHDR chunk"),
- Self::UnrecognizedChunk([a, b, c, d]) => write!(f, "unrecognized chunk type: {a} {b} {c} {d}"),
+ Self::UnrecognizedChunk([a, b, c, d]) => {
+ write!(f, "unrecognized chunk type: {a} {b} {c} {d}")
+ }
Self::BadBlockType => write!(f, "bad DEFLATE block type"),
Self::TooMuchData => write!(f, "decompressed data is larger than it should be"),
Self::UnexpectedEob => write!(f, "unexpected end of block"),
Self::BadZlibHeader => write!(f, "bad zlib header"),
+ Self::BadCode => write!(f, "bad code in DEFLATE data"),
+ Self::BadHuffmanCodes => write!(f, "bad Huffman codes"),
+ Self::BadBackReference => {
+ write!(f, "bad DEFLATE back reference (goes past start of stream)")
+ }
+ Self::TooLargeForUsize => write!(f, "decompressed data larger than usize::MAX bytes"),
+ Self::UnsupportedInterlace => write!(f, "unsupported interlacing method"),
+ Self::BadFilter => write!(f, "bad PNG filter"),
+ Self::UnsupportedPalette => write!(f, "unsupported palette"),
}
}
}
@@ -54,7 +63,7 @@ impl<I: IOError> std::error::Error for Error<I> {}
pub trait Read {
type Error: IOError;
- fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
+ fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
fn skip_bytes(&mut self, count: usize) -> Result<(), Self::Error> {
let mut count = count;
const BUF_LEN: usize = 128;
@@ -68,6 +77,21 @@ pub trait Read {
}
}
+#[cfg(feature = "std")]
+impl<T: std::io::Read + std::io::Seek> Read for std::io::BufReader<T> {
+ type Error = std::io::Error;
+
+ fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
+ use std::io::Read;
+ self.read_exact(buf)
+ }
+ fn skip_bytes(&mut self, bytes: usize) -> Result<(), Self::Error> {
+ use std::io::Seek;
+ self.seek(std::io::SeekFrom::Current(bytes as i64))
+ .map(|_| ())
+ }
+}
+
#[derive(Debug)]
pub struct UnexpectedEofError;
@@ -87,30 +111,43 @@ impl<'a> Read for &'a [u8] {
*self = &self[buf.len()..];
Ok(())
}
- // TODO: skip_bytes implementation
+ fn skip_bytes(&mut self, bytes: usize) -> Result<(), Self::Error> {
+ if self.len() < bytes {
+ return Err(UnexpectedEofError);
+ }
+ *self = &self[bytes..];
+ Ok(())
+ }
}
struct BlockReader<'a, R: Read> {
inner: &'a mut R,
- bytes_left: usize
+ bytes_left: usize,
}
-impl<'a, R: Read> BlockReader<'_, R> {
+impl<R: Read> BlockReader<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> Result<(), Error<R::Error>> {
if buf.len() > self.bytes_left {
return Err(Error::UnexpectedEob);
}
- Ok(self.inner.read(buf)?)
+ self.inner.read(buf)?;
+ self.bytes_left -= buf.len();
+ Ok(())
}
-
+
fn read_partial(&mut self, buf: &mut [u8]) -> Result<usize, Error<R::Error>> {
let count = min(self.bytes_left, buf.len());
self.read(&mut buf[..count])?;
Ok(count)
}
+
+ fn read_to_end(&mut self) -> Result<(), Error<R::Error>> {
+ self.inner.skip_bytes(self.bytes_left)?;
+ Ok(())
+ }
}
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
pub struct ImageHeader {
width: u32,
height: u32,
@@ -119,12 +156,13 @@ pub struct ImageHeader {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(u8)]
pub enum BitDepth {
- One,
- Two,
- Four,
- Eight,
- Sixteen,
+ One = 1,
+ Two = 2,
+ Four = 4,
+ Eight = 8,
+ Sixteen = 16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -144,7 +182,7 @@ impl BitDepth {
4 => Self::Four,
8 => Self::Eight,
16 => Self::Sixteen,
- _ => return None
+ _ => return None,
})
}
}
@@ -175,6 +213,24 @@ impl ImageHeader {
pub fn color_type(&self) -> ColorType {
self.color_type
}
+ fn checked_required_bytes(&self) -> Option<usize> {
+ let row_bytes = 1 + usize::try_from(self.width())
+ .ok()?
+ .checked_mul(usize::from(self.bit_depth() as u8))?
+ .checked_add(7)?
+ / 8;
+ row_bytes.checked_mul(usize::try_from(self.height()).ok()?)
+ }
+ pub fn required_bytes(&self) -> usize {
+ self.checked_required_bytes().unwrap()
+ }
+ pub fn bytes_per_scanline(&self) -> usize {
+ (self.width() as usize * usize::from(self.bit_depth() as u8) + 7) / 8
+ }
+ fn data_size(&self) -> usize {
+ let scanline_bytes = self.bytes_per_scanline();
+ scanline_bytes * self.height() as usize
+ }
}
/// number of bits to read in each [`Read::read`] call.
@@ -213,15 +269,36 @@ impl<R: Read> BitReader<'_, R> {
}
Ok((self.bits as u32) & ((1 << count) - 1))
}
-
+
fn read_bits(&mut self, count: u8) -> Result<u32, Error<R::Error>> {
let bits = self.peek_bits(count)?;
self.bits_left -= count;
self.bits >>= count;
Ok(bits)
}
-}
+ /// at least `count` bits MUST have been peeked before calling this!
+ fn skip_peeked_bits(&mut self, count: u8) {
+ debug_assert!(self.bits_left >= count);
+ self.bits_left -= count;
+ self.bits >>= count;
+ }
+
+ fn read_bits_usize(&mut self, count: u8) -> Result<usize, Error<R::Error>> {
+ debug_assert!(u32::from(count) <= usize::BITS);
+ self.read_bits(count).map(|x| x as usize)
+ }
+
+ fn read_bits_u8(&mut self, count: u8) -> Result<u8, Error<R::Error>> {
+ debug_assert!(count <= 8);
+ self.read_bits(count).map(|x| x as u8)
+ }
+
+ fn read_bits_u16(&mut self, count: u8) -> Result<u16, Error<R::Error>> {
+ debug_assert!(count <= 16);
+ self.read_bits(count).map(|x| x as u16)
+ }
+}
pub fn read_png_header<R: Read>(reader: &mut R) -> Result<ImageHeader, Error<R::Error>> {
let mut signature = [0; 8];
@@ -243,28 +320,293 @@ pub fn read_png_header<R: Read>(reader: &mut R) -> Result<ImageHeader, Error<R::
let color_type = ColorType::from_byte(ihdr[17]).ok_or(Error::BadIhdr)?;
let compression = ihdr[18];
let filter = ihdr[19];
+ let interlace = ihdr[20];
if compression != 0 || filter != 0 {
return Err(Error::BadIhdr);
}
-
- Ok(ImageHeader { width, height, bit_depth, color_type })
+ if interlace != 0 {
+ return Err(Error::UnsupportedInterlace);
+ }
+
+ let hdr = ImageHeader {
+ width,
+ height,
+ bit_depth,
+ color_type,
+ };
+ if hdr.checked_required_bytes().is_none() {
+ return Err(Error::TooLargeForUsize);
+ }
+ Ok(hdr)
}
-struct ByteWriter<'a> {
+#[derive(Debug)]
+struct DecompressedDataWriter<'a> {
slice: &'a mut [u8],
+ pos: usize,
}
-fn read_idat<R: Read>(mut reader: BlockReader<'_, R>, writer: &mut ByteWriter) -> Result<(), Error<R::Error>> {
+impl<'a> From<&'a mut [u8]> for DecompressedDataWriter<'a> {
+ fn from(slice: &'a mut [u8]) -> Self {
+ Self { slice, pos: 0 }
+ }
+}
+
+impl<'a> DecompressedDataWriter<'a> {
+ fn write_byte<I: IOError>(&mut self, byte: u8) -> Result<(), Error<I>> {
+ match self.slice.get_mut(self.pos) {
+ None => return Err(Error::TooMuchData),
+ Some(p) => *p = byte,
+ }
+ self.pos += 1;
+ Ok(())
+ }
+
+ fn copy<I: IOError>(&mut self, distance: usize, length: usize) -> Result<(), Error<I>> {
+ if self.pos < distance {
+ return Err(Error::BadBackReference);
+ }
+
+ let mut src = self.pos - distance;
+ let mut dest = self.pos;
+ if length > self.slice.len() - dest {
+ return Err(Error::TooMuchData);
+ }
+ for _ in 0..length {
+ self.slice[dest] = self.slice[src];
+ dest += 1;
+ src += 1;
+ }
+ self.pos = dest;
+ Ok(())
+ }
+}
+
+const HUFFMAN_MAX_CODES: usize = 286;
+const HUFFMAN_MAX_BITS: u8 = 15;
+const HUFFMAN_MAIN_TABLE_BITS: u8 = 11;
+const HUFFMAN_MAIN_TABLE_SIZE: usize = 1 << HUFFMAN_MAIN_TABLE_BITS;
+const HUFFMAN_SUBTABLE_SIZE: usize = 1 << (HUFFMAN_MAX_BITS - HUFFMAN_MAIN_TABLE_BITS);
+#[derive(Debug)]
+struct HuffmanTable {
+ main_table: [u16; HUFFMAN_MAIN_TABLE_SIZE],
+ subtables: [[u16; HUFFMAN_SUBTABLE_SIZE]; HUFFMAN_MAX_CODES],
+ subtables_used: u16,
+}
+
+impl HuffmanTable {
+ fn assign(&mut self, code: u16, length: u8, value: u16) {
+ if length == 0 {
+ return;
+ }
+ // reverse code
+ let code = code.reverse_bits() >> (16 - length);
+
+ if length <= HUFFMAN_MAIN_TABLE_BITS {
+ // just throw it in the main table
+ for i in 0..1u16 << (HUFFMAN_MAIN_TABLE_BITS - length) {
+ self.main_table[usize::from(i << length | code)] = value | u16::from(length) << 10;
+ }
+ } else {
+ // put it in a subtable.
+ let main_table_entry = usize::from(code) & (HUFFMAN_MAIN_TABLE_SIZE - 1);
+ let mut subtable_index = self.main_table[main_table_entry] & 0x1ff;
+ if subtable_index == 0 {
+ subtable_index = self.subtables_used;
+ self.main_table[main_table_entry] = 0x200 | subtable_index;
+ self.subtables_used += 1;
+ }
+ let subtable = &mut self.subtables[usize::from(subtable_index)];
+ let diff = length - HUFFMAN_MAIN_TABLE_BITS;
+ for i in 0..1u16 << (HUFFMAN_MAX_BITS - length) {
+ subtable[usize::from(i << diff | code)] = value | u16::from(length) << 10;
+ }
+ }
+ }
+
+ fn from_code_lengths(code_lengths: &[u8]) -> Self {
+ let mut bl_count = [0; HUFFMAN_MAX_BITS as usize + 1];
+ for l in code_lengths.iter().copied() {
+ bl_count[usize::from(l)] += 1;
+ }
+ bl_count[0] = 0;
+ let mut next_code = [0; HUFFMAN_MAX_BITS as usize + 1];
+ let mut code = 0;
+ for bits in 1..=usize::from(HUFFMAN_MAX_BITS) {
+ code = (code + bl_count[bits - 1]) << 1;
+ next_code[bits] = code;
+ }
+ let mut table = HuffmanTable {
+ main_table: [0; HUFFMAN_MAIN_TABLE_SIZE],
+ subtables: [[0; HUFFMAN_SUBTABLE_SIZE]; HUFFMAN_MAX_CODES],
+ subtables_used: 0,
+ };
+ for (i, l) in code_lengths.iter().copied().enumerate() {
+ table.assign(next_code[usize::from(l)], l, i as u16);
+ next_code[usize::from(l)] += 1;
+ }
+ table
+ }
+
+ fn read_value<R: Read>(&self, reader: &mut BitReader<'_, R>) -> Result<u16, Error<R::Error>> {
+ let code = reader.peek_bits(HUFFMAN_MAX_BITS)? as u16;
+ let entry = self.main_table[usize::from(code) & (HUFFMAN_MAIN_TABLE_SIZE - 1)];
+ let entry = if (entry & 0x200) == 0 {
+ entry
+ } else {
+ self.subtables[usize::from(entry & 0x1ff)][usize::from(code >> HUFFMAN_MAIN_TABLE_BITS)]
+ };
+ let length = (entry >> 10) as u8;
+ if length == 0 {
+ return Err(Error::BadCode);
+ }
+ reader.skip_peeked_bits(length);
+ Ok(entry & 0x1ff)
+ }
+}
+
+fn read_compressed_block<R: Read>(
+ reader: &mut BitReader<'_, R>,
+ writer: &mut DecompressedDataWriter,
+ dynamic: bool,
+) -> Result<(), Error<R::Error>> {
+ let literal_length_table;
+ let distance_table;
+
+ if dynamic {
+ let literal_length_code_lengths_count = reader.read_bits_usize(5)? + 257;
+ let distance_code_lengths_count = reader.read_bits_usize(5)? + 1;
+ let code_length_code_lengths_count = reader.read_bits_usize(4)? + 4;
+ let mut code_length_code_lengths = [0; 19];
+ for i in 0..code_length_code_lengths_count {
+ const ORDER: [u8; 19] = [
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
+ ];
+ code_length_code_lengths[usize::from(ORDER[i])] = reader.read_bits_u8(3)?;
+ }
+ let code_length_table = HuffmanTable::from_code_lengths(&code_length_code_lengths);
+ let mut code_lengths = [0; 286 + 32];
+ let mut i = 0;
+ let total_code_lengths = literal_length_code_lengths_count + distance_code_lengths_count;
+ loop {
+ let op = code_length_table.read_value(reader)? as u8;
+ if op < 16 {
+ code_lengths[i] = op;
+ i += 1;
+ } else if op == 16 {
+ let rep = reader.read_bits_usize(2)? + 3;
+ if i + rep > total_code_lengths {
+ return Err(Error::BadHuffmanCodes);
+ }
+ let l = code_lengths[i];
+ for _ in 0..rep {
+ code_lengths[i] = l;
+ i += 1;
+ }
+ } else if op == 17 {
+ let rep = reader.read_bits_usize(3)? + 3;
+ if i + rep > total_code_lengths {
+ return Err(Error::BadHuffmanCodes);
+ }
+ for _ in 0..rep {
+ code_lengths[i] = 0;
+ i += 1;
+ }
+ } else if op == 18 {
+ let rep = reader.read_bits_usize(7)? + 11;
+ if i + rep > total_code_lengths {
+ return Err(Error::BadHuffmanCodes);
+ }
+ for _ in 0..rep {
+ code_lengths[i] = 0;
+ i += 1;
+ }
+ } else {
+ debug_assert!(false, "should not be reachable");
+ }
+ if i >= total_code_lengths {
+ break;
+ }
+ }
+
+ let literal_length_code_lengths = &code_lengths[0..literal_length_code_lengths_count];
+ let distance_code_lengths =
+ &code_lengths[literal_length_code_lengths_count..total_code_lengths];
+
+ literal_length_table = HuffmanTable::from_code_lengths(literal_length_code_lengths);
+ distance_table = HuffmanTable::from_code_lengths(distance_code_lengths);
+ } else {
+ todo!()
+ }
+ loop {
+ let literal_length = literal_length_table.read_value(reader)?;
+ match literal_length {
+ 0..=255 => {
+ // literal
+ //println!("lit {literal_length}");
+ writer.write_byte(literal_length as u8)?;
+ }
+ 256 => {
+ // end of block
+ //println!("eob");
+ break;
+ }
+ _ => {
+ // length + distance
+ //println!("{literal_length}");
+ let length = match literal_length {
+ 257..=264 => literal_length - 254,
+ 265..=284 => {
+ const BASES: [u16; 20] = [
+ 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131,
+ 163, 195, 227,
+ ];
+ let base = BASES[usize::from(literal_length - 265)];
+ let extra_bits = (literal_length - 261) as u8 / 4;
+ let extra = reader.read_bits_u16(extra_bits)?;
+ base + extra
+ }
+ 285 => 258,
+ _ => return Err(Error::BadCode),
+ };
+
+ let distance_code = distance_table.read_value(reader)?;
+ let distance = match distance_code {
+ 0..=3 => distance_code + 1,
+ 4..=29 => {
+ const BASES: [u16; 26] = [
+ 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769,
+ 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577,
+ ];
+ let base = BASES[usize::from(distance_code - 4)];
+ let extra_bits = (distance_code - 2) as u8 / 2;
+ let extra = reader.read_bits_u16(extra_bits)?;
+ base + extra
+ }
+ _ => return Err(Error::BadCode),
+ };
+ //println!("D {distance} L {length}");
+ writer.copy(usize::from(distance), usize::from(length))?;
+ }
+ }
+ }
+ Ok(())
+}
+
+fn read_idat<R: Read>(
+ mut reader: BlockReader<'_, R>,
+ writer: &mut DecompressedDataWriter,
+) -> Result<(), Error<R::Error>> {
let mut zlib_header = [0; 2];
reader.read(&mut zlib_header)?;
-
+
let mut reader = BitReader::from(reader);
loop {
let bfinal = reader.read_bits(1)?;
let btype = reader.read_bits(2)?;
if btype == 0 {
// uncompressed block
- let len = reader.read_bits(16)? as usize;
+ let len = reader.read_bits_usize(16)?;
reader.read_bits(16)?; // nlen
if writer.slice.len() < len {
return Err(Error::TooMuchData);
@@ -272,57 +614,168 @@ fn read_idat<R: Read>(mut reader: BlockReader<'_, R>, writer: &mut ByteWriter) -
reader.inner.read(&mut writer.slice[..len])?;
} else if btype == 1 || btype == 2 {
// compressed block
- todo!("{btype}")
+ read_compressed_block(&mut reader, writer, btype == 2)?;
} else {
// 0b11 is not a valid block type
return Err(Error::BadBlockType);
}
-
if bfinal != 0 {
break;
}
}
-
- // skip checksum
- let mut trash = [0; 4];
- reader.inner.read(&mut trash[..4 - usize::from(reader.bits_left) / 8])?;
-
+ reader.inner.read_to_end()?;
+
Ok(())
}
-pub fn read_png<R: Read>(
+fn apply_filters<I: IOError>(header: &ImageHeader, data: &mut [u8]) -> Result<(), Error<I>> {
+ let mut s = 0;
+ let mut d = 0;
+
+ let scanline_bytes = header.bytes_per_scanline();
+ for scanline in 0..header.height() {
+ let filter = data[s];
+ s += 1;
+
+ for i in 0..scanline_bytes {
+ let x = i32::from(data[s]);
+ let a = i32::from(if i == 0 { 0 } else { data[d - 1] });
+ let b = i32::from(if scanline == 0 {
+ 0
+ } else {
+ data[d - scanline_bytes]
+ });
+ let c = i32::from(if scanline == 0 || i == 0 {
+ 0
+ } else {
+ data[d - 1 - scanline_bytes]
+ });
+
+ fn paeth(a: i32, b: i32, c: i32) -> i32 {
+ let p = a + b - c;
+ let pa = (p - a).abs();
+ let pb = (p - b).abs();
+ let pc = (p - c).abs();
+ if pa <= pb && pa <= pc {
+ a
+ } else if pb <= pc {
+ b
+ } else {
+ c
+ }
+ }
+ data[d] = (match filter {
+ 0 => x,
+ 1 => x + a,
+ 2 => x + b,
+ 3 => x + (a + b) / 2,
+ 4 => x + paeth(a, b, c),
+ _ => return Err(Error::BadFilter),
+ }) as u8;
+ s += 1;
+ d += 1;
+ }
+ }
+ Ok(())
+}
+
+pub fn read_png<'a, R: Read>(
reader: &mut R,
- header: Option<ImageHeader>,
- buf: &mut [u8],
-) -> Result<(), Error<R::Error>> {
- let _header = match header {
+ header: Option<&ImageHeader>,
+ buf: &'a mut [u8],
+) -> Result<&'a [u8], Error<R::Error>> {
+ let header = match header {
None => read_png_header(reader)?,
- Some(h) => h,
+ Some(h) => *h,
};
- let mut writer = ByteWriter { slice: buf };
+ let mut writer = DecompressedDataWriter::from(buf);
loop {
let mut chunk_header = [0; 8];
reader.read(&mut chunk_header[..])?;
- let chunk_len = u32::from_be_bytes([chunk_header[0], chunk_header[1], chunk_header[2], chunk_header[3]]) as usize;
- let chunk_type = [chunk_header[4], chunk_header[5], chunk_header[6], chunk_header[7]];
+ let chunk_len = u32::from_be_bytes([
+ chunk_header[0],
+ chunk_header[1],
+ chunk_header[2],
+ chunk_header[3],
+ ]) as usize;
+ let chunk_type = [
+ chunk_header[4],
+ chunk_header[5],
+ chunk_header[6],
+ chunk_header[7],
+ ];
if &chunk_type == b"IEND" {
break;
} else if &chunk_type == b"IDAT" {
- read_idat(BlockReader {
- inner: reader,
- bytes_left: chunk_len
- }, &mut writer)?;
+ read_idat(
+ BlockReader {
+ inner: reader,
+ bytes_left: chunk_len + 4,
+ },
+ &mut writer,
+ )?;
} else if &chunk_type == b"PLTE" {
- todo!();
+ return Err(Error::UnsupportedPalette);
} else if chunk_type[0].is_ascii_lowercase() {
// non-essential chunk
+ reader.skip_bytes(chunk_len + 4)?;
} else {
return Err(Error::UnrecognizedChunk(chunk_type));
}
-
- reader.skip_bytes(chunk_len + 4)?;
}
-
- Ok(())
+ let buf = writer.slice;
+ apply_filters(&header, buf)?;
+
+ Ok(&buf[..header.data_size()])
}
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fs::File;
+
+ fn test_file(path: &str) {
+ let decoder = png::Decoder::new(File::open(path).expect("file not found"));
+ let mut reader = decoder.read_info().unwrap();
+
+ let mut png_buf = vec![0; reader.output_buffer_size()];
+ let png_header = reader.next_frame(&mut png_buf).unwrap();
+ let png_bytes = &png_buf[..png_header.buffer_size()];
+
+ let mut r = std::io::BufReader::new(File::open(path).expect("file not found"));
+ let tiny_header = read_png_header(&mut r).unwrap();
+ let mut tiny_buf = vec![0; tiny_header.required_bytes()];
+ let tiny_bytes = read_png(&mut r, Some(&tiny_header), &mut tiny_buf).unwrap();
+
+ assert_eq!(png_bytes.len(), tiny_bytes.len());
+ assert_eq!(png_bytes, tiny_bytes);
+ }
+
+ fn test_bytes(mut bytes: &[u8]) {
+ let decoder = png::Decoder::new(bytes);
+ let mut reader = decoder.read_info().unwrap();
+
+ let mut png_buf = vec![0; reader.output_buffer_size()];
+ let png_header = reader.next_frame(&mut png_buf).unwrap();
+ let png_bytes = &png_buf[..png_header.buffer_size()];
+
+ let tiny_header = read_png_header(&mut bytes).unwrap();
+ let mut tiny_buf = vec![0; tiny_header.required_bytes()];
+ let tiny_bytes = read_png(&mut bytes, Some(&tiny_header), &mut tiny_buf).unwrap();
+
+ assert_eq!(png_bytes.len(), tiny_bytes.len());
+ assert_eq!(png_bytes, tiny_bytes);
+ }
+
+ macro_rules! test_both {
+ ($file:literal) => {
+ test_file($file);
+ test_bytes(include_bytes!(concat!("../", $file)));
+ };
+ }
+
+ #[test]
+ fn test1() {
+ test_both!("examples/test.png");
+ }
+}