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