use std::convert::TryInto;
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, ContentLightLevelInfo, DisposeOp, FrameControl,
Info, MasteringDisplayColorVolume, ParameterError, ParameterErrorKind, PixelDimensions,
ScaledFloat, SourceChromaticities, Unit,
};
use crate::text_metadata::{ITXtChunk, TEXtChunk, TextDecodingError, ZTXtChunk};
use crate::traits::ReadBytesExt;
use crate::{CodingIndependentCodePoints, Limits};
pub const CHUNK_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,
MissingFctl,
MissingImageData,
ChunkBeforeIhdr {
kind: ChunkType,
},
AfterIdat {
kind: ChunkType,
},
BeforePlte {
kind: ChunkType,
},
AfterPlte {
kind: ChunkType,
},
OutsidePlteIdat {
kind: ChunkType,
},
DuplicateChunk {
kind: ChunkType,
},
ApngOrder {
present: u32,
expected: u32,
},
ShortPalette {
expected: usize,
len: usize,
},
InvalidSbitChunkSize {
color_type: ColorType,
expected: usize,
len: usize,
},
InvalidSbit {
sample_depth: BitDepth,
sbit: u8,
},
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,
UnexpectedRestartOfDataChunkSequence {
kind: ChunkType,
},
ChunkTooShort {
kind: ChunkType,
},
}
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
),
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),
BeforePlte { kind } => write!(fmt, "Chunk {:?} is invalid before PLTE 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
),
InvalidSbitChunkSize {color_type, expected, len} => write!(
fmt,
"The size of the sBIT chunk should be {} byte(s), but {} byte(s) were provided for the {:?} color type.",
expected, len, color_type
),
InvalidSbit {sample_depth, sbit} => write!(
fmt,
"Invalid sBIT value {}. It must be greater than zero and less than the sample depth {:?}.",
sbit, sample_depth
),
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 bit depth {}.", 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."),
NoMoreImageData => write!(
fmt,
"IDAT or fDAT chunk does not have 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"),
UnexpectedRestartOfDataChunkSequence { kind } => {
write!(fmt, "Unexpected restart of {:?} chunk sequence", kind)
}
ChunkTooShort { kind } => {
write!(fmt, "Chunk is too short: {:?}", kind)
}
}
}
}
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,
ignore_iccp_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,
ignore_iccp_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_ignore_iccp_chunk(&mut self, ignore_iccp_chunk: bool) {
self.ignore_iccp_chunk = ignore_iccp_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,
ready_for_idat_chunks: bool,
ready_for_fdat_chunks: bool,
have_iccp: 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,
have_iccp: false,
ready_for_idat_chunks: true,
ready_for_fdat_chunks: 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 set_ignore_iccp_chunk(&mut self, ignore_iccp_chunk: bool) {
self.decode_options.set_ignore_iccp_chunk(ignore_iccp_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> {
if self.state.is_none() {
return Err(DecodingError::Parameter(
ParameterErrorKind::PolledAfterFatalError.into(),
));
}
let len = buf.len();
while !buf.is_empty() {
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) => {
debug_assert!(self.state.is_none());
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.ready_for_idat_chunks = false;
self.ready_for_fdat_chunks = false;
self.state = Some(State::U32 {
kind,
bytes,
accumulated_count: 4,
});
return Ok(Decoded::ImageDataFlushed);
}
self.state = match type_str {
chunk::fdAT => {
if !self.ready_for_fdat_chunks {
return Err(DecodingError::Format(
FormatErrorInner::UnexpectedRestartOfDataChunkSequence {
kind: chunk::fdAT,
}
.into(),
));
}
if length < 4 {
return Err(DecodingError::Format(
FormatErrorInner::FdatShorterThanFourBytes.into(),
));
}
Some(State::new_u32(U32ValueKind::ApngSequenceNumber))
}
IDAT => {
if !self.ready_for_idat_chunks {
return Err(DecodingError::Format(
FormatErrorInner::UnexpectedRestartOfDataChunkSequence {
kind: IDAT,
}
.into(),
));
}
self.have_idat = true;
Some(State::ImageData(type_str))
}
_ => Some(State::ReadChunkData(type_str)),
};
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();
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 {
if type_str == IEND {
debug_assert!(self.state.is_none());
Ok(Decoded::ImageEnd)
} else {
self.state = Some(State::new_u32(U32ValueKind::Length));
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::sBIT => self.parse_sbit(),
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::cICP => Ok(self.parse_cicp()),
chunk::mDCV => Ok(self.parse_mdcv()),
chunk::cLLI => Ok(self.parse_clli()),
chunk::bKGD => Ok(self.parse_bkgd()),
chunk::iCCP if !self.decode_options.ignore_iccp_chunk => 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)),
};
parse_result.map_err(|e| {
self.state = None;
match e {
DecodingError::IoError(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
let fmt_err: FormatError =
FormatErrorInner::ChunkTooShort { kind: type_str }.into();
fmt_err.into()
}
e => e,
}
})
}
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();
self.ready_for_fdat_chunks = true;
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_sbit(&mut self) -> Result<Decoded, DecodingError> {
let mut parse = || {
let info = self.info.as_mut().unwrap();
if info.palette.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::AfterPlte { kind: chunk::sBIT }.into(),
));
}
if self.have_idat {
return Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::sBIT }.into(),
));
}
if info.sbit.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::sBIT }.into(),
));
}
let (color_type, bit_depth) = { (info.color_type, info.bit_depth) };
let sample_depth = if color_type == ColorType::Indexed {
BitDepth::Eight
} else {
bit_depth
};
self.limits
.reserve_bytes(self.current_chunk.raw_bytes.len())?;
let vec = self.current_chunk.raw_bytes.clone();
let len = vec.len();
let expected = match color_type {
ColorType::Grayscale => 1,
ColorType::Rgb | ColorType::Indexed => 3,
ColorType::GrayscaleAlpha => 2,
ColorType::Rgba => 4,
};
if expected != len {
return Err(DecodingError::Format(
FormatErrorInner::InvalidSbitChunkSize {
color_type,
expected,
len,
}
.into(),
));
}
for sbit in &vec {
if *sbit < 1 || *sbit > sample_depth as u8 {
return Err(DecodingError::Format(
FormatErrorInner::InvalidSbit {
sample_depth,
sbit: *sbit,
}
.into(),
));
}
}
info.sbit = Some(Cow::Owned(vec));
Ok(Decoded::Nothing)
};
parse().ok();
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::BeforePlte { 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_cicp(&mut self) -> Decoded {
fn parse(mut buf: &[u8]) -> Result<CodingIndependentCodePoints, std::io::Error> {
let color_primaries: u8 = buf.read_be()?;
let transfer_function: u8 = buf.read_be()?;
let matrix_coefficients: u8 = buf.read_be()?;
let is_video_full_range_image = {
let flag: u8 = buf.read_be()?;
match flag {
0 => false,
1 => true,
_ => {
return Err(std::io::ErrorKind::InvalidData.into());
}
}
};
if matrix_coefficients != 0 {
return Err(std::io::ErrorKind::InvalidData.into());
}
if !buf.is_empty() {
return Err(std::io::ErrorKind::InvalidData.into());
}
Ok(CodingIndependentCodePoints {
color_primaries,
transfer_function,
matrix_coefficients,
is_video_full_range_image,
})
}
let info = self.info.as_mut().unwrap();
let is_before_plte_and_idat = !self.have_idat && info.palette.is_none();
if is_before_plte_and_idat && info.coding_independent_code_points.is_none() {
info.coding_independent_code_points = parse(&self.current_chunk.raw_bytes[..]).ok();
}
Decoded::Nothing
}
fn parse_mdcv(&mut self) -> Decoded {
fn parse(mut buf: &[u8]) -> Result<MasteringDisplayColorVolume, std::io::Error> {
let red_x: u16 = buf.read_be()?;
let red_y: u16 = buf.read_be()?;
let green_x: u16 = buf.read_be()?;
let green_y: u16 = buf.read_be()?;
let blue_x: u16 = buf.read_be()?;
let blue_y: u16 = buf.read_be()?;
let white_x: u16 = buf.read_be()?;
let white_y: u16 = buf.read_be()?;
fn scale(chunk: u16) -> ScaledFloat {
ScaledFloat::from_scaled((chunk as u32) * 2)
}
let chromaticities = SourceChromaticities {
white: (scale(white_x), scale(white_y)),
red: (scale(red_x), scale(red_y)),
green: (scale(green_x), scale(green_y)),
blue: (scale(blue_x), scale(blue_y)),
};
let max_luminance: u32 = buf.read_be()?;
let min_luminance: u32 = buf.read_be()?;
if !buf.is_empty() {
return Err(std::io::ErrorKind::InvalidData.into());
}
Ok(MasteringDisplayColorVolume {
chromaticities,
max_luminance,
min_luminance,
})
}
let info = self.info.as_mut().unwrap();
let is_before_plte_and_idat = !self.have_idat && info.palette.is_none();
if is_before_plte_and_idat && info.mastering_display_color_volume.is_none() {
info.mastering_display_color_volume = parse(&self.current_chunk.raw_bytes[..]).ok();
}
Decoded::Nothing
}
fn parse_clli(&mut self) -> Decoded {
fn parse(mut buf: &[u8]) -> Result<ContentLightLevelInfo, std::io::Error> {
let max_content_light_level: u32 = buf.read_be()?;
let max_frame_average_light_level: u32 = buf.read_be()?;
if !buf.is_empty() {
return Err(std::io::ErrorKind::InvalidData.into());
}
Ok(ContentLightLevelInfo {
max_content_light_level,
max_frame_average_light_level,
})
}
let info = self.info.as_mut().unwrap();
if info.content_light_level.is_none() {
info.content_light_level = parse(&self.current_chunk.raw_bytes[..]).ok();
}
Decoded::Nothing
}
fn parse_iccp(&mut self) -> Result<Decoded, DecodingError> {
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::iCCP }.into(),
))
} else if self.have_iccp {
Ok(Decoded::Nothing)
} else {
self.have_iccp = true;
let _ = self.parse_iccp_raw();
Ok(Decoded::Nothing)
}
}
fn parse_iccp_raw(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
let mut buf = &self.current_chunk.raw_bytes[..];
for len in 0..=80 {
let raw: u8 = buf.read_be()?;
if (raw == 0 && len == 0) || (raw != 0 && len == 80) {
return Err(DecodingError::from(TextDecodingError::InvalidKeywordSize));
}
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(())
}
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)
}
fn parse_bkgd(&mut self) -> Decoded {
let info = self.info.as_mut().unwrap();
if info.bkgd.is_none() && !self.have_idat {
let expected = match info.color_type {
ColorType::Indexed => {
if info.palette.is_none() {
return Decoded::Nothing;
};
1
}
ColorType::Grayscale | ColorType::GrayscaleAlpha => 2,
ColorType::Rgb | ColorType::Rgba => 6,
};
let vec = self.current_chunk.raw_bytes.clone();
let len = vec.len();
if len == expected {
info.bkgd = Some(Cow::Owned(vec));
}
}
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(CHUNK_BUFFER_SIZE),
}
}
}
#[cfg(test)]
mod tests {
use super::ScaledFloat;
use super::SourceChromaticities;
use crate::test_utils::*;
use crate::{Decoder, DecodingError, Reader};
use approx::assert_relative_eq;
use byteorder::WriteBytesExt;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fs::File;
use std::io::{ErrorKind, Read, Write};
use std::rc::Rc;
#[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 image_source_sbit() {
fn trial(path: &str, expected: Option<Cow<[u8]>>) {
let decoder = crate::Decoder::new(File::open(path).unwrap());
let reader = decoder.read_info().unwrap();
let actual: Option<Cow<[u8]>> = reader.info().sbit.clone();
assert!(actual == expected);
}
trial("tests/sbit/g.png", Some(Cow::Owned(vec![5u8])));
trial("tests/sbit/ga.png", Some(Cow::Owned(vec![5u8, 3u8])));
trial(
"tests/sbit/indexed.png",
Some(Cow::Owned(vec![5u8, 6u8, 5u8])),
);
trial("tests/sbit/rgb.png", Some(Cow::Owned(vec![5u8, 6u8, 5u8])));
trial(
"tests/sbit/rgba.png",
Some(Cow::Owned(vec![5u8, 6u8, 5u8, 8u8])),
);
}
#[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));
}
#[test]
fn test_iccp_roundtrip() {
let dummy_icc = b"I'm a profile";
let mut info = crate::Info::with_size(1, 1);
info.icc_profile = Some(dummy_icc.into());
let mut encoded_image = Vec::new();
let enc = crate::Encoder::with_info(&mut encoded_image, info).unwrap();
let mut enc = enc.write_header().unwrap();
enc.write_image_data(&[0]).unwrap();
enc.finish().unwrap();
let dec = crate::Decoder::new(encoded_image.as_slice());
let dec = dec.read_info().unwrap();
assert_eq!(dummy_icc, &**dec.info().icc_profile.as_ref().unwrap());
}
#[test]
fn test_png_with_broken_iccp() {
let decoder = crate::Decoder::new(File::open("tests/iccp/broken_iccp.png").unwrap());
assert!(decoder.read_info().is_ok());
let mut decoder = crate::Decoder::new(File::open("tests/iccp/broken_iccp.png").unwrap());
decoder.set_ignore_iccp_chunk(true);
assert!(decoder.read_info().is_ok());
}
#[test]
fn test_mdcv_and_clli_chunks() {
let decoder = crate::Decoder::new(File::open("tests/bugfixes/cicp_pq.png").unwrap());
let reader = decoder.read_info().unwrap();
let info = reader.info();
let cicp = info.coding_independent_code_points.unwrap();
assert_eq!(cicp.color_primaries, 9);
assert_eq!(cicp.transfer_function, 16);
assert_eq!(cicp.matrix_coefficients, 0);
assert!(cicp.is_video_full_range_image);
let mdcv = info.mastering_display_color_volume.unwrap();
assert_relative_eq!(mdcv.chromaticities.red.0.into_value(), 0.680);
assert_relative_eq!(mdcv.chromaticities.red.1.into_value(), 0.320);
assert_relative_eq!(mdcv.chromaticities.green.0.into_value(), 0.265);
assert_relative_eq!(mdcv.chromaticities.green.1.into_value(), 0.690);
assert_relative_eq!(mdcv.chromaticities.blue.0.into_value(), 0.150);
assert_relative_eq!(mdcv.chromaticities.blue.1.into_value(), 0.060);
assert_relative_eq!(mdcv.chromaticities.white.0.into_value(), 0.3127);
assert_relative_eq!(mdcv.chromaticities.white.1.into_value(), 0.3290);
assert_relative_eq!(mdcv.min_luminance as f32 / 10_000.0, 0.01);
assert_relative_eq!(mdcv.max_luminance as f32 / 10_000.0, 5000.0);
let clli = info.content_light_level.unwrap();
assert_relative_eq!(clli.max_content_light_level as f32 / 10_000.0, 4000.0);
assert_relative_eq!(clli.max_frame_average_light_level as f32 / 10_000.0, 2627.0);
}
#[test]
fn test_finishing_twice() {
let mut png = Vec::new();
write_noncompressed_png(&mut png, 16, 1024);
let decoder = Decoder::new(png.as_slice());
let mut reader = decoder.read_info().unwrap();
reader.finish().unwrap();
let err = reader.finish().unwrap_err();
assert!(matches!(&err, DecodingError::Parameter(_)));
assert_eq!("End of image has been reached", format!("{err}"));
}
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(_)));
assert_eq!("fdAT chunk shorter than 4 bytes", format!("{err}"));
let err2 = reader.next_frame(&mut buf).unwrap_err();
assert!(matches!(&err2, DecodingError::Parameter(_)));
assert_eq!(
"A fatal decoding error has been encounted earlier",
format!("{err2}")
);
}
#[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(_)));
assert_eq!("fdAT chunk shorter than 4 bytes", format!("{err}"));
}
#[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));
}
#[test]
fn test_only_idat_chunk_in_input_stream() {
let png = {
let mut png = Vec::new();
write_png_sig(&mut png);
write_chunk(&mut png, b"IDAT", &[]);
png
};
let decoder = Decoder::new(png.as_slice());
let Err(err) = decoder.read_info() else {
panic!("Expected an error")
};
assert!(matches!(&err, DecodingError::Format(_)));
assert_eq!(
"ChunkType { type: IDAT, \
critical: true, \
private: false, \
reserved: false, \
safecopy: false \
} chunk appeared before IHDR chunk",
format!("{err}"),
);
}
#[derive(Clone)]
struct StreamingInput(Rc<RefCell<StreamingInputState>>);
struct StreamingInputState {
full_input: Vec<u8>,
current_pos: usize,
available_len: usize,
}
impl StreamingInput {
fn new(full_input: Vec<u8>) -> Self {
Self(Rc::new(RefCell::new(StreamingInputState {
full_input,
current_pos: 0,
available_len: 0,
})))
}
fn with_noncompressed_png(width: u32, idat_size: usize) -> Self {
let mut png = Vec::new();
write_noncompressed_png(&mut png, width, idat_size);
Self::new(png)
}
fn expose_next_byte(&self) {
let mut state = self.0.borrow_mut();
assert!(state.available_len < state.full_input.len());
state.available_len += 1;
}
fn stream_input_until_reader_is_available(&self) -> Reader<StreamingInput> {
loop {
self.0.borrow_mut().current_pos = 0;
match Decoder::new(self.clone()).read_info() {
Ok(reader) => {
break reader;
}
Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => {
self.expose_next_byte();
}
_ => panic!("Unexpected error"),
}
}
}
fn decode_full_input<F, R>(&self, f: F) -> R
where
F: FnOnce(Reader<&[u8]>) -> R,
{
let state = self.0.borrow();
let decoder = Decoder::new(state.full_input.as_slice());
f(decoder.read_info().unwrap())
}
}
impl Read for StreamingInput {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let mut state = self.0.borrow_mut();
let mut available_bytes = &state.full_input[state.current_pos..state.available_len];
let number_of_read_bytes = available_bytes.read(buf)?;
state.current_pos += number_of_read_bytes;
assert!(state.current_pos <= state.available_len);
Ok(number_of_read_bytes)
}
}
#[test]
fn test_streaming_input_and_decoding_via_next_frame() {
const WIDTH: u32 = 16;
const IDAT_SIZE: usize = 512;
let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE);
let (whole_output_info, decoded_from_whole_input) =
streaming_input.decode_full_input(|mut r| {
let mut buf = vec![0; r.output_buffer_size()];
let output_info = r.next_frame(&mut buf).unwrap();
(output_info, buf)
});
let mut png_reader = streaming_input.stream_input_until_reader_is_available();
let mut decoded_from_streaming_input = vec![0; png_reader.output_buffer_size()];
let streaming_output_info = loop {
match png_reader.next_frame(decoded_from_streaming_input.as_mut_slice()) {
Ok(output_info) => break output_info,
Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => {
streaming_input.expose_next_byte()
}
e => panic!("Unexpected error: {:?}", e),
}
};
assert_eq!(whole_output_info, streaming_output_info);
assert_eq!(
crc32fast::hash(&decoded_from_whole_input),
crc32fast::hash(&decoded_from_streaming_input)
);
}
#[test]
fn test_streaming_input_and_decoding_via_next_row() {
const WIDTH: u32 = 16;
const IDAT_SIZE: usize = 512;
let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE);
let decoded_from_whole_input = streaming_input.decode_full_input(|mut r| {
let mut buf = vec![0; r.output_buffer_size()];
r.next_frame(&mut buf).unwrap();
buf
});
let mut png_reader = streaming_input.stream_input_until_reader_is_available();
let mut decoded_from_streaming_input = Vec::new();
loop {
match png_reader.next_row() {
Ok(None) => break,
Ok(Some(row)) => decoded_from_streaming_input.extend_from_slice(row.data()),
Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => {
streaming_input.expose_next_byte()
}
e => panic!("Unexpected error: {:?}", e),
}
}
assert_eq!(
crc32fast::hash(&decoded_from_whole_input),
crc32fast::hash(&decoded_from_streaming_input)
);
}
#[test]
fn test_streaming_input_and_reading_header_info() {
const WIDTH: u32 = 16;
const IDAT_SIZE: usize = 512;
let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE);
let info_from_whole_input = streaming_input.decode_full_input(|r| r.info().clone());
let mut decoder = Decoder::new(streaming_input.clone());
let info_from_streaming_input = loop {
match decoder.read_header_info() {
Ok(info) => break info.clone(),
Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => {
streaming_input.expose_next_byte()
}
e => panic!("Unexpected error: {:?}", e),
}
};
assert_eq!(info_from_whole_input.width, info_from_streaming_input.width);
assert_eq!(
info_from_whole_input.height,
info_from_streaming_input.height
);
assert_eq!(
info_from_whole_input.bit_depth,
info_from_streaming_input.bit_depth
);
assert_eq!(
info_from_whole_input.color_type,
info_from_streaming_input.color_type
);
assert_eq!(
info_from_whole_input.interlaced,
info_from_streaming_input.interlaced
);
}
fn create_reader_of_ihdr_idat() -> Reader<VecDeque<u8>> {
let mut png = VecDeque::new();
write_noncompressed_png(&mut png, 16, 1024);
Decoder::new(png).read_info().unwrap()
}
fn create_reader_of_ihdr_actl_fctl_idat_fctl_fdat() -> Reader<VecDeque<u8>> {
let idat_width = 16;
let mut fctl = crate::FrameControl {
width: idat_width,
height: idat_width, ..Default::default()
};
let mut png = VecDeque::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, idat_width);
write_actl(
&mut png,
&crate::AnimationControl {
num_frames: 2,
num_plays: 0,
},
);
fctl.sequence_number = 0;
write_fctl(&mut png, &fctl);
let idat_data = generate_rgba8_with_width_and_height(fctl.width, fctl.height + 1);
write_chunk(&mut png, b"IDAT", &idat_data);
let fdat_width = 10;
fctl.sequence_number = 1;
fctl.width = fdat_width;
write_fctl(&mut png, &fctl);
let fdat_data = generate_rgba8_with_width_and_height(fctl.width, fctl.height);
write_fdat(&mut png, 2, &fdat_data);
write_iend(&mut png);
Decoder::new(png).read_info().unwrap()
}
fn create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat() -> Reader<VecDeque<u8>> {
let width = 16;
let frame_data = generate_rgba8_with_width_and_height(width, width);
let mut fctl = crate::FrameControl {
width,
height: width,
..Default::default()
};
let mut png = VecDeque::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, width);
write_actl(
&mut png,
&crate::AnimationControl {
num_frames: 2,
num_plays: 0,
},
);
write_chunk(&mut png, b"IDAT", &frame_data);
fctl.sequence_number = 0;
write_fctl(&mut png, &fctl);
write_fdat(&mut png, 1, &frame_data);
fctl.sequence_number = 2;
write_fctl(&mut png, &fctl);
write_fdat(&mut png, 3, &frame_data);
write_iend(&mut png);
Decoder::new(png).read_info().unwrap()
}
fn get_fctl_sequence_number(reader: &Reader<impl Read>) -> u32 {
reader
.info()
.frame_control
.as_ref()
.unwrap()
.sequence_number
}
#[test]
fn test_next_frame_polling_after_end_non_animated() {
let mut reader = create_reader_of_ihdr_idat();
let mut buf = vec![0; reader.output_buffer_size()];
reader
.next_frame(&mut buf)
.expect("Expecting no error for IDAT frame");
let err = reader
.next_frame(&mut buf)
.expect_err("Main test - expecting error");
assert!(
matches!(&err, DecodingError::Parameter(_)),
"Unexpected kind of error: {:?}",
&err,
);
}
#[test]
fn test_next_frame_info_polling_after_end_non_animated() {
let mut reader = create_reader_of_ihdr_idat();
let err = reader
.next_frame_info()
.expect_err("Main test - expecting error");
assert!(
matches!(&err, DecodingError::Parameter(_)),
"Unexpected kind of error: {:?}",
&err,
);
}
#[test]
fn test_next_frame_polling_after_end_idat_part_of_animation() {
let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat();
let mut buf = vec![0; reader.output_buffer_size()];
assert_eq!(get_fctl_sequence_number(&reader), 0);
reader
.next_frame(&mut buf)
.expect("Expecting no error for IDAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 0);
reader
.next_frame(&mut buf)
.expect("Expecting no error for fdAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 1);
let err = reader
.next_frame(&mut buf)
.expect_err("Main test - expecting error");
assert!(
matches!(&err, DecodingError::Parameter(_)),
"Unexpected kind of error: {:?}",
&err,
);
}
#[test]
fn test_next_frame_polling_after_end_idat_not_part_of_animation() {
let mut reader = create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat();
let mut buf = vec![0; reader.output_buffer_size()];
assert!(reader.info().frame_control.is_none());
reader
.next_frame(&mut buf)
.expect("Expecting no error for IDAT frame");
assert!(reader.info().frame_control.is_none());
reader
.next_frame(&mut buf)
.expect("Expecting no error for 1st fdAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 0);
reader
.next_frame(&mut buf)
.expect("Expecting no error for 2nd fdAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 2);
let err = reader
.next_frame(&mut buf)
.expect_err("Main test - expecting error");
assert!(
matches!(&err, DecodingError::Parameter(_)),
"Unexpected kind of error: {:?}",
&err,
);
}
#[test]
fn test_row_by_row_then_next_frame() {
let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat();
let mut buf = vec![0; reader.output_buffer_size()];
assert_eq!(get_fctl_sequence_number(&reader), 0);
while let Some(_) = reader.next_row().unwrap() {}
assert_eq!(get_fctl_sequence_number(&reader), 0);
buf.fill(0x0f);
reader
.next_frame(&mut buf)
.expect("Expecting no error from next_frame call");
assert_eq!(get_fctl_sequence_number(&reader), 1);
assert!(buf.iter().any(|byte| *byte != 0x0f));
}
#[test]
fn test_row_by_row_of_two_frames() {
let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat();
let mut rows_of_frame1 = 0;
assert_eq!(get_fctl_sequence_number(&reader), 0);
while let Some(_) = reader.next_row().unwrap() {
rows_of_frame1 += 1;
}
assert_eq!(rows_of_frame1, 16);
assert_eq!(get_fctl_sequence_number(&reader), 0);
let mut rows_of_frame2 = 0;
assert_eq!(reader.next_frame_info().unwrap().sequence_number, 1);
assert_eq!(get_fctl_sequence_number(&reader), 1);
while let Some(_) = reader.next_row().unwrap() {
rows_of_frame2 += 1;
}
assert_eq!(rows_of_frame2, 16);
assert_eq!(get_fctl_sequence_number(&reader), 1);
let err = reader
.next_frame_info()
.expect_err("No more frames - expecting error");
assert!(
matches!(&err, DecodingError::Parameter(_)),
"Unexpected kind of error: {:?}",
&err,
);
}
#[test]
fn test_next_frame_info_after_next_frame() {
let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat();
let mut buf = vec![0; reader.output_buffer_size()];
assert_eq!(get_fctl_sequence_number(&reader), 0);
reader
.next_frame(&mut buf)
.expect("Expecting no error for IDAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 0);
assert_eq!(reader.next_frame_info().unwrap().sequence_number, 1);
assert_eq!(get_fctl_sequence_number(&reader), 1);
reader
.next_frame(&mut buf)
.expect("Expecting no error for fdAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 1);
let err = reader
.next_frame_info()
.expect_err("Main test - expecting error");
assert!(
matches!(&err, DecodingError::Parameter(_)),
"Unexpected kind of error: {:?}",
&err,
);
}
#[test]
fn test_next_frame_info_to_skip_first_frame() {
let mut reader = create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat();
let mut buf = vec![0; reader.output_buffer_size()];
assert!(reader.info().frame_control.is_none());
assert_eq!(reader.next_frame_info().unwrap().sequence_number, 0);
assert_eq!(get_fctl_sequence_number(&reader), 0);
reader
.next_frame(&mut buf)
.expect("Expecting no error for 1st fdAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 0);
assert_eq!(reader.next_frame_info().unwrap().sequence_number, 2);
reader
.next_frame(&mut buf)
.expect("Expecting no error for 2nd fdAT frame");
assert_eq!(get_fctl_sequence_number(&reader), 2);
let err = reader
.next_frame_info()
.expect_err("Main test - expecting error");
assert!(
matches!(&err, DecodingError::Parameter(_)),
"Unexpected kind of error: {:?}",
&err,
);
}
}