Files
another-boids-in-rust/vendor/png/src/decoder/stream.rs

3214 lines
122 KiB
Rust

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::UnfilterBuf;
use super::zlib::ZlibStream;
use crate::chunk::is_critical;
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 = 128;
/// Determines if checksum checks should be disabled globally.
///
/// This is used only in fuzzing. `afl` automatically adds `--cfg fuzzing` to RUSTFLAGS which can
/// be used to detect that build.
#[allow(unexpected_cfgs)]
const CHECKSUM_DISABLED: bool = cfg!(fuzzing);
/// Kind of `u32` value that is being read via `State::U32`.
#[derive(Debug)]
enum U32ValueKind {
/// First 4 bytes of the PNG signature - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#PNG-file-signature
Signature1stU32,
/// Second 4 bytes of the PNG signature - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#PNG-file-signature
Signature2ndU32,
/// Chunk length - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout
Length,
/// Chunk type - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout
Type { length: u32 },
/// Chunk checksum - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout
Crc(ChunkType),
/// Sequence number from an `fdAT` chunk - see
/// https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk
ApngSequenceNumber,
}
#[derive(Debug)]
enum State {
/// In this state we are reading a u32 value from external input. We start with
/// `accumulated_count` set to `0`. After reading or accumulating the required 4 bytes we will
/// call `parse_32` which will then move onto the next state.
U32 {
kind: U32ValueKind,
bytes: [u8; 4],
accumulated_count: usize,
},
/// In this state we are reading chunk data from external input, and appending it to
/// `ChunkState::raw_bytes`. Then if all data has been read, we parse the chunk.
ReadChunkData(ChunkType),
/// In this state we are reading image data from external input and feeding it directly into
/// `StreamingDecoder::inflater`.
ImageData(ChunkType),
}
impl State {
fn new_u32(kind: U32ValueKind) -> Self {
Self::U32 {
kind,
bytes: [0; 4],
accumulated_count: 0,
}
}
}
#[derive(Debug)]
/// Result of the decoding process
pub enum Decoded {
/// Nothing decoded yet
Nothing,
/// A chunk header (length and type fields) has been read.
ChunkBegin(u32, ChunkType),
/// Chunk has been read successfully.
ChunkComplete(ChunkType),
/// An ancillary chunk has been read but it was in the wrong place, had corrupt contents, or had
/// an invalid CRC.
BadAncillaryChunk(ChunkType),
/// Skipped an ancillary chunk because it was unrecognized or the decoder was configured to skip
/// this type of chunk.
SkippedAncillaryChunk(ChunkType),
/// Decoded raw image data.
ImageData,
/// The last of a consecutive chunk of IDAT was done.
/// This is distinct from ChunkComplete which only marks that some IDAT chunk was completed but
/// not that no additional IDAT chunk follows.
ImageDataFlushed,
}
/// Any kind of error during PNG decoding.
///
/// This enumeration provides a very rough analysis on the origin of the failure. That is, each
/// variant corresponds to one kind of actor causing the error. It should not be understood as a
/// direct blame but can inform the search for a root cause or if such a search is required.
#[derive(Debug)]
pub enum DecodingError {
/// An error in IO of the underlying reader.
///
/// Note that some IO errors may be recoverable - decoding may be retried after the
/// error is resolved. For example, decoding from a slow stream of data (e.g. decoding from a
/// network stream) may occasionally result in [std::io::ErrorKind::UnexpectedEof] kind of
/// error, but decoding can resume when more data becomes available.
IoError(io::Error),
/// The input image was not a valid PNG.
///
/// There isn't a lot that can be done here, except if the program itself was responsible for
/// creating this image then investigate the generator. This is internally implemented with a
/// large Enum. If You are interested in accessing some of the more exact information on the
/// variant then we can discuss in an issue.
Format(FormatError),
/// An interface was used incorrectly.
///
/// This is used in cases where it's expected that the programmer might trip up and stability
/// could be affected. For example when:
///
/// * The decoder is polled for more animation frames despite being done (or not being animated
/// in the first place).
/// * The output buffer does not have the required size.
///
/// As a rough guideline for introducing new variants parts of the requirements are dynamically
/// derived from the (untrusted) input data while the other half is from the caller. In the
/// above cases the number of frames respectively the size is determined by the file while the
/// number of calls
///
/// If you're an application you might want to signal that a bug report is appreciated.
Parameter(ParameterError),
/// The image would have required exceeding the limits configured with the decoder.
///
/// Note that Your allocations, e.g. when reading into a pre-allocated buffer, is __NOT__
/// considered part of the limits. Nevertheless, required intermediate buffers such as for
/// singular lines is checked against the limit.
///
/// Note that this is a best-effort basis.
LimitsExceeded,
}
#[derive(Debug)]
pub struct FormatError {
inner: FormatErrorInner,
}
#[derive(Debug)]
pub(crate) enum FormatErrorInner {
/// Bad framing.
CrcMismatch {
/// Stored CRC32 value
crc_val: u32,
/// Calculated CRC32 sum
crc_sum: u32,
/// The chunk type that has the CRC mismatch.
chunk: ChunkType,
},
/// Not a PNG, the magic signature is missing.
InvalidSignature,
// Errors of chunk level ordering, missing etc.
/// Fctl must occur if an animated chunk occurs.
MissingFctl,
/// Image data that was indicated in IHDR or acTL is missing.
MissingImageData,
/// 4.3., Must be first.
ChunkBeforeIhdr {
kind: ChunkType,
},
/// 4.3., some chunks must be before IDAT.
AfterIdat {
kind: ChunkType,
},
// 4.3., Some chunks must be after PLTE.
BeforePlte {
kind: ChunkType,
},
/// 4.3., some chunks must be before PLTE.
AfterPlte {
kind: ChunkType,
},
/// 4.3., some chunks must be between PLTE and IDAT.
OutsidePlteIdat {
kind: ChunkType,
},
/// 4.3., some chunks must be unique.
DuplicateChunk {
kind: ChunkType,
},
/// Specifically for fdat there is an embedded sequence number for chunks.
ApngOrder {
/// The sequence number in the chunk.
present: u32,
/// The one that should have been present.
expected: u32,
},
// Errors specific to particular chunk data to be validated.
/// The palette did not even contain a single pixel data.
ShortPalette {
expected: usize,
len: usize,
},
/// sBIT chunk size based on color type.
InvalidSbitChunkSize {
color_type: ColorType,
expected: usize,
len: usize,
},
InvalidSbit {
sample_depth: BitDepth,
sbit: u8,
},
/// A palletized image did not have a palette.
PaletteRequired,
/// The color-depth combination is not valid according to Table 11.1.
InvalidColorBitDepth {
color_type: ColorType,
bit_depth: BitDepth,
},
ColorWithBadTrns(ColorType),
/// The image width or height is zero.
InvalidDimensions,
InvalidBitDepth(u8),
InvalidColorType(u8),
InvalidDisposeOp(u8),
InvalidBlendOp(u8),
InvalidUnit(u8),
/// The rendering intent of the sRGB chunk is invalid.
InvalidSrgbRenderingIntent(u8),
UnknownCompressionMethod(u8),
UnknownFilterMethod(u8),
UnknownInterlaceMethod(u8),
/// The subframe is not in bounds of the image.
/// TODO: fields with relevant data.
BadSubFrameBounds {},
// Errors specific to the IDAT/fdAT chunks.
/// The compression of the data stream was faulty.
CorruptFlateStream {
err: fdeflate::DecompressionError,
},
/// The image data chunk was too short for the expected pixel count.
NoMoreImageData,
/// Bad text encoding
BadTextEncoding(TextDecodingError),
/// fdAT shorter than 4 bytes
FdatShorterThanFourBytes,
/// "11.2.4 IDAT Image data" section of the PNG spec says: There may be multiple IDAT chunks;
/// if so, they shall appear consecutively with no other intervening chunks.
/// `UnexpectedRestartOfDataChunkSequence{kind: IDAT}` indicates that there were "intervening
/// chunks".
///
/// The APNG spec doesn't directly describe an error similar to `CantInterleaveIdatChunks`,
/// but we require that a new sequence of consecutive `fdAT` chunks cannot appear unless we've
/// seen an `fcTL` chunk.
UnexpectedRestartOfDataChunkSequence {
kind: ChunkType,
},
/// Failure to parse a chunk, because the chunk didn't contain enough bytes.
ChunkTooShort {
kind: ChunkType,
},
UnrecognizedCriticalChunk {
/// The type of the unrecognized critical chunk.
type_str: ChunkType,
},
BadGammaValue,
}
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)
}
// TODO: Wrap more info in the enum variant
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)
}
UnrecognizedCriticalChunk { type_str } => {
write!(fmt, "Unrecognized critical chunk: {:?}", type_str)
}
BadGammaValue => write!(fmt, "Bad gamma value."),
}
}
}
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),
})
}
}
/// Decoder configuration options
#[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 {
/// When set, the decoder will not compute and verify the Adler-32 checksum.
///
/// Defaults to `true`.
pub fn set_ignore_adler32(&mut self, ignore_adler32: bool) {
self.ignore_adler32 = ignore_adler32;
}
/// When set, the decoder will not compute and verify the CRC code.
///
/// Defaults to `false`.
pub fn set_ignore_crc(&mut self, ignore_crc: bool) {
self.ignore_crc = ignore_crc;
}
/// Flag to ignore computing and verifying the Adler-32 checksum and CRC
/// code.
pub fn set_ignore_checksums(&mut self, ignore_checksums: bool) {
self.ignore_adler32 = ignore_checksums;
self.ignore_crc = ignore_checksums;
}
/// Ignore text chunks while decoding.
///
/// Defaults to `false`.
pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) {
self.ignore_text_chunk = ignore_text_chunk;
}
/// Ignore ICCP chunks while decoding.
///
/// Defaults to `false`.
pub fn set_ignore_iccp_chunk(&mut self, ignore_iccp_chunk: bool) {
self.ignore_iccp_chunk = ignore_iccp_chunk;
}
/// Ignore ancillary chunks if CRC fails
///
/// Defaults to `true`
pub fn set_skip_ancillary_crc_failures(&mut self, skip_ancillary_crc_failures: bool) {
self.skip_ancillary_crc_failures = skip_ancillary_crc_failures;
}
}
/// PNG StreamingDecoder (low-level interface)
///
/// By default, the decoder does not verify Adler-32 checksum computation. To
/// enable checksum verification, set it with [`StreamingDecoder::set_ignore_adler32`]
/// before starting decompression.
pub struct StreamingDecoder {
state: Option<State>,
current_chunk: ChunkState,
/// The inflater state handling consecutive `IDAT` and `fdAT` chunks.
inflater: ZlibStream,
/// The complete image info read from all prior chunks.
pub(crate) info: Option<Info<'static>>,
/// The animation chunk sequence number.
current_seq_no: Option<u32>,
/// Whether we have already seen a start of an IDAT chunk. (Used to validate chunk ordering -
/// some chunk types can only appear before or after an IDAT chunk.)
have_idat: bool,
/// Whether we are ready for a start of an `IDAT` chunk sequence. Initially `true` and set to
/// `false` when the first sequence of consecutive `IDAT` chunks ends.
ready_for_idat_chunks: bool,
/// Whether we are ready for a start of an `fdAT` chunk sequence. Initially `false`. Set to
/// `true` after encountering an `fcTL` chunk. Set to `false` when a sequence of consecutive
/// `fdAT` chunks ends.
ready_for_fdat_chunks: bool,
/// Whether we have already seen an iCCP chunk. Used to prevent parsing of duplicate iCCP chunks.
have_iccp: bool,
decode_options: DecodeOptions,
pub(crate) limits: Limits,
}
struct ChunkState {
/// The type of the current chunk.
/// Relevant for `IDAT` and `fdAT` which aggregate consecutive chunks of their own type.
type_: ChunkType,
/// Partial crc until now.
crc: Crc32,
/// Remaining bytes to be read.
remaining: u32,
/// Non-decoded bytes in the chunk.
raw_bytes: Vec<u8>,
}
impl StreamingDecoder {
/// Creates a new StreamingDecoder
///
/// Allocates the internal buffers.
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 {
type_: ChunkType([0; 4]),
crc: Crc32::new(),
remaining: 0,
raw_bytes: Vec::with_capacity(CHUNK_BUFFER_SIZE),
},
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 },
}
}
/// Resets the StreamingDecoder
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;
}
/// Provides access to the inner `info` field
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);
}
/// Return whether the decoder is set to ignore the Adler-32 checksum.
pub fn ignore_adler32(&self) -> bool {
self.inflater.ignore_adler32()
}
/// Set whether to compute and verify the Adler-32 checksum during
/// decompression. Return `true` if the flag was successfully set.
///
/// The decoder defaults to `true`.
///
/// This flag cannot be modified after decompression has started until the
/// [`StreamingDecoder`] is reset.
pub fn set_ignore_adler32(&mut self, ignore_adler32: bool) -> bool {
self.inflater.set_ignore_adler32(ignore_adler32)
}
/// Set whether to compute and verify the Adler-32 checksum during
/// decompression.
///
/// The decoder defaults to `false`.
pub fn set_ignore_crc(&mut self, ignore_crc: bool) {
self.decode_options.set_ignore_crc(ignore_crc)
}
/// Ignore ancillary chunks if CRC fails
///
/// Defaults to `true`
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)
}
/// Low level StreamingDecoder interface.
///
/// Allows to stream partial data to the encoder. Returns a tuple containing the bytes that have
/// been consumed from the input buffer and the current decoding result. If the decoded chunk
/// was an image data chunk, it also appends the read data to `image_data`.
pub fn update(
&mut self,
mut buf: &[u8],
mut image_data: Option<&mut UnfilterBuf<'_>>,
) -> Result<(usize, Decoded), DecodingError> {
if self.state.is_none() {
return Err(DecodingError::Parameter(
ParameterErrorKind::PolledAfterFatalError.into(),
));
}
let len = buf.len();
while !buf.is_empty() {
let image_data = image_data.as_deref_mut();
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: Option<&mut UnfilterBuf<'_>>,
) -> Result<(usize, Decoded), DecodingError> {
use self::State::*;
// Driver should ensure that state is never None
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 {
// Handling these `accumulated_count` and `buf.len()` values in a separate `if`
// branch is not strictly necessary - the `else` statement below is already
// capable of handling these values. The main reason for special-casing these
// values is that they occur fairly frequently and special-casing them results
// in performance gains.
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))
}
}
}
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;
if raw_bytes.len() == raw_bytes.capacity() {
if self.limits.bytes == 0 {
return Err(DecodingError::LimitsExceeded);
}
// Double the size of the Vec, but not beyond the allocation limit.
debug_assert!(raw_bytes.capacity() > 0);
let reserve_size = raw_bytes.capacity().min(self.limits.bytes);
self.limits.reserve_bytes(reserve_size)?;
raw_bytes.reserve_exact(reserve_size);
}
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);
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 {
debug_assert!(type_str != IDAT && type_str != chunk::fdAT);
self.state = Some(State::new_u32(U32ValueKind::Crc(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 = if let Some(image_data) = image_data {
self.inflater.decompress(buf, image_data)?
} else {
len
};
if !self.decode_options.ignore_crc {
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: Option<&mut UnfilterBuf<'_>>,
) -> 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;
if let Some(image_data) = image_data {
self.inflater.finish_compressed_chunks(image_data)?;
}
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) => {
// If ignore_crc is set, do not calculate CRC. We set
// sum=val so that it short-circuits to true in the next
// if-statement block
let sum = if self.decode_options.ignore_crc {
val
} else {
self.current_chunk.crc.clone().finalize()
};
if val == sum || CHECKSUM_DISABLED {
// A fatal error in chunk parsing leaves the decoder in state 'None' to enforce
// that parsing can't continue after an error.
debug_assert!(self.state.is_none());
let decoded = self.parse_chunk(type_str)?;
if type_str != IEND {
self.state = Some(State::new_u32(U32ValueKind::Length));
}
Ok(decoded)
} else if self.decode_options.skip_ancillary_crc_failures
&& !chunk::is_critical(type_str)
{
// Ignore ancillary chunk with invalid CRC
self.state = Some(State::new_u32(U32ValueKind::Length));
Ok(Decoded::BadAncillaryChunk(type_str))
} 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;
// Should be verified by the FdatShorterThanFourBytes check earlier.
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::Nothing)
}
}
}
fn parse_chunk(&mut self, type_str: ChunkType) -> Result<Decoded, DecodingError> {
let mut parse_result = match type_str {
// Critical non-data chunks.
IHDR => self.parse_ihdr(),
chunk::PLTE => self.parse_plte(),
chunk::IEND => Ok(()), // TODO: Check chunk size.
// Data chunks handled separately.
chunk::IDAT => Ok(()),
chunk::fdAT => Ok(()),
// Recognized bounded-size ancillary chunks.
chunk::sBIT => self.parse_sbit(),
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 => self.parse_cicp(),
chunk::mDCV => self.parse_mdcv(),
chunk::cLLI => self.parse_clli(),
chunk::bKGD => self.parse_bkgd(),
// Ancillary chunks with unbounded size.
chunk::eXIf => self.parse_exif(), // TODO: allow skipping.
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(),
// Unrecognized chunks.
_ => {
if is_critical(type_str) {
return Err(DecodingError::Format(
FormatErrorInner::UnrecognizedCriticalChunk { type_str }.into(),
));
} else {
return Ok(Decoded::SkippedAncillaryChunk(type_str));
}
}
};
parse_result = parse_result.map_err(|e| {
match e {
// `parse_chunk` is invoked after gathering **all** bytes of a chunk, so
// `UnexpectedEof` from something like `read_be` is permanent and indicates an
// invalid PNG that should be represented as a `FormatError`, rather than as a
// (potentially recoverable) `IoError` / `UnexpectedEof`.
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,
}
});
match parse_result {
Ok(()) => Ok(Decoded::ChunkComplete(type_str)),
Err(DecodingError::Format(_))
if type_str != chunk::fcTL && !chunk::is_critical(type_str) =>
{
// Ignore benign errors in most auxiliary chunks. `LimitsExceeded`, `Parameter` and
// other error kinds are *not* treated as benign. We don't ignore errors in `fcTL`
// chunks because the fallback to the static/non-animated image has to be
// implemented *on top* of the `StreamingDecoder` API.
//
// TODO: Consider supporting a strict mode where even benign errors are reported up.
// See https://github.com/image-rs/image-png/pull/569#issuecomment-2642062285
Ok(Decoded::BadAncillaryChunk(type_str))
}
Err(e) => Err(e),
}
}
fn parse_fctl(&mut self) -> Result<(), DecodingError> {
let mut buf = &self.current_chunk.raw_bytes[..];
let next_seq_no = buf.read_be()?;
// Assuming that fcTL is required before *every* fdAT-sequence
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)?;
if !self.have_idat {
self.info.as_ref().unwrap().validate_default_image(&fc)?;
}
self.info.as_mut().unwrap().frame_control = Some(fc);
Ok(())
}
fn parse_actl(&mut self) -> Result<(), 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()?,
};
// The spec says that "0 is not a valid value" for `num_frames`.
// So let's ignore such malformed `acTL` chunks.
if actl.num_frames == 0 {
return Ok(());
}
self.info.as_mut().unwrap().animation_control = Some(actl);
Ok(())
}
}
fn parse_plte(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
if info.palette.is_some() {
// Only one palette is allowed
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(())
}
}
fn parse_sbit(&mut self) -> Result<(), DecodingError> {
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) };
// The sample depth for color type 3 is fixed at eight bits.
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();
// expected lenth of the chunk
let expected = match color_type {
ColorType::Grayscale => 1,
ColorType::Rgb | ColorType::Indexed => 3,
ColorType::GrayscaleAlpha => 2,
ColorType::Rgba => 4,
};
// Check if the sbit chunk size is valid.
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(())
}
fn parse_trns(&mut self) -> Result<(), 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(())
}
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(())
}
ColorType::Indexed => {
// The transparency chunk must be after the palette chunk and
// before the data chunk.
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(())
}
c => Err(DecodingError::Format(
FormatErrorInner::ColorWithBadTrns(c).into(),
)),
}
}
fn parse_phys(&mut self) -> Result<(), 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(())
}
}
fn parse_chrm(&mut self) -> Result<(), 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);
Ok(())
}
}
fn parse_gama(&mut self) -> Result<(), 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()?;
if source_gamma == 0 {
return Err(DecodingError::Format(
FormatErrorInner::BadGammaValue.into(),
));
}
let source_gamma = ScaledFloat::from_scaled(source_gamma);
info.gama_chunk = Some(source_gamma);
Ok(())
}
}
fn parse_srgb(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::sRGB }.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()?; // BE is is nonsense for single bytes, but this way the size is checked.
let rendering_intent = crate::SrgbRenderingIntent::from_raw(raw).ok_or_else(|| {
FormatError::from(FormatErrorInner::InvalidSrgbRenderingIntent(raw))
})?;
// Set srgb and override source gamma and chromaticities.
info.srgb = Some(rendering_intent);
Ok(())
}
}
fn parse_cicp(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
// The spec requires that the cICP chunk MUST come before the PLTE and IDAT chunks.
if info.coding_independent_code_points.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::cICP }.into(),
));
} else if info.palette.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::AfterPlte { kind: chunk::cICP }.into(),
));
} else if self.have_idat {
return Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::cICP }.into(),
));
}
let mut buf = &*self.current_chunk.raw_bytes;
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(DecodingError::IoError(
std::io::ErrorKind::InvalidData.into(),
));
}
}
};
// RGB is currently the only supported color model in PNG, and as
// such Matrix Coefficients shall be set to 0.
if matrix_coefficients != 0 {
return Err(DecodingError::IoError(
std::io::ErrorKind::InvalidData.into(),
));
}
if !buf.is_empty() {
return Err(DecodingError::IoError(
std::io::ErrorKind::InvalidData.into(),
));
}
info.coding_independent_code_points = Some(CodingIndependentCodePoints {
color_primaries,
transfer_function,
matrix_coefficients,
is_video_full_range_image,
});
Ok(())
}
fn parse_mdcv(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
// The spec requires that the mDCV chunk MUST come before the PLTE and IDAT chunks.
if info.mastering_display_color_volume.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::mDCV }.into(),
));
} else if info.palette.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::AfterPlte { kind: chunk::mDCV }.into(),
));
} else if self.have_idat {
return Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::mDCV }.into(),
));
}
let mut buf = &*self.current_chunk.raw_bytes;
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::SCALING` is hardcoded to 100_000, which works
// well for the `cHRM` chunk where the spec says that "a value
// of 0.3127 would be stored as the integer 31270". In the
// `mDCV` chunk the spec says that "0.708, 0.292)" is stored as
// "{ 35400, 14600 }", using a scaling factor of 50_000, so we
// multiply by 2 before converting.
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(DecodingError::IoError(
std::io::ErrorKind::InvalidData.into(),
));
}
info.mastering_display_color_volume = Some(MasteringDisplayColorVolume {
chromaticities,
max_luminance,
min_luminance,
});
Ok(())
}
fn parse_clli(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
if info.content_light_level.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::cLLI }.into(),
));
}
let mut buf = &*self.current_chunk.raw_bytes;
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(DecodingError::IoError(
std::io::ErrorKind::InvalidData.into(),
));
}
info.content_light_level = Some(ContentLightLevelInfo {
max_content_light_level,
max_frame_average_light_level,
});
Ok(())
}
fn parse_exif(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
if info.exif_metadata.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::eXIf }.into(),
));
}
info.exif_metadata = Some(self.current_chunk.raw_bytes.clone().into());
Ok(())
}
fn parse_iccp(&mut self) -> Result<(), DecodingError> {
if self.have_idat {
Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::iCCP }.into(),
))
} else if self.have_iccp {
Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::iCCP }.into(),
))
} else {
self.have_iccp = true;
let _ = self.parse_iccp_raw();
Ok(())
}
}
fn parse_iccp_raw(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
let mut buf = &self.current_chunk.raw_bytes[..];
// read profile name
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()? {
// compression method
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<(), 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()? {
// compression method
0u8 => (),
n => {
return Err(DecodingError::Format(
FormatErrorInner::UnknownCompressionMethod(n).into(),
))
}
}
match buf.read_be()? {
// filter method
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(),
))
}
};
self.info = Some(Info {
width,
height,
bit_depth,
color_type,
interlaced,
..Default::default()
});
Ok(())
}
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<(), 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(())
}
fn parse_ztxt(&mut self) -> Result<(), 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(())
}
fn parse_itxt(&mut self) -> Result<(), 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(())
}
fn parse_bkgd(&mut self) -> Result<(), DecodingError> {
let info = self.info.as_mut().unwrap();
if info.bkgd.is_some() {
// Only one bKGD chunk is allowed
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::bKGD }.into(),
));
} else if self.have_idat {
return Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::bKGD }.into(),
));
}
let expected = match info.color_type {
ColorType::Indexed => {
if info.palette.is_none() {
return Err(DecodingError::IoError(
std::io::ErrorKind::InvalidData.into(),
));
};
1
}
ColorType::Grayscale | ColorType::GrayscaleAlpha => 2,
ColorType::Rgb | ColorType::Rgba => 6,
};
let vec = self.current_chunk.raw_bytes.clone();
if vec.len() != expected {
return Err(DecodingError::Format(
FormatErrorInner::ChunkTooShort { kind: chunk::bKGD }.into(),
));
}
info.bkgd = Some(Cow::Owned(vec));
Ok(())
}
}
impl Info<'_> {
fn validate_default_image(&self, fc: &FrameControl) -> Result<(), DecodingError> {
// https://www.w3.org/TR/png-3/#fcTL-chunk says that:
//
// > The fcTL chunk corresponding to the default image, if it exists, has these
// > restrictions:
// >
// > * The x_offset and y_offset fields must be 0.
// > * The width and height fields must equal
// > the corresponding fields from the IHDR chunk.
if fc.x_offset != 0
|| fc.y_offset != 0
|| fc.width != self.width
|| fc.height != self.height
{
return Err(DecodingError::Format(
FormatErrorInner::BadSubFrameBounds {}.into(),
));
}
Ok(())
}
fn validate(&self, fc: &FrameControl) -> Result<(), DecodingError> {
if fc.width == 0 || fc.height == 0 {
return Err(DecodingError::Format(
FormatErrorInner::InvalidDimensions.into(),
));
}
// Validate mathematically: fc.width + fc.x_offset <= self.width
let in_x_bounds = Some(fc.width) <= self.width.checked_sub(fc.x_offset);
// Validate mathematically: fc.height + fc.y_offset <= self.height
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(
// TODO: do we want to display the bad bounds?
FormatErrorInner::BadSubFrameBounds {}.into(),
));
}
Ok(())
}
}
impl Default for StreamingDecoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::ScaledFloat;
use super::SourceChromaticities;
use crate::test_utils::*;
use crate::{Decoder, DecodingError, Reader, SrgbRenderingIntent, Unit};
use approx::assert_relative_eq;
use byteorder::WriteBytesExt;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fs::File;
use std::io::BufRead;
use std::io::Cursor;
use std::io::Seek;
use std::io::{BufReader, ErrorKind, Read, Write};
use std::rc::Rc;
#[test]
fn image_gamma() -> Result<(), ()> {
fn trial(path: &str, expected: Option<ScaledFloat>) {
let decoder = crate::Decoder::new(BufReader::new(File::open(path).unwrap()));
let reader = decoder.read_info().unwrap();
let actual: Option<ScaledFloat> = reader.info().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(BufReader::new(File::open(path).unwrap()));
let reader = decoder.read_info().unwrap();
let actual: Option<SourceChromaticities> = reader.info().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(BufReader::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 handling of a PNG file that contains *two* iCCP chunks.
/// This is a regression test for https://github.com/image-rs/image/issues/1825.
#[test]
fn test_two_iccp_chunks() {
// The test file has been taken from
// https://github.com/image-rs/image/issues/1825#issuecomment-1321798639,
// but the 2nd iCCP chunk has been altered manually (see the 2nd comment below for more
// details).
let decoder = crate::Decoder::new(BufReader::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 that the contents of the *first* iCCP chunk are returned.
//
// Note that the 2nd chunk in the test file has been manually altered to have a different
// content (`b"test iccp contents"`) which would have a different CRC (797351983).
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(Cursor::new(&encoded_image));
let dec = dec.read_info().unwrap();
assert_eq!(dummy_icc, &**dec.info().icc_profile.as_ref().unwrap());
}
#[test]
fn test_phys_roundtrip() {
let mut info = crate::Info::with_size(1, 1);
info.pixel_dims = Some(crate::PixelDimensions {
xppu: 12,
yppu: 34,
unit: Unit::Meter,
});
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(Cursor::new(&encoded_image));
let dec = dec.read_info().unwrap();
let phys = dec.info().pixel_dims.as_ref().unwrap();
assert_eq!(phys.xppu, 12);
assert_eq!(phys.yppu, 34);
assert_eq!(phys.unit, Unit::Meter);
}
#[test]
fn test_srgb_roundtrip() {
let mut info = crate::Info::with_size(1, 1);
info.srgb = Some(SrgbRenderingIntent::Saturation);
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(Cursor::new(&encoded_image));
let dec = dec.read_info().unwrap();
assert_eq!(dec.info().srgb.unwrap(), SrgbRenderingIntent::Saturation);
}
#[test]
fn test_png_with_broken_iccp() {
let decoder = crate::Decoder::new(BufReader::new(
File::open("tests/iccp/broken_iccp.png").unwrap(),
));
assert!(decoder.read_info().is_ok());
let mut decoder = crate::Decoder::new(BufReader::new(
File::open("tests/iccp/broken_iccp.png").unwrap(),
));
decoder.set_ignore_iccp_chunk(true);
assert!(decoder.read_info().is_ok());
}
/// Test handling of `cICP`, `mDCV`, and `cLLI` chunks.
#[test]
fn test_cicp_mdcv_and_clli_chunks() {
let mut decoder = crate::Decoder::new(BufReader::new(
File::open("tests/bugfixes/cicp_pq.png").unwrap(),
));
decoder.ignore_checksums(true);
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 handling of `eXIf` chunk.
#[test]
fn test_exif_chunk() {
let decoder = crate::Decoder::new(BufReader::new(
File::open("tests/bugfixes/F-exif-chunk-early.png").unwrap(),
));
let reader = decoder.read_info().unwrap();
let info = reader.info();
let exif = info.exif_metadata.as_ref().unwrap().as_ref();
assert_eq!(exif.len(), 90);
}
/// Tests what happens then [`Reader.finish`] is called twice.
#[test]
fn test_finishing_twice() {
let mut png = Vec::new();
write_noncompressed_png(&mut png, 16, 1024);
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
// First call to `finish` - expecting success.
reader.finish().unwrap();
// Second call to `finish` - expecting an error.
let err = reader.finish().unwrap_err();
assert!(matches!(&err, DecodingError::Parameter(_)));
assert_eq!("End of image has been reached", format!("{err}"));
}
/// Writes an acTL chunk.
/// See https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk
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);
}
/// Writes an fcTL chunk.
/// See https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
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);
}
/// Writes an fdAT chunk.
/// See https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk
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);
}
/// Writes PNG signature and chunks that can precede an fdAT chunk that is expected
/// to have
/// - `sequence_number` set to 0
/// - image data with rgba8 pixels in a `width` by `width` image
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(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size().unwrap()];
reader.next_frame(&mut buf).unwrap();
// 0-length fdAT should result in an error.
let err = reader.next_frame(&mut buf).unwrap_err();
assert!(matches!(&err, DecodingError::Format(_)));
assert_eq!("fdAT chunk shorter than 4 bytes", format!("{err}"));
// Calling `next_frame` again should return an error. Same error as above would be nice,
// but it is probably unnecessary and infeasible (`DecodingError` can't derive `Clone`
// because `std::io::Error` doesn't implement `Clone`).. But it definitely shouldn't enter
// an infinite loop.
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(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size().unwrap()];
reader.next_frame(&mut buf).unwrap();
// 3-bytes-long fdAT should result in an error.
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() {
// Generate test data where the 2nd animation frame is split across 2 fdAT chunks.
//
// This is similar to the example given in
// https://wiki.mozilla.org/APNG_Specification#Chunk_Sequence_Numbers:
//
// ```
// Sequence number Chunk
// (none) `acTL`
// 0 `fcTL` first frame
// (none) `IDAT` first frame / default image
// 1 `fcTL` second frame
// 2 first `fdAT` for second frame
// 3 second `fdAT` for second frame
// ```
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
};
// Start decoding.
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size().unwrap()];
let Some(animation_control) = reader.info().animation_control else {
panic!("No acTL");
};
assert_eq!(animation_control.num_frames, 2);
// Process the 1st animation frame.
let first_frame: Vec<u8>;
{
reader.next_frame(&mut buf).unwrap();
first_frame = buf.clone();
// Note that the doc comment of `Reader::next_frame` says that "[...]
// can be checked afterwards by calling `info` **after** a successful call and
// inspecting the `frame_control` data.". (Note the **emphasis** on "after".)
let Some(frame_control) = reader.info().frame_control else {
panic!("No fcTL (1st frame)");
};
// The sequence number is taken from the `fcTL` chunk that comes before the `IDAT`
// chunk.
assert_eq!(frame_control.sequence_number, 0);
}
// Process the 2nd animation frame.
let second_frame: Vec<u8>;
{
reader.next_frame(&mut buf).unwrap();
second_frame = buf.clone();
// Same as above - updated `frame_control` is available *after* the `next_frame` call.
let Some(frame_control) = reader.info().frame_control else {
panic!("No fcTL (2nd frame)");
};
// The sequence number is taken from the `fcTL` chunk that comes before the two `fdAT`
// chunks. Note that sequence numbers inside `fdAT` chunks are not publicly exposed
// (but they are still checked when decoding to verify that they are sequential).
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);
// Here we want to test an invalid image where the `IDAT` chunk contains more data
// (data for 8x256 image) than declared in the `IHDR` chunk (which only describes an
// 8x8 image).
write_chunk(
&mut png,
b"IDAT",
&generate_rgba8_with_width_and_height(8, 256),
);
write_iend(&mut png);
png
};
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size().unwrap()];
// TODO: Should this return an error instead? For now let's just have test assertions for
// the current behavior.
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(Cursor::new(&png));
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}"),
);
}
/// `StreamingInput` can be used by tests to simulate a streaming input
/// (e.g. a slow http response, where all bytes are not immediately available).
#[derive(Clone)]
struct StreamingInput {
full_input: Vec<u8>,
state: Rc<RefCell<StreamingInputState>>,
}
struct StreamingInputState {
current_pos: usize,
available_len: usize,
}
impl StreamingInput {
fn new(full_input: Vec<u8>) -> Self {
Self {
full_input,
state: Rc::new(RefCell::new(StreamingInputState {
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.state.borrow_mut();
assert!(state.available_len < self.full_input.len());
state.available_len += 1;
}
fn stream_input_until_reader_is_available(&self) -> Reader<StreamingInput> {
loop {
self.state.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<Cursor<&[u8]>>) -> R,
{
let decoder = Decoder::new(Cursor::new(&*self.full_input));
f(decoder.read_info().unwrap())
}
}
impl Read for StreamingInput {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let mut state = self.state.borrow_mut();
let mut available_bytes = &self.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)
}
}
impl BufRead for StreamingInput {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
let state = self.state.borrow();
Ok(&self.full_input[state.current_pos..state.available_len])
}
fn consume(&mut self, amt: usize) {
let mut state = self.state.borrow_mut();
state.current_pos += amt;
assert!(state.current_pos <= state.available_len);
}
}
impl Seek for StreamingInput {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
let mut state = self.state.borrow_mut();
state.current_pos = match pos {
std::io::SeekFrom::Start(n) => n as usize,
std::io::SeekFrom::End(n) => (self.full_input.len() as i64 + n) as usize,
std::io::SeekFrom::Current(n) => (state.current_pos as i64 + n) as usize,
} as usize;
Ok(state.current_pos as u64)
}
fn stream_position(&mut self) -> std::io::Result<u64> {
Ok(self.state.borrow().current_pos as u64)
}
}
/// Test resuming/retrying `Reader.next_frame` after `UnexpectedEof`.
#[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().unwrap()];
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().unwrap()];
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 resuming/retrying `Reader.next_row` after `UnexpectedEof`.
#[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().unwrap()];
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 resuming/retrying `Decoder.read_header_info` after `UnexpectedEof`.
#[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
);
}
/// Creates a ready-to-test [`Reader`] which decodes a PNG that contains:
/// IHDR, IDAT, IEND.
fn create_reader_of_ihdr_idat() -> Reader<Cursor<Vec<u8>>> {
let mut png = Vec::new();
write_noncompressed_png(&mut png, /* width = */ 16, /* idat_size = */ 1024);
Decoder::new(Cursor::new(png)).read_info().unwrap()
}
/// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains:
/// IHDR, acTL, fcTL, IDAT, fcTL, fdAT, IEND. (i.e. IDAT is part of the animation)
fn create_reader_of_ihdr_actl_fctl_idat_fctl_fdat() -> Reader<Cursor<Vec<u8>>> {
let width = 16;
let mut fctl = crate::FrameControl {
width,
height: width,
..Default::default()
};
let mut png = Vec::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,
},
);
fctl.sequence_number = 0;
write_fctl(&mut png, &fctl);
// Using `fctl.height + 1` means that the `IDAT` will have "left-over" data after
// processing. This helps to verify that `Reader.read_until_image_data` discards the
// left-over data when resetting `UnfilteredRowsBuffer`.
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;
// Using different width in `IDAT` and `fDAT` frames helps to catch problems that
// may arise when `Reader.read_until_image_data` doesn't properly reset
// `UnfilteredRowsBuffer`.
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(Cursor::new(png)).read_info().unwrap()
}
/// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains: IHDR, acTL,
/// IDAT, fcTL, fdAT, fcTL, fdAT, IEND. (i.e. IDAT is *not* part of the animation)
fn create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat() -> Reader<Cursor<Vec<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 = Vec::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(Cursor::new(png)).read_info().unwrap()
}
fn get_fctl_sequence_number(reader: &Reader<impl BufRead + Seek>) -> u32 {
reader
.info()
.frame_control
.as_ref()
.unwrap()
.sequence_number
}
/// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called
/// after already decoding a single frame in a non-animated PNG.
#[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().unwrap()];
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,
);
}
/// Tests that [`Reader.next_frame_info`] will report a `PolledAfterEndOfImage` error when
/// called when decoding a PNG that only contains a single frame.
#[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,
);
}
/// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called
/// after already decoding a single frame in an animated PNG where IDAT is part of the
/// animation.
#[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().unwrap()];
assert_eq!(get_fctl_sequence_number(&reader), 0);
reader
.next_frame(&mut buf)
.expect("Expecting no error for IDAT frame");
// `next_frame` doesn't advance to the next `fcTL`.
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,
);
}
/// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called
/// after already decoding a single frame in an animated PNG where IDAT is *not* part of the
/// animation.
#[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().unwrap()];
assert!(reader.info().frame_control.is_none());
reader
.next_frame(&mut buf)
.expect("Expecting no error for IDAT frame");
// `next_frame` doesn't advance to the next `fcTL`.
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,
);
}
/// Tests that after decoding a whole frame via [`Reader.next_row`] the call to
/// [`Reader.next_frame`] will decode the **next** frame.
#[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().unwrap()];
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");
// Verify if we have read the next `fcTL` chunk + repopulated `buf`:
assert_eq!(get_fctl_sequence_number(&reader), 1);
assert!(buf.iter().any(|byte| *byte != 0x0f));
}
/// Tests that after decoding a whole frame via [`Reader.next_row`] it is possible
/// to use [`Reader.next_row`] to decode the next frame (by using the `next_frame_info` API to
/// advance to the next frame when `next_row` returns `None`).
#[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,
);
}
/// This test is similar to `test_next_frame_polling_after_end_idat_part_of_animation`, but it
/// uses `next_frame_info` calls to read to the next `fcTL` earlier - before the next call to
/// `next_frame` (knowing `fcTL` before calling `next_frame` may be helpful to determine the
/// size of the output buffer and/or to prepare the buffer based on the `DisposeOp` of the
/// previous frames).
#[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().unwrap()];
assert_eq!(get_fctl_sequence_number(&reader), 0);
reader
.next_frame(&mut buf)
.expect("Expecting no error for IDAT frame");
// `next_frame` doesn't advance to the next `fcTL`.
assert_eq!(get_fctl_sequence_number(&reader), 0);
// But `next_frame_info` can be used to go to the next `fcTL`.
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,
);
}
/// This test is similar to `test_next_frame_polling_after_end_idat_not_part_of_animation`, but
/// it uses `next_frame_info` to skip the `IDAT` frame entirely + to move between frames.
#[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().unwrap()];
// First (IDAT) frame doesn't have frame control info, which means
// that it is not part of the animation.
assert!(reader.info().frame_control.is_none());
// `next_frame_info` can be used to skip the IDAT frame (without first having to separately
// discard the image data - e.g. by also calling `next_frame` first).
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);
// Get the `fcTL` for the 2nd frame.
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,
);
}
#[test]
fn test_incorrect_trns_chunk_is_ignored() {
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"tRNS", &[12, 34, 56]);
write_chunk(
&mut png,
b"IDAT",
&generate_rgba8_with_width_and_height(8, 8),
);
write_iend(&mut png);
png
};
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size().unwrap()];
assert!(reader.info().trns.is_none());
reader.next_frame(&mut buf).unwrap();
assert_eq!(3093270825, crc32fast::hash(&buf));
assert!(reader.info().trns.is_none());
}
/// This is a regression test for https://crbug.com/422421347
#[test]
fn test_actl_num_frames_zero() {
let width = 16;
let frame_data = generate_rgba8_with_width_and_height(width, width);
let mut png = Vec::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, width);
write_actl(
&mut png,
&crate::AnimationControl {
num_frames: 0, // <= spec violation needed by this test
num_plays: 0,
},
);
// Presence of an `fcTL` chunk will prevent incrementing
// `num_frames` when calculating `remaining_frames` in
// `Decoder::read_info`. So the test writes an `fcTL` chunk
// to end up with `remaining_frames == 0` if `parse_actl` allows
// `num_frames == 0`.
write_fctl(
&mut png,
&crate::FrameControl {
width,
height: width,
..Default::default()
},
);
write_chunk(&mut png, b"IDAT", &frame_data);
write_iend(&mut png);
let mut reader = Decoder::new(Cursor::new(png)).read_info().unwrap();
// Using `next_interlaced_row` in the test, because it doesn't check
// `Reader::remaining_frames` (unlike `next_frame`), because it assumes that either
// `read_info` or `next_frame` leave `Reader` in a valid state.
//
// The test passes if these `next_interlaced_row` calls don't hit any `assert!` failures.
while let Some(_row) = reader.next_interlaced_row().unwrap() {}
}
#[test]
fn test_small_fctl() {
const FCTL_SIZE: u32 = 30;
const IHDR_SIZE: u32 = 50;
let mut png = Vec::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, IHDR_SIZE);
write_actl(
&mut png,
&crate::AnimationControl {
num_frames: 1,
num_plays: 1,
},
);
write_fctl(
&mut png,
&crate::FrameControl {
width: FCTL_SIZE,
height: FCTL_SIZE,
x_offset: 10,
y_offset: 10,
sequence_number: 0,
..Default::default()
},
);
write_chunk(
&mut png,
b"IDAT",
&generate_rgba8_with_width_and_height(IHDR_SIZE, IHDR_SIZE),
);
write_iend(&mut png);
let reader = Decoder::new(Cursor::new(png)).read_info();
let err = reader.err().unwrap();
assert!(matches!(&err, DecodingError::Format(_)));
assert_eq!("Sub frame is out-of-bounds.", format!("{err}"));
}
#[test]
fn test_invalid_text_chunk() {
// The spec requires a NUL character (separating keyword from text) within the first 80
// bytes of the chunk. Here there is no NUL character in the first 100 bytes, so this
// chunk is invalid and should trigger an error in `parse_text`.
let invalid_text_chunk = vec![b'A'; 100];
const SIZE: u32 = 20;
let mut png = Vec::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, SIZE);
write_chunk(&mut png, b"tEXt", invalid_text_chunk.as_slice());
write_chunk(
&mut png,
b"IDAT",
&generate_rgba8_with_width_and_height(SIZE, SIZE),
);
write_iend(&mut png);
let reader = Decoder::new(Cursor::new(png)).read_info().unwrap();
let info = reader.info();
assert_eq!(info.width, SIZE);
assert_eq!(info.uncompressed_latin1_text.len(), 0);
}
}