extern crate crc32fast;
use std::convert::{From, TryInto};
use std::default::Default;
use std::error;
use std::fmt;
use std::io;
use std::{borrow::Cow, cmp::min};
use crc32fast::Hasher as Crc32;
use super::zlib::ZlibStream;
use crate::chunk::{self, ChunkType, IDAT, IEND, IHDR};
use crate::common::{
AnimationControl, BitDepth, BlendOp, ColorType, DisposeOp, FrameControl, Info, ParameterError,
PixelDimensions, ScaledFloat, SourceChromaticities, Unit,
};
use crate::text_metadata::{ITXtChunk, TEXtChunk, TextDecodingError, ZTXtChunk};
use crate::traits::ReadBytesExt;
use crate::Limits;
pub const CHUNCK_BUFFER_SIZE: usize = 32 * 1024;
const CHECKSUM_DISABLED: bool = cfg!(fuzzing);
#[derive(Debug)]
enum U32ValueKind {
Signature1stU32,
Signature2ndU32,
Length,
Type { length: u32 },
Crc(ChunkType),
ApngSequenceNumber,
}
#[derive(Debug)]
enum State {
U32 {
kind: U32ValueKind,
bytes: [u8; 4],
accumulated_count: usize,
},
ReadChunkData(ChunkType),
ParseChunkData(ChunkType),
ImageData(ChunkType),
}
impl State {
fn new_u32(kind: U32ValueKind) -> Self {
Self::U32 {
kind,
bytes: [0; 4],
accumulated_count: 0,
}
}
}
#[derive(Debug)]
pub enum Decoded {
Nothing,
Header(u32, u32, BitDepth, ColorType, bool),
ChunkBegin(u32, ChunkType),
ChunkComplete(u32, ChunkType),
PixelDimensions(PixelDimensions),
AnimationControl(AnimationControl),
FrameControl(FrameControl),
ImageData,
ImageDataFlushed,
PartialChunk(ChunkType),
ImageEnd,
}
#[derive(Debug)]
pub enum DecodingError {
IoError(io::Error),
Format(FormatError),
Parameter(ParameterError),
LimitsExceeded,
}
#[derive(Debug)]
pub struct FormatError {
inner: FormatErrorInner,
}
#[derive(Debug)]
pub(crate) enum FormatErrorInner {
CrcMismatch {
crc_val: u32,
crc_sum: u32,
chunk: ChunkType,
},
InvalidSignature,
UnexpectedEof,
UnexpectedEndOfChunk,
MissingIhdr,
MissingFctl,
MissingImageData,
ChunkBeforeIhdr {
kind: ChunkType,
},
AfterIdat {
kind: ChunkType,
},
AfterPlte {
kind: ChunkType,
},
OutsidePlteIdat {
kind: ChunkType,
},
DuplicateChunk {
kind: ChunkType,
},
ApngOrder {
present: u32,
expected: u32,
},
ShortPalette {
expected: usize,
len: usize,
},
PaletteRequired,
InvalidColorBitDepth {
color_type: ColorType,
bit_depth: BitDepth,
},
ColorWithBadTrns(ColorType),
InvalidDimensions,
InvalidBitDepth(u8),
InvalidColorType(u8),
InvalidDisposeOp(u8),
InvalidBlendOp(u8),
InvalidUnit(u8),
InvalidSrgbRenderingIntent(u8),
UnknownCompressionMethod(u8),
UnknownFilterMethod(u8),
UnknownInterlaceMethod(u8),
BadSubFrameBounds {},
CorruptFlateStream {
err: fdeflate::DecompressionError,
},
NoMoreImageData,
BadTextEncoding(TextDecodingError),
FdatShorterThanFourBytes,
}
impl error::Error for DecodingError {
fn cause(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
DecodingError::IoError(err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for DecodingError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::DecodingError::*;
match self {
IoError(err) => write!(fmt, "{}", err),
Parameter(desc) => write!(fmt, "{}", &desc),
Format(desc) => write!(fmt, "{}", desc),
LimitsExceeded => write!(fmt, "limits are exceeded"),
}
}
}
impl fmt::Display for FormatError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use FormatErrorInner::*;
match &self.inner {
CrcMismatch {
crc_val,
crc_sum,
chunk,
..
} => write!(
fmt,
"CRC error: expected 0x{:x} have 0x{:x} while decoding {:?} chunk.",
crc_val, crc_sum, chunk
),
MissingIhdr => write!(fmt, "IHDR chunk missing"),
MissingFctl => write!(fmt, "fcTL chunk missing before fdAT chunk."),
MissingImageData => write!(fmt, "IDAT or fDAT chunk is missing."),
ChunkBeforeIhdr { kind } => write!(fmt, "{:?} chunk appeared before IHDR chunk", kind),
AfterIdat { kind } => write!(fmt, "Chunk {:?} is invalid after IDAT chunk.", kind),
AfterPlte { kind } => write!(fmt, "Chunk {:?} is invalid after PLTE chunk.", kind),
OutsidePlteIdat { kind } => write!(
fmt,
"Chunk {:?} must appear between PLTE and IDAT chunks.",
kind
),
DuplicateChunk { kind } => write!(fmt, "Chunk {:?} must appear at most once.", kind),
ApngOrder { present, expected } => write!(
fmt,
"Sequence is not in order, expected #{} got #{}.",
expected, present,
),
ShortPalette { expected, len } => write!(
fmt,
"Not enough palette entries, expect {} got {}.",
expected, len
),
PaletteRequired => write!(fmt, "Missing palette of indexed image."),
InvalidDimensions => write!(fmt, "Invalid image dimensions"),
InvalidColorBitDepth {
color_type,
bit_depth,
} => write!(
fmt,
"Invalid color/depth combination in header: {:?}/{:?}",
color_type, bit_depth,
),
ColorWithBadTrns(color_type) => write!(
fmt,
"Transparency chunk found for color type {:?}.",
color_type
),
InvalidBitDepth(nr) => write!(fmt, "Invalid dispose operation {}.", nr),
InvalidColorType(nr) => write!(fmt, "Invalid color type {}.", nr),
InvalidDisposeOp(nr) => write!(fmt, "Invalid dispose op {}.", nr),
InvalidBlendOp(nr) => write!(fmt, "Invalid blend op {}.", nr),
InvalidUnit(nr) => write!(fmt, "Invalid physical pixel size unit {}.", nr),
InvalidSrgbRenderingIntent(nr) => write!(fmt, "Invalid sRGB rendering intent {}.", nr),
UnknownCompressionMethod(nr) => write!(fmt, "Unknown compression method {}.", nr),
UnknownFilterMethod(nr) => write!(fmt, "Unknown filter method {}.", nr),
UnknownInterlaceMethod(nr) => write!(fmt, "Unknown interlace method {}.", nr),
BadSubFrameBounds {} => write!(fmt, "Sub frame is out-of-bounds."),
InvalidSignature => write!(fmt, "Invalid PNG signature."),
UnexpectedEof => write!(fmt, "Unexpected end of data before image end."),
UnexpectedEndOfChunk => write!(fmt, "Unexpected end of data within a chunk."),
NoMoreImageData => write!(fmt, "IDAT or fDAT chunk is has not enough data for image."),
CorruptFlateStream { err } => {
write!(fmt, "Corrupt deflate stream. ")?;
write!(fmt, "{:?}", err)
}
BadTextEncoding(tde) => {
match tde {
TextDecodingError::Unrepresentable => {
write!(fmt, "Unrepresentable data in tEXt chunk.")
}
TextDecodingError::InvalidKeywordSize => {
write!(fmt, "Keyword empty or longer than 79 bytes.")
}
TextDecodingError::MissingNullSeparator => {
write!(fmt, "No null separator in tEXt chunk.")
}
TextDecodingError::InflationError => {
write!(fmt, "Invalid compressed text data.")
}
TextDecodingError::OutOfDecompressionSpace => {
write!(fmt, "Out of decompression space. Try with a larger limit.")
}
TextDecodingError::InvalidCompressionMethod => {
write!(fmt, "Using an unrecognized byte as compression method.")
}
TextDecodingError::InvalidCompressionFlag => {
write!(fmt, "Using a flag that is not 0 or 255 as a compression flag for iTXt chunk.")
}
TextDecodingError::MissingCompressionFlag => {
write!(fmt, "No compression flag in the iTXt chunk.")
}
}
}
FdatShorterThanFourBytes => write!(fmt, "fdAT chunk shorter than 4 bytes"),
}
}
}
impl From<io::Error> for DecodingError {
fn from(err: io::Error) -> DecodingError {
DecodingError::IoError(err)
}
}
impl From<FormatError> for DecodingError {
fn from(err: FormatError) -> DecodingError {
DecodingError::Format(err)
}
}
impl From<FormatErrorInner> for FormatError {
fn from(inner: FormatErrorInner) -> Self {
FormatError { inner }
}
}
impl From<DecodingError> for io::Error {
fn from(err: DecodingError) -> io::Error {
match err {
DecodingError::IoError(err) => err,
err => io::Error::new(io::ErrorKind::Other, err.to_string()),
}
}
}
impl From<TextDecodingError> for DecodingError {
fn from(tbe: TextDecodingError) -> Self {
DecodingError::Format(FormatError {
inner: FormatErrorInner::BadTextEncoding(tbe),
})
}
}
#[derive(Clone)]
pub struct DecodeOptions {
ignore_adler32: bool,
ignore_crc: bool,
ignore_text_chunk: bool,
skip_ancillary_crc_failures: bool,
}
impl Default for DecodeOptions {
fn default() -> Self {
Self {
ignore_adler32: true,
ignore_crc: false,
ignore_text_chunk: false,
skip_ancillary_crc_failures: true,
}
}
}
impl DecodeOptions {
pub fn set_ignore_adler32(&mut self, ignore_adler32: bool) {
self.ignore_adler32 = ignore_adler32;
}
pub fn set_ignore_crc(&mut self, ignore_crc: bool) {
self.ignore_crc = ignore_crc;
}
pub fn set_ignore_checksums(&mut self, ignore_checksums: bool) {
self.ignore_adler32 = ignore_checksums;
self.ignore_crc = ignore_checksums;
}
pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) {
self.ignore_text_chunk = ignore_text_chunk;
}
pub fn set_skip_ancillary_crc_failures(&mut self, skip_ancillary_crc_failures: bool) {
self.skip_ancillary_crc_failures = skip_ancillary_crc_failures;
}
}
pub struct StreamingDecoder {
state: Option<State>,
current_chunk: ChunkState,
inflater: ZlibStream,
pub(crate) info: Option<Info<'static>>,
current_seq_no: Option<u32>,
have_idat: bool,
decode_options: DecodeOptions,
pub(crate) limits: Limits,
}
struct ChunkState {
type_: ChunkType,
crc: Crc32,
remaining: u32,
raw_bytes: Vec<u8>,
}
impl StreamingDecoder {
pub fn new() -> StreamingDecoder {
StreamingDecoder::new_with_options(DecodeOptions::default())
}
pub fn new_with_options(decode_options: DecodeOptions) -> StreamingDecoder {
let mut inflater = ZlibStream::new();
inflater.set_ignore_adler32(decode_options.ignore_adler32);
StreamingDecoder {
state: Some(State::new_u32(U32ValueKind::Signature1stU32)),
current_chunk: ChunkState::default(),
inflater,
info: None,
current_seq_no: None,
have_idat: false,
decode_options,
limits: Limits { bytes: usize::MAX },
}
}
pub fn reset(&mut self) {
self.state = Some(State::new_u32(U32ValueKind::Signature1stU32));
self.current_chunk.crc = Crc32::new();
self.current_chunk.remaining = 0;
self.current_chunk.raw_bytes.clear();
self.inflater.reset();
self.info = None;
self.current_seq_no = None;
self.have_idat = false;
}
pub fn info(&self) -> Option<&Info<'static>> {
self.info.as_ref()
}
pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) {
self.decode_options.set_ignore_text_chunk(ignore_text_chunk);
}
pub fn ignore_adler32(&self) -> bool {
self.inflater.ignore_adler32()
}
pub fn set_ignore_adler32(&mut self, ignore_adler32: bool) -> bool {
self.inflater.set_ignore_adler32(ignore_adler32)
}
pub fn set_ignore_crc(&mut self, ignore_crc: bool) {
self.decode_options.set_ignore_crc(ignore_crc)
}
pub fn set_skip_ancillary_crc_failures(&mut self, skip_ancillary_crc_failures: bool) {
self.decode_options
.set_skip_ancillary_crc_failures(skip_ancillary_crc_failures)
}
pub fn update(
&mut self,
mut buf: &[u8],
image_data: &mut Vec<u8>,
) -> Result<(usize, Decoded), DecodingError> {
let len = buf.len();
while !buf.is_empty() && self.state.is_some() {
match self.next_state(buf, image_data) {
Ok((bytes, Decoded::Nothing)) => buf = &buf[bytes..],
Ok((bytes, result)) => {
buf = &buf[bytes..];
return Ok((len - buf.len(), result));
}
Err(err) => return Err(err),
}
}
Ok((len - buf.len(), Decoded::Nothing))
}
fn next_state(
&mut self,
buf: &[u8],
image_data: &mut Vec<u8>,
) -> Result<(usize, Decoded), DecodingError> {
use self::State::*;
let state = self.state.take().unwrap();
match state {
U32 {
kind,
mut bytes,
mut accumulated_count,
} => {
debug_assert!(accumulated_count <= 4);
if accumulated_count == 0 && buf.len() >= 4 {
const CONSUMED_BYTES: usize = 4;
self.parse_u32(kind, &buf[0..4], image_data)
.map(|decoded| (CONSUMED_BYTES, decoded))
} else {
let remaining_count = 4 - accumulated_count;
let consumed_bytes = {
let available_count = min(remaining_count, buf.len());
bytes[accumulated_count..accumulated_count + available_count]
.copy_from_slice(&buf[0..available_count]);
accumulated_count += available_count;
available_count
};
if accumulated_count < 4 {
self.state = Some(U32 {
kind,
bytes,
accumulated_count,
});
Ok((consumed_bytes, Decoded::Nothing))
} else {
debug_assert_eq!(accumulated_count, 4);
self.parse_u32(kind, &bytes, image_data)
.map(|decoded| (consumed_bytes, decoded))
}
}
}
ParseChunkData(type_str) => {
debug_assert!(type_str != IDAT && type_str != chunk::fdAT);
if self.current_chunk.remaining == 0 {
Ok((0, self.parse_chunk(type_str)?))
} else {
self.reserve_current_chunk()?;
self.state = Some(ReadChunkData(type_str));
Ok((0, Decoded::PartialChunk(type_str)))
}
}
ReadChunkData(type_str) => {
debug_assert!(type_str != IDAT && type_str != chunk::fdAT);
if self.current_chunk.remaining == 0 {
self.state = Some(State::new_u32(U32ValueKind::Crc(type_str)));
Ok((0, Decoded::Nothing))
} else {
let ChunkState {
crc,
remaining,
raw_bytes,
type_: _,
} = &mut self.current_chunk;
let buf_avail = raw_bytes.capacity() - raw_bytes.len();
let bytes_avail = min(buf.len(), buf_avail);
let n = min(*remaining, bytes_avail as u32);
if buf_avail == 0 {
self.state = Some(ParseChunkData(type_str));
Ok((0, Decoded::Nothing))
} else {
let buf = &buf[..n as usize];
if !self.decode_options.ignore_crc {
crc.update(buf);
}
raw_bytes.extend_from_slice(buf);
*remaining -= n;
if *remaining == 0 {
self.state = Some(ParseChunkData(type_str));
} else {
self.state = Some(ReadChunkData(type_str));
}
Ok((n as usize, Decoded::Nothing))
}
}
}
ImageData(type_str) => {
debug_assert!(type_str == IDAT || type_str == chunk::fdAT);
let len = std::cmp::min(buf.len(), self.current_chunk.remaining as usize);
let buf = &buf[..len];
let consumed = self.inflater.decompress(buf, image_data)?;
self.current_chunk.crc.update(&buf[..consumed]);
self.current_chunk.remaining -= consumed as u32;
if self.current_chunk.remaining == 0 {
self.state = Some(State::new_u32(U32ValueKind::Crc(type_str)));
} else {
self.state = Some(ImageData(type_str));
}
Ok((consumed, Decoded::ImageData))
}
}
}
fn parse_u32(
&mut self,
kind: U32ValueKind,
u32_be_bytes: &[u8],
image_data: &mut Vec<u8>,
) -> Result<Decoded, DecodingError> {
debug_assert_eq!(u32_be_bytes.len(), 4);
let bytes = u32_be_bytes.try_into().unwrap();
let val = u32::from_be_bytes(bytes);
match kind {
U32ValueKind::Signature1stU32 => {
if bytes == [137, 80, 78, 71] {
self.state = Some(State::new_u32(U32ValueKind::Signature2ndU32));
Ok(Decoded::Nothing)
} else {
Err(DecodingError::Format(
FormatErrorInner::InvalidSignature.into(),
))
}
}
U32ValueKind::Signature2ndU32 => {
if bytes == [13, 10, 26, 10] {
self.state = Some(State::new_u32(U32ValueKind::Length));
Ok(Decoded::Nothing)
} else {
Err(DecodingError::Format(
FormatErrorInner::InvalidSignature.into(),
))
}
}
U32ValueKind::Length => {
self.state = Some(State::new_u32(U32ValueKind::Type { length: val }));
Ok(Decoded::Nothing)
}
U32ValueKind::Type { length } => {
let type_str = ChunkType(bytes);
if self.info.is_none() && type_str != IHDR {
return Err(DecodingError::Format(
FormatErrorInner::ChunkBeforeIhdr { kind: type_str }.into(),
));
}
if type_str != self.current_chunk.type_
&& (self.current_chunk.type_ == IDAT || self.current_chunk.type_ == chunk::fdAT)
{
self.current_chunk.type_ = type_str;
self.inflater.finish_compressed_chunks(image_data)?;
self.inflater.reset();
self.state = Some(State::U32 {
kind,
bytes,
accumulated_count: 4,
});
return Ok(Decoded::ImageDataFlushed);
}
self.current_chunk.type_ = type_str;
if !self.decode_options.ignore_crc {
self.current_chunk.crc.reset();
self.current_chunk.crc.update(&type_str.0);
}
self.current_chunk.remaining = length;
self.current_chunk.raw_bytes.clear();
self.state = match type_str {
chunk::fdAT => {
if length < 4 {
return Err(DecodingError::Format(
FormatErrorInner::FdatShorterThanFourBytes.into(),
));
}
Some(State::new_u32(U32ValueKind::ApngSequenceNumber))
}
IDAT => {
self.have_idat = true;
Some(State::ImageData(type_str))
}
_ => Some(State::ReadChunkData(type_str)),
};
Ok(Decoded::ChunkBegin(length, type_str))
}
U32ValueKind::Crc(type_str) => {
let sum = if self.decode_options.ignore_crc {
val
} else {
self.current_chunk.crc.clone().finalize()
};
if val == sum || CHECKSUM_DISABLED {
self.state = Some(State::new_u32(U32ValueKind::Length));
if type_str == IEND {
Ok(Decoded::ImageEnd)
} else {
Ok(Decoded::ChunkComplete(val, type_str))
}
} else if self.decode_options.skip_ancillary_crc_failures
&& !chunk::is_critical(type_str)
{
self.state = Some(State::new_u32(U32ValueKind::Length));
Ok(Decoded::Nothing)
} else {
Err(DecodingError::Format(
FormatErrorInner::CrcMismatch {
crc_val: val,
crc_sum: sum,
chunk: type_str,
}
.into(),
))
}
}
U32ValueKind::ApngSequenceNumber => {
debug_assert_eq!(self.current_chunk.type_, chunk::fdAT);
let next_seq_no = val;
debug_assert!(self.current_chunk.remaining >= 4);
self.current_chunk.remaining -= 4;
if let Some(seq_no) = self.current_seq_no {
if next_seq_no != seq_no + 1 {
return Err(DecodingError::Format(
FormatErrorInner::ApngOrder {
present: next_seq_no,
expected: seq_no + 1,
}
.into(),
));
}
self.current_seq_no = Some(next_seq_no);
} else {
return Err(DecodingError::Format(FormatErrorInner::MissingFctl.into()));
}
if !self.decode_options.ignore_crc {
let data = next_seq_no.to_be_bytes();
self.current_chunk.crc.update(&data);
}
self.state = Some(State::ImageData(chunk::fdAT));
Ok(Decoded::PartialChunk(chunk::fdAT))
}
}
}
fn reserve_current_chunk(&mut self) -> Result<(), DecodingError> {
let max = self.limits.bytes;
let buffer = &mut self.current_chunk.raw_bytes;
let reserve_size = max.saturating_sub(buffer.capacity()).min(buffer.len());
self.limits.reserve_bytes(reserve_size)?;
buffer.reserve_exact(reserve_size);
if buffer.capacity() == buffer.len() {
Err(DecodingError::LimitsExceeded)
} else {
Ok(())
}
}
fn parse_chunk(&mut self, type_str: ChunkType) -> Result<Decoded, DecodingError> {
self.state = Some(State::new_u32(U32ValueKind::Crc(type_str)));
let parse_result = match type_str {
IHDR => self.parse_ihdr(),
chunk::PLTE => self.parse_plte(),
chunk::tRNS => self.parse_trns(),
chunk::pHYs => self.parse_phys(),
chunk::gAMA => self.parse_gama(),
chunk::acTL => self.parse_actl(),
chunk::fcTL => self.parse_fctl(),
chunk::cHRM => self.parse_chrm(),
chunk::sRGB => self.parse_srgb(),
chunk::iCCP => self.parse_iccp(),
chunk::tEXt if !self.decode_options.ignore_text_chunk => self.parse_text(),
chunk::zTXt if !self.decode_options.ignore_text_chunk => self.parse_ztxt(),
chunk::iTXt if !self.decode_options.ignore_text_chunk => self.parse_itxt(),
_ => Ok(Decoded::PartialChunk(type_str)),
};
if parse_result.is_err() {
self.state = None;
}
parse_result
}
fn parse_fctl(&mut self) -> Result<Decoded, DecodingError> {
let mut buf = &self.current_chunk.raw_bytes[..];
let next_seq_no = buf.read_be()?;
self.current_seq_no = Some(if let Some(seq_no) = self.current_seq_no {
if next_seq_no != seq_no + 1 {
return Err(DecodingError::Format(
FormatErrorInner::ApngOrder {
expected: seq_no + 1,
present: next_seq_no,
}
.into(),
));
}
next_seq_no
} else {
if next_seq_no != 0 {
return Err(DecodingError::Format(
FormatErrorInner::ApngOrder {
expected: 0,
present: next_seq_no,
}
.into(),
));
}
0
});
self.inflater.reset();
let fc = FrameControl {
sequence_number: next_seq_no,
width: buf.read_be()?,
height: buf.read_be()?,
x_offset: buf.read_be()?,
y_offset: buf.read_be()?,
delay_num: buf.read_be()?,
delay_den: buf.read_be()?,
dispose_op: {
let dispose_op = buf.read_be()?;
match DisposeOp::from_u8(dispose_op) {
Some(dispose_op) => dispose_op,
None => {
return Err(DecodingError::Format(
FormatErrorInner::InvalidDisposeOp(dispose_op).into(),
))
}
}
},
blend_op: {
let blend_op = buf.read_be()?;
match BlendOp::from_u8(blend_op) {
Some(blend_op) => blend_op,
None => {
return Err(DecodingError::Format(
FormatErrorInner::InvalidBlendOp(blend_op).into(),
))
}
}
},
};
self.info.as_ref().unwrap().validate(&fc)?;
self.info.as_mut().unwrap().frame_control = Some(fc);
Ok(Decoded::FrameControl(fc))
}
fn parse_actl(&mut self) -> Result<Decoded, DecodingError> {
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::acTL }.into(),
))
} else {
let mut buf = &self.current_chunk.raw_bytes[..];
let actl = AnimationControl {
num_frames: buf.read_be()?,
num_plays: buf.read_be()?,
};
self.info.as_mut().unwrap().animation_control = Some(actl);
Ok(Decoded::AnimationControl(actl))
}
}
fn parse_plte(&mut self) -> Result<Decoded, DecodingError> {
let info = self.info.as_mut().unwrap();
if info.palette.is_some() {
Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::PLTE }.into(),
))
} else {
self.limits
.reserve_bytes(self.current_chunk.raw_bytes.len())?;
info.palette = Some(Cow::Owned(self.current_chunk.raw_bytes.clone()));
Ok(Decoded::Nothing)
}
}
fn parse_trns(&mut self) -> Result<Decoded, DecodingError> {
let info = self.info.as_mut().unwrap();
if info.trns.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::PLTE }.into(),
));
}
let (color_type, bit_depth) = { (info.color_type, info.bit_depth as u8) };
self.limits
.reserve_bytes(self.current_chunk.raw_bytes.len())?;
let mut vec = self.current_chunk.raw_bytes.clone();
let len = vec.len();
match color_type {
ColorType::Grayscale => {
if len < 2 {
return Err(DecodingError::Format(
FormatErrorInner::ShortPalette { expected: 2, len }.into(),
));
}
if bit_depth < 16 {
vec[0] = vec[1];
vec.truncate(1);
}
info.trns = Some(Cow::Owned(vec));
Ok(Decoded::Nothing)
}
ColorType::Rgb => {
if len < 6 {
return Err(DecodingError::Format(
FormatErrorInner::ShortPalette { expected: 6, len }.into(),
));
}
if bit_depth < 16 {
vec[0] = vec[1];
vec[1] = vec[3];
vec[2] = vec[5];
vec.truncate(3);
}
info.trns = Some(Cow::Owned(vec));
Ok(Decoded::Nothing)
}
ColorType::Indexed => {
if info.palette.is_none() {
return Err(DecodingError::Format(
FormatErrorInner::AfterPlte { kind: chunk::tRNS }.into(),
));
} else if self.have_idat {
return Err(DecodingError::Format(
FormatErrorInner::OutsidePlteIdat { kind: chunk::tRNS }.into(),
));
}
info.trns = Some(Cow::Owned(vec));
Ok(Decoded::Nothing)
}
c => Err(DecodingError::Format(
FormatErrorInner::ColorWithBadTrns(c).into(),
)),
}
}
fn parse_phys(&mut self) -> Result<Decoded, DecodingError> {
let info = self.info.as_mut().unwrap();
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::pHYs }.into(),
))
} else if info.pixel_dims.is_some() {
Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::pHYs }.into(),
))
} else {
let mut buf = &self.current_chunk.raw_bytes[..];
let xppu = buf.read_be()?;
let yppu = buf.read_be()?;
let unit = buf.read_be()?;
let unit = match Unit::from_u8(unit) {
Some(unit) => unit,
None => {
return Err(DecodingError::Format(
FormatErrorInner::InvalidUnit(unit).into(),
))
}
};
let pixel_dims = PixelDimensions { xppu, yppu, unit };
info.pixel_dims = Some(pixel_dims);
Ok(Decoded::PixelDimensions(pixel_dims))
}
}
fn parse_chrm(&mut self) -> Result<Decoded, DecodingError> {
let info = self.info.as_mut().unwrap();
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::cHRM }.into(),
))
} else if info.chrm_chunk.is_some() {
Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::cHRM }.into(),
))
} else {
let mut buf = &self.current_chunk.raw_bytes[..];
let white_x: u32 = buf.read_be()?;
let white_y: u32 = buf.read_be()?;
let red_x: u32 = buf.read_be()?;
let red_y: u32 = buf.read_be()?;
let green_x: u32 = buf.read_be()?;
let green_y: u32 = buf.read_be()?;
let blue_x: u32 = buf.read_be()?;
let blue_y: u32 = buf.read_be()?;
let source_chromaticities = SourceChromaticities {
white: (
ScaledFloat::from_scaled(white_x),
ScaledFloat::from_scaled(white_y),
),
red: (
ScaledFloat::from_scaled(red_x),
ScaledFloat::from_scaled(red_y),
),
green: (
ScaledFloat::from_scaled(green_x),
ScaledFloat::from_scaled(green_y),
),
blue: (
ScaledFloat::from_scaled(blue_x),
ScaledFloat::from_scaled(blue_y),
),
};
info.chrm_chunk = Some(source_chromaticities);
if info.srgb.is_none() {
info.source_chromaticities = Some(source_chromaticities);
}
Ok(Decoded::Nothing)
}
}
fn parse_gama(&mut self) -> Result<Decoded, DecodingError> {
let info = self.info.as_mut().unwrap();
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::gAMA }.into(),
))
} else if info.gama_chunk.is_some() {
Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::gAMA }.into(),
))
} else {
let mut buf = &self.current_chunk.raw_bytes[..];
let source_gamma: u32 = buf.read_be()?;
let source_gamma = ScaledFloat::from_scaled(source_gamma);
info.gama_chunk = Some(source_gamma);
if info.srgb.is_none() {
info.source_gamma = Some(source_gamma);
}
Ok(Decoded::Nothing)
}
}
fn parse_srgb(&mut self) -> Result<Decoded, DecodingError> {
let info = self.info.as_mut().unwrap();
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::acTL }.into(),
))
} else if info.srgb.is_some() {
Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::sRGB }.into(),
))
} else {
let mut buf = &self.current_chunk.raw_bytes[..];
let raw: u8 = buf.read_be()?; let rendering_intent = crate::SrgbRenderingIntent::from_raw(raw).ok_or_else(|| {
FormatError::from(FormatErrorInner::InvalidSrgbRenderingIntent(raw))
})?;
info.srgb = Some(rendering_intent);
info.source_gamma = Some(crate::srgb::substitute_gamma());
info.source_chromaticities = Some(crate::srgb::substitute_chromaticities());
Ok(Decoded::Nothing)
}
}
fn parse_iccp(&mut self) -> Result<Decoded, DecodingError> {
let info = self.info.as_mut().unwrap();
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::iCCP }.into(),
))
} else if info.icc_profile.is_some() {
Ok(Decoded::Nothing)
} else {
let mut buf = &self.current_chunk.raw_bytes[..];
let _: u8 = buf.read_be()?;
for _ in 1..80 {
let raw: u8 = buf.read_be()?;
if raw == 0 {
break;
}
}
match buf.read_be()? {
0u8 => (),
n => {
return Err(DecodingError::Format(
FormatErrorInner::UnknownCompressionMethod(n).into(),
))
}
}
match fdeflate::decompress_to_vec_bounded(buf, self.limits.bytes) {
Ok(profile) => {
self.limits.reserve_bytes(profile.len())?;
info.icc_profile = Some(Cow::Owned(profile));
}
Err(fdeflate::BoundedDecompressionError::DecompressionError { inner: err }) => {
return Err(DecodingError::Format(
FormatErrorInner::CorruptFlateStream { err }.into(),
))
}
Err(fdeflate::BoundedDecompressionError::OutputTooLarge { .. }) => {
return Err(DecodingError::LimitsExceeded);
}
}
Ok(Decoded::Nothing)
}
}
fn parse_ihdr(&mut self) -> Result<Decoded, DecodingError> {
if self.info.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: IHDR }.into(),
));
}
let mut buf = &self.current_chunk.raw_bytes[..];
let width = buf.read_be()?;
let height = buf.read_be()?;
if width == 0 || height == 0 {
return Err(DecodingError::Format(
FormatErrorInner::InvalidDimensions.into(),
));
}
let bit_depth = buf.read_be()?;
let bit_depth = match BitDepth::from_u8(bit_depth) {
Some(bits) => bits,
None => {
return Err(DecodingError::Format(
FormatErrorInner::InvalidBitDepth(bit_depth).into(),
))
}
};
let color_type = buf.read_be()?;
let color_type = match ColorType::from_u8(color_type) {
Some(color_type) => {
if color_type.is_combination_invalid(bit_depth) {
return Err(DecodingError::Format(
FormatErrorInner::InvalidColorBitDepth {
color_type,
bit_depth,
}
.into(),
));
} else {
color_type
}
}
None => {
return Err(DecodingError::Format(
FormatErrorInner::InvalidColorType(color_type).into(),
))
}
};
match buf.read_be()? {
0u8 => (),
n => {
return Err(DecodingError::Format(
FormatErrorInner::UnknownCompressionMethod(n).into(),
))
}
}
match buf.read_be()? {
0u8 => (),
n => {
return Err(DecodingError::Format(
FormatErrorInner::UnknownFilterMethod(n).into(),
))
}
}
let interlaced = match buf.read_be()? {
0u8 => false,
1 => true,
n => {
return Err(DecodingError::Format(
FormatErrorInner::UnknownInterlaceMethod(n).into(),
))
}
};
if let Some(mut raw_row_len) = color_type.checked_raw_row_length(bit_depth, width) {
if interlaced {
raw_row_len = raw_row_len.saturating_mul(2);
}
self.inflater
.set_max_total_output((height as usize).saturating_mul(raw_row_len));
}
self.info = Some(Info {
width,
height,
bit_depth,
color_type,
interlaced,
..Default::default()
});
Ok(Decoded::Header(
width, height, bit_depth, color_type, interlaced,
))
}
fn split_keyword(buf: &[u8]) -> Result<(&[u8], &[u8]), DecodingError> {
let null_byte_index = buf
.iter()
.position(|&b| b == 0)
.ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))?;
if null_byte_index == 0 || null_byte_index > 79 {
return Err(DecodingError::from(TextDecodingError::InvalidKeywordSize));
}
Ok((&buf[..null_byte_index], &buf[null_byte_index + 1..]))
}
fn parse_text(&mut self) -> Result<Decoded, DecodingError> {
let buf = &self.current_chunk.raw_bytes[..];
self.limits.reserve_bytes(buf.len())?;
let (keyword_slice, value_slice) = Self::split_keyword(buf)?;
self.info
.as_mut()
.unwrap()
.uncompressed_latin1_text
.push(TEXtChunk::decode(keyword_slice, value_slice).map_err(DecodingError::from)?);
Ok(Decoded::Nothing)
}
fn parse_ztxt(&mut self) -> Result<Decoded, DecodingError> {
let buf = &self.current_chunk.raw_bytes[..];
self.limits.reserve_bytes(buf.len())?;
let (keyword_slice, value_slice) = Self::split_keyword(buf)?;
let compression_method = *value_slice
.first()
.ok_or_else(|| DecodingError::from(TextDecodingError::InvalidCompressionMethod))?;
let text_slice = &value_slice[1..];
self.info.as_mut().unwrap().compressed_latin1_text.push(
ZTXtChunk::decode(keyword_slice, compression_method, text_slice)
.map_err(DecodingError::from)?,
);
Ok(Decoded::Nothing)
}
fn parse_itxt(&mut self) -> Result<Decoded, DecodingError> {
let buf = &self.current_chunk.raw_bytes[..];
self.limits.reserve_bytes(buf.len())?;
let (keyword_slice, value_slice) = Self::split_keyword(buf)?;
let compression_flag = *value_slice
.first()
.ok_or_else(|| DecodingError::from(TextDecodingError::MissingCompressionFlag))?;
let compression_method = *value_slice
.get(1)
.ok_or_else(|| DecodingError::from(TextDecodingError::InvalidCompressionMethod))?;
let second_null_byte_index = value_slice[2..]
.iter()
.position(|&b| b == 0)
.ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))?
+ 2;
let language_tag_slice = &value_slice[2..second_null_byte_index];
let third_null_byte_index = value_slice[second_null_byte_index + 1..]
.iter()
.position(|&b| b == 0)
.ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))?
+ (second_null_byte_index + 1);
let translated_keyword_slice =
&value_slice[second_null_byte_index + 1..third_null_byte_index];
let text_slice = &value_slice[third_null_byte_index + 1..];
self.info.as_mut().unwrap().utf8_text.push(
ITXtChunk::decode(
keyword_slice,
compression_flag,
compression_method,
language_tag_slice,
translated_keyword_slice,
text_slice,
)
.map_err(DecodingError::from)?,
);
Ok(Decoded::Nothing)
}
}
impl Info<'_> {
fn validate(&self, fc: &FrameControl) -> Result<(), DecodingError> {
if fc.width == 0 || fc.height == 0 {
return Err(DecodingError::Format(
FormatErrorInner::InvalidDimensions.into(),
));
}
let in_x_bounds = Some(fc.width) <= self.width.checked_sub(fc.x_offset);
let in_y_bounds = Some(fc.height) <= self.height.checked_sub(fc.y_offset);
if !in_x_bounds || !in_y_bounds {
return Err(DecodingError::Format(
FormatErrorInner::BadSubFrameBounds {}.into(),
));
}
Ok(())
}
}
impl Default for StreamingDecoder {
fn default() -> Self {
Self::new()
}
}
impl Default for ChunkState {
fn default() -> Self {
ChunkState {
type_: ChunkType([0; 4]),
crc: Crc32::new(),
remaining: 0,
raw_bytes: Vec::with_capacity(CHUNCK_BUFFER_SIZE),
}
}
}
#[cfg(test)]
mod tests {
use super::ScaledFloat;
use super::SourceChromaticities;
use crate::test_utils::*;
use crate::{Decoder, DecodingError};
use byteorder::WriteBytesExt;
use std::fs::File;
use std::io::Write;
#[test]
fn image_gamma() -> Result<(), ()> {
fn trial(path: &str, expected: Option<ScaledFloat>) {
let decoder = crate::Decoder::new(File::open(path).unwrap());
let reader = decoder.read_info().unwrap();
let actual: Option<ScaledFloat> = reader.info().source_gamma;
assert!(actual == expected);
}
trial("tests/pngsuite/f00n0g08.png", None);
trial("tests/pngsuite/f00n2c08.png", None);
trial("tests/pngsuite/f01n0g08.png", None);
trial("tests/pngsuite/f01n2c08.png", None);
trial("tests/pngsuite/f02n0g08.png", None);
trial("tests/pngsuite/f02n2c08.png", None);
trial("tests/pngsuite/f03n0g08.png", None);
trial("tests/pngsuite/f03n2c08.png", None);
trial("tests/pngsuite/f04n0g08.png", None);
trial("tests/pngsuite/f04n2c08.png", None);
trial("tests/pngsuite/f99n0g04.png", None);
trial("tests/pngsuite/tm3n3p02.png", None);
trial("tests/pngsuite/g03n0g16.png", Some(ScaledFloat::new(0.35)));
trial("tests/pngsuite/g03n2c08.png", Some(ScaledFloat::new(0.35)));
trial("tests/pngsuite/g03n3p04.png", Some(ScaledFloat::new(0.35)));
trial("tests/pngsuite/g04n0g16.png", Some(ScaledFloat::new(0.45)));
trial("tests/pngsuite/g04n2c08.png", Some(ScaledFloat::new(0.45)));
trial("tests/pngsuite/g04n3p04.png", Some(ScaledFloat::new(0.45)));
trial("tests/pngsuite/g05n0g16.png", Some(ScaledFloat::new(0.55)));
trial("tests/pngsuite/g05n2c08.png", Some(ScaledFloat::new(0.55)));
trial("tests/pngsuite/g05n3p04.png", Some(ScaledFloat::new(0.55)));
trial("tests/pngsuite/g07n0g16.png", Some(ScaledFloat::new(0.7)));
trial("tests/pngsuite/g07n2c08.png", Some(ScaledFloat::new(0.7)));
trial("tests/pngsuite/g07n3p04.png", Some(ScaledFloat::new(0.7)));
trial("tests/pngsuite/g10n0g16.png", Some(ScaledFloat::new(1.0)));
trial("tests/pngsuite/g10n2c08.png", Some(ScaledFloat::new(1.0)));
trial("tests/pngsuite/g10n3p04.png", Some(ScaledFloat::new(1.0)));
trial("tests/pngsuite/g25n0g16.png", Some(ScaledFloat::new(2.5)));
trial("tests/pngsuite/g25n2c08.png", Some(ScaledFloat::new(2.5)));
trial("tests/pngsuite/g25n3p04.png", Some(ScaledFloat::new(2.5)));
Ok(())
}
#[test]
fn image_source_chromaticities() -> Result<(), ()> {
fn trial(path: &str, expected: Option<SourceChromaticities>) {
let decoder = crate::Decoder::new(File::open(path).unwrap());
let reader = decoder.read_info().unwrap();
let actual: Option<SourceChromaticities> = reader.info().source_chromaticities;
assert!(actual == expected);
}
trial(
"tests/pngsuite/ccwn2c08.png",
Some(SourceChromaticities::new(
(0.3127, 0.3290),
(0.64, 0.33),
(0.30, 0.60),
(0.15, 0.06),
)),
);
trial(
"tests/pngsuite/ccwn3p08.png",
Some(SourceChromaticities::new(
(0.3127, 0.3290),
(0.64, 0.33),
(0.30, 0.60),
(0.15, 0.06),
)),
);
trial("tests/pngsuite/basi0g01.png", None);
trial("tests/pngsuite/basi0g02.png", None);
trial("tests/pngsuite/basi0g04.png", None);
trial("tests/pngsuite/basi0g08.png", None);
trial("tests/pngsuite/basi0g16.png", None);
trial("tests/pngsuite/basi2c08.png", None);
trial("tests/pngsuite/basi2c16.png", None);
trial("tests/pngsuite/basi3p01.png", None);
trial("tests/pngsuite/basi3p02.png", None);
trial("tests/pngsuite/basi3p04.png", None);
trial("tests/pngsuite/basi3p08.png", None);
trial("tests/pngsuite/basi4a08.png", None);
trial("tests/pngsuite/basi4a16.png", None);
trial("tests/pngsuite/basi6a08.png", None);
trial("tests/pngsuite/basi6a16.png", None);
trial("tests/pngsuite/basn0g01.png", None);
trial("tests/pngsuite/basn0g02.png", None);
trial("tests/pngsuite/basn0g04.png", None);
trial("tests/pngsuite/basn0g08.png", None);
trial("tests/pngsuite/basn0g16.png", None);
trial("tests/pngsuite/basn2c08.png", None);
trial("tests/pngsuite/basn2c16.png", None);
trial("tests/pngsuite/basn3p01.png", None);
trial("tests/pngsuite/basn3p02.png", None);
trial("tests/pngsuite/basn3p04.png", None);
trial("tests/pngsuite/basn3p08.png", None);
trial("tests/pngsuite/basn4a08.png", None);
trial("tests/pngsuite/basn4a16.png", None);
trial("tests/pngsuite/basn6a08.png", None);
trial("tests/pngsuite/basn6a16.png", None);
trial("tests/pngsuite/bgai4a08.png", None);
trial("tests/pngsuite/bgai4a16.png", None);
trial("tests/pngsuite/bgan6a08.png", None);
trial("tests/pngsuite/bgan6a16.png", None);
trial("tests/pngsuite/bgbn4a08.png", None);
trial("tests/pngsuite/bggn4a16.png", None);
trial("tests/pngsuite/bgwn6a08.png", None);
trial("tests/pngsuite/bgyn6a16.png", None);
trial("tests/pngsuite/cdfn2c08.png", None);
trial("tests/pngsuite/cdhn2c08.png", None);
trial("tests/pngsuite/cdsn2c08.png", None);
trial("tests/pngsuite/cdun2c08.png", None);
trial("tests/pngsuite/ch1n3p04.png", None);
trial("tests/pngsuite/ch2n3p08.png", None);
trial("tests/pngsuite/cm0n0g04.png", None);
trial("tests/pngsuite/cm7n0g04.png", None);
trial("tests/pngsuite/cm9n0g04.png", None);
trial("tests/pngsuite/cs3n2c16.png", None);
trial("tests/pngsuite/cs3n3p08.png", None);
trial("tests/pngsuite/cs5n2c08.png", None);
trial("tests/pngsuite/cs5n3p08.png", None);
trial("tests/pngsuite/cs8n2c08.png", None);
trial("tests/pngsuite/cs8n3p08.png", None);
trial("tests/pngsuite/ct0n0g04.png", None);
trial("tests/pngsuite/ct1n0g04.png", None);
trial("tests/pngsuite/cten0g04.png", None);
trial("tests/pngsuite/ctfn0g04.png", None);
trial("tests/pngsuite/ctgn0g04.png", None);
trial("tests/pngsuite/cthn0g04.png", None);
trial("tests/pngsuite/ctjn0g04.png", None);
trial("tests/pngsuite/ctzn0g04.png", None);
trial("tests/pngsuite/f00n0g08.png", None);
trial("tests/pngsuite/f00n2c08.png", None);
trial("tests/pngsuite/f01n0g08.png", None);
trial("tests/pngsuite/f01n2c08.png", None);
trial("tests/pngsuite/f02n0g08.png", None);
trial("tests/pngsuite/f02n2c08.png", None);
trial("tests/pngsuite/f03n0g08.png", None);
trial("tests/pngsuite/f03n2c08.png", None);
trial("tests/pngsuite/f04n0g08.png", None);
trial("tests/pngsuite/f04n2c08.png", None);
trial("tests/pngsuite/f99n0g04.png", None);
trial("tests/pngsuite/g03n0g16.png", None);
trial("tests/pngsuite/g03n2c08.png", None);
trial("tests/pngsuite/g03n3p04.png", None);
trial("tests/pngsuite/g04n0g16.png", None);
trial("tests/pngsuite/g04n2c08.png", None);
trial("tests/pngsuite/g04n3p04.png", None);
trial("tests/pngsuite/g05n0g16.png", None);
trial("tests/pngsuite/g05n2c08.png", None);
trial("tests/pngsuite/g05n3p04.png", None);
trial("tests/pngsuite/g07n0g16.png", None);
trial("tests/pngsuite/g07n2c08.png", None);
trial("tests/pngsuite/g07n3p04.png", None);
trial("tests/pngsuite/g10n0g16.png", None);
trial("tests/pngsuite/g10n2c08.png", None);
trial("tests/pngsuite/g10n3p04.png", None);
trial("tests/pngsuite/g25n0g16.png", None);
trial("tests/pngsuite/g25n2c08.png", None);
trial("tests/pngsuite/g25n3p04.png", None);
trial("tests/pngsuite/oi1n0g16.png", None);
trial("tests/pngsuite/oi1n2c16.png", None);
trial("tests/pngsuite/oi2n0g16.png", None);
trial("tests/pngsuite/oi2n2c16.png", None);
trial("tests/pngsuite/oi4n0g16.png", None);
trial("tests/pngsuite/oi4n2c16.png", None);
trial("tests/pngsuite/oi9n0g16.png", None);
trial("tests/pngsuite/oi9n2c16.png", None);
trial("tests/pngsuite/PngSuite.png", None);
trial("tests/pngsuite/pp0n2c16.png", None);
trial("tests/pngsuite/pp0n6a08.png", None);
trial("tests/pngsuite/ps1n0g08.png", None);
trial("tests/pngsuite/ps1n2c16.png", None);
trial("tests/pngsuite/ps2n0g08.png", None);
trial("tests/pngsuite/ps2n2c16.png", None);
trial("tests/pngsuite/s01i3p01.png", None);
trial("tests/pngsuite/s01n3p01.png", None);
trial("tests/pngsuite/s02i3p01.png", None);
trial("tests/pngsuite/s02n3p01.png", None);
trial("tests/pngsuite/s03i3p01.png", None);
trial("tests/pngsuite/s03n3p01.png", None);
trial("tests/pngsuite/s04i3p01.png", None);
trial("tests/pngsuite/s04n3p01.png", None);
trial("tests/pngsuite/s05i3p02.png", None);
trial("tests/pngsuite/s05n3p02.png", None);
trial("tests/pngsuite/s06i3p02.png", None);
trial("tests/pngsuite/s06n3p02.png", None);
trial("tests/pngsuite/s07i3p02.png", None);
trial("tests/pngsuite/s07n3p02.png", None);
trial("tests/pngsuite/s08i3p02.png", None);
trial("tests/pngsuite/s08n3p02.png", None);
trial("tests/pngsuite/s09i3p02.png", None);
trial("tests/pngsuite/s09n3p02.png", None);
trial("tests/pngsuite/s32i3p04.png", None);
trial("tests/pngsuite/s32n3p04.png", None);
trial("tests/pngsuite/s33i3p04.png", None);
trial("tests/pngsuite/s33n3p04.png", None);
trial("tests/pngsuite/s34i3p04.png", None);
trial("tests/pngsuite/s34n3p04.png", None);
trial("tests/pngsuite/s35i3p04.png", None);
trial("tests/pngsuite/s35n3p04.png", None);
trial("tests/pngsuite/s36i3p04.png", None);
trial("tests/pngsuite/s36n3p04.png", None);
trial("tests/pngsuite/s37i3p04.png", None);
trial("tests/pngsuite/s37n3p04.png", None);
trial("tests/pngsuite/s38i3p04.png", None);
trial("tests/pngsuite/s38n3p04.png", None);
trial("tests/pngsuite/s39i3p04.png", None);
trial("tests/pngsuite/s39n3p04.png", None);
trial("tests/pngsuite/s40i3p04.png", None);
trial("tests/pngsuite/s40n3p04.png", None);
trial("tests/pngsuite/tbbn0g04.png", None);
trial("tests/pngsuite/tbbn2c16.png", None);
trial("tests/pngsuite/tbbn3p08.png", None);
trial("tests/pngsuite/tbgn2c16.png", None);
trial("tests/pngsuite/tbgn3p08.png", None);
trial("tests/pngsuite/tbrn2c08.png", None);
trial("tests/pngsuite/tbwn0g16.png", None);
trial("tests/pngsuite/tbwn3p08.png", None);
trial("tests/pngsuite/tbyn3p08.png", None);
trial("tests/pngsuite/tm3n3p02.png", None);
trial("tests/pngsuite/tp0n0g08.png", None);
trial("tests/pngsuite/tp0n2c08.png", None);
trial("tests/pngsuite/tp0n3p08.png", None);
trial("tests/pngsuite/tp1n3p08.png", None);
trial("tests/pngsuite/z00n2c08.png", None);
trial("tests/pngsuite/z03n2c08.png", None);
trial("tests/pngsuite/z06n2c08.png", None);
Ok(())
}
#[test]
fn test_two_iccp_chunks() {
let decoder = crate::Decoder::new(File::open("tests/bugfixes/issue#1825.png").unwrap());
let reader = decoder.read_info().unwrap();
let icc_profile = reader.info().icc_profile.clone().unwrap().into_owned();
assert_eq!(4070462061, crc32fast::hash(&icc_profile));
}
fn write_actl(w: &mut impl Write, animation: &crate::AnimationControl) {
let mut data = Vec::new();
data.write_u32::<byteorder::BigEndian>(animation.num_frames)
.unwrap();
data.write_u32::<byteorder::BigEndian>(animation.num_plays)
.unwrap();
write_chunk(w, b"acTL", &data);
}
fn write_fctl(w: &mut impl Write, frame: &crate::FrameControl) {
let mut data = Vec::new();
data.write_u32::<byteorder::BigEndian>(frame.sequence_number)
.unwrap();
data.write_u32::<byteorder::BigEndian>(frame.width).unwrap();
data.write_u32::<byteorder::BigEndian>(frame.height)
.unwrap();
data.write_u32::<byteorder::BigEndian>(frame.x_offset)
.unwrap();
data.write_u32::<byteorder::BigEndian>(frame.y_offset)
.unwrap();
data.write_u16::<byteorder::BigEndian>(frame.delay_num)
.unwrap();
data.write_u16::<byteorder::BigEndian>(frame.delay_den)
.unwrap();
data.write_u8(frame.dispose_op as u8).unwrap();
data.write_u8(frame.blend_op as u8).unwrap();
write_chunk(w, b"fcTL", &data);
}
fn write_fdat(w: &mut impl Write, sequence_number: u32, image_data: &[u8]) {
let mut data = Vec::new();
data.write_u32::<byteorder::BigEndian>(sequence_number)
.unwrap();
data.write_all(image_data).unwrap();
write_chunk(w, b"fdAT", &data);
}
fn write_fdat_prefix(w: &mut impl Write, num_frames: u32, width: u32) {
write_png_sig(w);
write_rgba8_ihdr_with_width(w, width);
write_actl(
w,
&crate::AnimationControl {
num_frames,
num_plays: 0,
},
);
let mut fctl = crate::FrameControl {
width,
height: width,
..Default::default()
};
write_fctl(w, &fctl);
write_rgba8_idats(w, width, 0x7fffffff);
fctl.sequence_number += 1;
write_fctl(w, &fctl);
}
#[test]
fn test_fdat_chunk_payload_length_0() {
let mut png = Vec::new();
write_fdat_prefix(&mut png, 2, 8);
write_chunk(&mut png, b"fdAT", &[]);
let decoder = Decoder::new(png.as_slice());
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf).unwrap();
let err = reader.next_frame(&mut buf).unwrap_err();
assert!(matches!(&err, DecodingError::Format(_)));
let msg = format!("{err}");
assert_eq!("fdAT chunk shorter than 4 bytes", msg);
}
#[test]
fn test_fdat_chunk_payload_length_3() {
let mut png = Vec::new();
write_fdat_prefix(&mut png, 2, 8);
write_chunk(&mut png, b"fdAT", &[1, 0, 0]);
let decoder = Decoder::new(png.as_slice());
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf).unwrap();
let err = reader.next_frame(&mut buf).unwrap_err();
assert!(matches!(&err, DecodingError::Format(_)));
let msg = format!("{err}");
assert_eq!("fdAT chunk shorter than 4 bytes", msg);
}
#[test]
fn test_frame_split_across_two_fdat_chunks() {
let png = {
let mut png = Vec::new();
write_fdat_prefix(&mut png, 2, 8);
let image_data = generate_rgba8_with_width_and_height(8, 8);
write_fdat(&mut png, 2, &image_data[..30]);
write_fdat(&mut png, 3, &image_data[30..]);
write_iend(&mut png);
png
};
let decoder = Decoder::new(png.as_slice());
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
let Some(animation_control) = reader.info().animation_control else {
panic!("No acTL");
};
assert_eq!(animation_control.num_frames, 2);
let first_frame: Vec<u8>;
{
reader.next_frame(&mut buf).unwrap();
first_frame = buf.clone();
let Some(frame_control) = reader.info().frame_control else {
panic!("No fcTL (1st frame)");
};
assert_eq!(frame_control.sequence_number, 0);
}
let second_frame: Vec<u8>;
{
reader.next_frame(&mut buf).unwrap();
second_frame = buf.clone();
let Some(frame_control) = reader.info().frame_control else {
panic!("No fcTL (2nd frame)");
};
assert_eq!(frame_control.sequence_number, 1);
}
assert_eq!(first_frame, second_frame);
}
#[test]
fn test_idat_bigger_than_image_size_from_ihdr() {
let png = {
let mut png = Vec::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, 8);
write_chunk(
&mut png,
b"IDAT",
&generate_rgba8_with_width_and_height(8, 256),
);
write_iend(&mut png);
png
};
let decoder = Decoder::new(png.as_slice());
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf).unwrap();
assert_eq!(3093270825, crc32fast::hash(&buf));
}
}