aboutsummaryrefslogtreecommitdiff
path: root/vendor/png/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/png/src/decoder')
-rw-r--r--vendor/png/src/decoder/mod.rs961
-rw-r--r--vendor/png/src/decoder/stream.rs1576
-rw-r--r--vendor/png/src/decoder/zlib.rs212
3 files changed, 2749 insertions, 0 deletions
diff --git a/vendor/png/src/decoder/mod.rs b/vendor/png/src/decoder/mod.rs
new file mode 100644
index 0000000..09772fe
--- /dev/null
+++ b/vendor/png/src/decoder/mod.rs
@@ -0,0 +1,961 @@
+mod stream;
+mod zlib;
+
+pub use self::stream::{DecodeOptions, Decoded, DecodingError, StreamingDecoder};
+use self::stream::{FormatErrorInner, CHUNCK_BUFFER_SIZE};
+
+use std::io::{BufRead, BufReader, Read};
+use std::mem;
+use std::ops::Range;
+
+use crate::chunk;
+use crate::common::{
+ BitDepth, BytesPerPixel, ColorType, Info, ParameterErrorKind, Transformations,
+};
+use crate::filter::{unfilter, FilterType};
+use crate::utils;
+
+/*
+pub enum InterlaceHandling {
+ /// Outputs the raw rows
+ RawRows,
+ /// Fill missing the pixels from the existing ones
+ Rectangle,
+ /// Only fill the needed pixels
+ Sparkle
+}
+*/
+
+/// Output info.
+///
+/// This describes one particular frame of the image that was written into the output buffer.
+#[derive(Debug, PartialEq, Eq)]
+pub struct OutputInfo {
+ /// The pixel width of this frame.
+ pub width: u32,
+ /// The pixel height of this frame.
+ pub height: u32,
+ /// The chosen output color type.
+ pub color_type: ColorType,
+ /// The chosen output bit depth.
+ pub bit_depth: BitDepth,
+ /// The byte count of each scan line in the image.
+ pub line_size: usize,
+}
+
+impl OutputInfo {
+ /// Returns the size needed to hold a decoded frame
+ /// If the output buffer was larger then bytes after this count should be ignored. They may
+ /// still have been changed.
+ pub fn buffer_size(&self) -> usize {
+ self.line_size * self.height as usize
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+/// Limits on the resources the `Decoder` is allowed too use
+pub struct Limits {
+ /// maximum number of bytes the decoder is allowed to allocate, default is 64Mib
+ pub bytes: usize,
+}
+
+impl Default for Limits {
+ fn default() -> Limits {
+ Limits {
+ bytes: 1024 * 1024 * 64,
+ }
+ }
+}
+
+/// PNG Decoder
+pub struct Decoder<R: Read> {
+ read_decoder: ReadDecoder<R>,
+ /// Output transformations
+ transform: Transformations,
+ /// Limits on resources the Decoder is allowed to use
+ limits: Limits,
+}
+
+/// A row of data with interlace information attached.
+#[derive(Clone, Copy, Debug)]
+pub struct InterlacedRow<'data> {
+ data: &'data [u8],
+ interlace: InterlaceInfo,
+}
+
+impl<'data> InterlacedRow<'data> {
+ pub fn data(&self) -> &'data [u8] {
+ self.data
+ }
+
+ pub fn interlace(&self) -> InterlaceInfo {
+ self.interlace
+ }
+}
+
+/// PNG (2003) specifies two interlace modes, but reserves future extensions.
+#[derive(Clone, Copy, Debug)]
+pub enum InterlaceInfo {
+ /// the null method means no interlacing
+ Null,
+ /// Adam7 derives its name from doing 7 passes over the image, only decoding a subset of all pixels in each pass.
+ /// The following table shows pictorially what parts of each 8x8 area of the image is found in each pass:
+ ///
+ /// 1 6 4 6 2 6 4 6
+ /// 7 7 7 7 7 7 7 7
+ /// 5 6 5 6 5 6 5 6
+ /// 7 7 7 7 7 7 7 7
+ /// 3 6 4 6 3 6 4 6
+ /// 7 7 7 7 7 7 7 7
+ /// 5 6 5 6 5 6 5 6
+ /// 7 7 7 7 7 7 7 7
+ Adam7 { pass: u8, line: u32, width: u32 },
+}
+
+/// A row of data without interlace information.
+#[derive(Clone, Copy, Debug)]
+pub struct Row<'data> {
+ data: &'data [u8],
+}
+
+impl<'data> Row<'data> {
+ pub fn data(&self) -> &'data [u8] {
+ self.data
+ }
+}
+
+impl<R: Read> Decoder<R> {
+ /// Create a new decoder configuration with default limits.
+ pub fn new(r: R) -> Decoder<R> {
+ Decoder::new_with_limits(r, Limits::default())
+ }
+
+ /// Create a new decoder configuration with custom limits.
+ pub fn new_with_limits(r: R, limits: Limits) -> Decoder<R> {
+ Decoder {
+ read_decoder: ReadDecoder {
+ reader: BufReader::with_capacity(CHUNCK_BUFFER_SIZE, r),
+ decoder: StreamingDecoder::new(),
+ at_eof: false,
+ },
+ transform: Transformations::IDENTITY,
+ limits,
+ }
+ }
+
+ /// Create a new decoder configuration with custom `DecodeOptions`.
+ pub fn new_with_options(r: R, decode_options: DecodeOptions) -> Decoder<R> {
+ Decoder {
+ read_decoder: ReadDecoder {
+ reader: BufReader::with_capacity(CHUNCK_BUFFER_SIZE, r),
+ decoder: StreamingDecoder::new_with_options(decode_options),
+ at_eof: false,
+ },
+ transform: Transformations::IDENTITY,
+ limits: Limits::default(),
+ }
+ }
+
+ /// Limit resource usage.
+ ///
+ /// Note that your allocations, e.g. when reading into a pre-allocated buffer, are __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.
+ ///
+ /// ```
+ /// use std::fs::File;
+ /// use png::{Decoder, Limits};
+ /// // This image is 32×32, 1bit per pixel. The reader buffers one row which requires 4 bytes.
+ /// let mut limits = Limits::default();
+ /// limits.bytes = 3;
+ /// let mut decoder = Decoder::new_with_limits(File::open("tests/pngsuite/basi0g01.png").unwrap(), limits);
+ /// assert!(decoder.read_info().is_err());
+ ///
+ /// // This image is 32x32 pixels, so the decoder will allocate less than 10Kib
+ /// let mut limits = Limits::default();
+ /// limits.bytes = 10*1024;
+ /// let mut decoder = Decoder::new_with_limits(File::open("tests/pngsuite/basi0g01.png").unwrap(), limits);
+ /// assert!(decoder.read_info().is_ok());
+ /// ```
+ pub fn set_limits(&mut self, limits: Limits) {
+ self.limits = limits;
+ }
+
+ /// Read the PNG header and return the information contained within.
+ ///
+ /// Most image metadata will not be read until `read_info` is called, so those fields will be
+ /// None or empty.
+ pub fn read_header_info(&mut self) -> Result<&Info, DecodingError> {
+ let mut buf = Vec::new();
+ while self.read_decoder.info().is_none() {
+ buf.clear();
+ if self.read_decoder.decode_next(&mut buf)?.is_none() {
+ return Err(DecodingError::Format(
+ FormatErrorInner::UnexpectedEof.into(),
+ ));
+ }
+ }
+ Ok(self.read_decoder.info().unwrap())
+ }
+
+ /// Reads all meta data until the first IDAT chunk
+ pub fn read_info(mut self) -> Result<Reader<R>, DecodingError> {
+ self.read_header_info()?;
+
+ let mut reader = Reader {
+ decoder: self.read_decoder,
+ bpp: BytesPerPixel::One,
+ subframe: SubframeInfo::not_yet_init(),
+ fctl_read: 0,
+ next_frame: SubframeIdx::Initial,
+ prev: Vec::new(),
+ current: Vec::new(),
+ scan_start: 0,
+ transform: self.transform,
+ scratch_buffer: Vec::new(),
+ limits: self.limits,
+ };
+
+ // Check if the decoding buffer of a single raw line has a valid size.
+ if reader.info().checked_raw_row_length().is_none() {
+ return Err(DecodingError::LimitsExceeded);
+ }
+
+ // Check if the output buffer has a valid size.
+ let (width, height) = reader.info().size();
+ let (color, depth) = reader.output_color_type();
+ let rowlen = color
+ .checked_raw_row_length(depth, width)
+ .ok_or(DecodingError::LimitsExceeded)?
+ - 1;
+ let height: usize =
+ std::convert::TryFrom::try_from(height).map_err(|_| DecodingError::LimitsExceeded)?;
+ if rowlen.checked_mul(height).is_none() {
+ return Err(DecodingError::LimitsExceeded);
+ }
+
+ reader.read_until_image_data()?;
+ Ok(reader)
+ }
+
+ /// Set the allowed and performed transformations.
+ ///
+ /// A transformation is a pre-processing on the raw image data modifying content or encoding.
+ /// Many options have an impact on memory or CPU usage during decoding.
+ pub fn set_transformations(&mut self, transform: Transformations) {
+ self.transform = transform;
+ }
+
+ /// Set the decoder to ignore all text chunks while parsing.
+ ///
+ /// eg.
+ /// ```
+ /// use std::fs::File;
+ /// use png::Decoder;
+ /// let mut decoder = Decoder::new(File::open("tests/pngsuite/basi0g01.png").unwrap());
+ /// decoder.set_ignore_text_chunk(true);
+ /// assert!(decoder.read_info().is_ok());
+ /// ```
+ pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) {
+ self.read_decoder
+ .decoder
+ .set_ignore_text_chunk(ignore_text_chunk);
+ }
+
+ /// Set the decoder to ignore and not verify the Adler-32 checksum
+ /// and CRC code.
+ pub fn ignore_checksums(&mut self, ignore_checksums: bool) {
+ self.read_decoder
+ .decoder
+ .set_ignore_adler32(ignore_checksums);
+ self.read_decoder.decoder.set_ignore_crc(ignore_checksums);
+ }
+}
+
+struct ReadDecoder<R: Read> {
+ reader: BufReader<R>,
+ decoder: StreamingDecoder,
+ at_eof: bool,
+}
+
+impl<R: Read> ReadDecoder<R> {
+ /// Returns the next decoded chunk. If the chunk is an ImageData chunk, its contents are written
+ /// into image_data.
+ fn decode_next(&mut self, image_data: &mut Vec<u8>) -> Result<Option<Decoded>, DecodingError> {
+ while !self.at_eof {
+ let (consumed, result) = {
+ let buf = self.reader.fill_buf()?;
+ if buf.is_empty() {
+ return Err(DecodingError::Format(
+ FormatErrorInner::UnexpectedEof.into(),
+ ));
+ }
+ self.decoder.update(buf, image_data)?
+ };
+ self.reader.consume(consumed);
+ match result {
+ Decoded::Nothing => (),
+ Decoded::ImageEnd => self.at_eof = true,
+ result => return Ok(Some(result)),
+ }
+ }
+ Ok(None)
+ }
+
+ fn finish_decoding(&mut self) -> Result<(), DecodingError> {
+ while !self.at_eof {
+ let buf = self.reader.fill_buf()?;
+ if buf.is_empty() {
+ return Err(DecodingError::Format(
+ FormatErrorInner::UnexpectedEof.into(),
+ ));
+ }
+ let (consumed, event) = self.decoder.update(buf, &mut vec![])?;
+ self.reader.consume(consumed);
+ match event {
+ Decoded::Nothing => (),
+ Decoded::ImageEnd => self.at_eof = true,
+ // ignore more data
+ Decoded::ChunkComplete(_, _) | Decoded::ChunkBegin(_, _) | Decoded::ImageData => {}
+ Decoded::ImageDataFlushed => return Ok(()),
+ Decoded::PartialChunk(_) => {}
+ new => unreachable!("{:?}", new),
+ }
+ }
+
+ Err(DecodingError::Format(
+ FormatErrorInner::UnexpectedEof.into(),
+ ))
+ }
+
+ fn info(&self) -> Option<&Info> {
+ self.decoder.info.as_ref()
+ }
+}
+
+/// PNG reader (mostly high-level interface)
+///
+/// Provides a high level that iterates over lines or whole images.
+pub struct Reader<R: Read> {
+ decoder: ReadDecoder<R>,
+ bpp: BytesPerPixel,
+ subframe: SubframeInfo,
+ /// Number of frame control chunks read.
+ /// By the APNG specification the total number must equal the count specified in the animation
+ /// control chunk. The IDAT image _may_ have such a chunk applying to it.
+ fctl_read: u32,
+ next_frame: SubframeIdx,
+ /// Previous raw line
+ prev: Vec<u8>,
+ /// Current raw line
+ current: Vec<u8>,
+ /// Start index of the current scan line.
+ scan_start: usize,
+ /// Output transformations
+ transform: Transformations,
+ /// This buffer is only used so that `next_row` and `next_interlaced_row` can return reference
+ /// to a byte slice. In a future version of this library, this buffer will be removed and
+ /// `next_row` and `next_interlaced_row` will write directly into a user provided output buffer.
+ scratch_buffer: Vec<u8>,
+ /// How resources we can spend (for example, on allocation).
+ limits: Limits,
+}
+
+/// The subframe specific information.
+///
+/// In APNG the frames are constructed by combining previous frame and a new subframe (through a
+/// combination of `dispose_op` and `overlay_op`). These sub frames specify individual dimension
+/// information and reuse the global interlace options. This struct encapsulates the state of where
+/// in a particular IDAT-frame or subframe we are.
+struct SubframeInfo {
+ width: u32,
+ height: u32,
+ rowlen: usize,
+ interlace: InterlaceIter,
+ consumed_and_flushed: bool,
+}
+
+#[derive(Clone)]
+enum InterlaceIter {
+ None(Range<u32>),
+ Adam7(utils::Adam7Iterator),
+}
+
+/// Denote a frame as given by sequence numbers.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+enum SubframeIdx {
+ /// The initial frame in an IDAT chunk without fcTL chunk applying to it.
+ /// Note that this variant precedes `Some` as IDAT frames precede fdAT frames and all fdAT
+ /// frames must have a fcTL applying to it.
+ Initial,
+ /// An IDAT frame with fcTL or an fdAT frame.
+ Some(u32),
+ /// The past-the-end index.
+ End,
+}
+
+impl<R: Read> Reader<R> {
+ /// Reads all meta data until the next frame data starts.
+ /// Requires IHDR before the IDAT and fcTL before fdAT.
+ fn read_until_image_data(&mut self) -> Result<(), DecodingError> {
+ loop {
+ // This is somewhat ugly. The API requires us to pass a buffer to decode_next but we
+ // know that we will stop before reading any image data from the stream. Thus pass an
+ // empty buffer and assert that remains empty.
+ let mut buf = Vec::new();
+ let state = self.decoder.decode_next(&mut buf)?;
+ assert!(buf.is_empty());
+
+ match state {
+ Some(Decoded::ChunkBegin(_, chunk::IDAT))
+ | Some(Decoded::ChunkBegin(_, chunk::fdAT)) => break,
+ Some(Decoded::FrameControl(_)) => {
+ self.subframe = SubframeInfo::new(self.info());
+ // The next frame is the one to which this chunk applies.
+ self.next_frame = SubframeIdx::Some(self.fctl_read);
+ // TODO: what about overflow here? That would imply there are more fctl chunks
+ // than can be specified in the animation control but also that we have read
+ // several gigabytes of data.
+ self.fctl_read += 1;
+ }
+ None => {
+ return Err(DecodingError::Format(
+ FormatErrorInner::MissingImageData.into(),
+ ))
+ }
+ // Ignore all other chunk events. Any other chunk may be between IDAT chunks, fdAT
+ // chunks and their control chunks.
+ _ => {}
+ }
+ }
+
+ let info = self
+ .decoder
+ .info()
+ .ok_or(DecodingError::Format(FormatErrorInner::MissingIhdr.into()))?;
+ self.bpp = info.bpp_in_prediction();
+ self.subframe = SubframeInfo::new(info);
+
+ // Allocate output buffer.
+ let buflen = self.output_line_size(self.subframe.width);
+ if buflen > self.limits.bytes {
+ return Err(DecodingError::LimitsExceeded);
+ }
+
+ self.prev.clear();
+ self.prev.resize(self.subframe.rowlen, 0);
+
+ Ok(())
+ }
+
+ /// Get information on the image.
+ ///
+ /// The structure will change as new frames of an animated image are decoded.
+ pub fn info(&self) -> &Info {
+ self.decoder.info().unwrap()
+ }
+
+ /// Decodes the next frame into `buf`.
+ ///
+ /// Note that this decodes raw subframes that need to be mixed according to blend-op and
+ /// dispose-op by the caller.
+ ///
+ /// The caller must always provide a buffer large enough to hold a complete frame (the APNG
+ /// specification restricts subframes to the dimensions given in the image header). The region
+ /// that has been written be checked afterwards by calling `info` after a successful call and
+ /// inspecting the `frame_control` data. This requirement may be lifted in a later version of
+ /// `png`.
+ ///
+ /// Output lines will be written in row-major, packed matrix with width and height of the read
+ /// frame (or subframe), all samples are in big endian byte order where this matters.
+ pub fn next_frame(&mut self, buf: &mut [u8]) -> Result<OutputInfo, DecodingError> {
+ let subframe_idx = match self.decoder.info().unwrap().frame_control() {
+ None => SubframeIdx::Initial,
+ Some(_) => SubframeIdx::Some(self.fctl_read - 1),
+ };
+
+ if self.next_frame == SubframeIdx::End {
+ return Err(DecodingError::Parameter(
+ ParameterErrorKind::PolledAfterEndOfImage.into(),
+ ));
+ } else if self.next_frame != subframe_idx {
+ // Advance until we've read the info / fcTL for this frame.
+ self.read_until_image_data()?;
+ }
+
+ if buf.len() < self.output_buffer_size() {
+ return Err(DecodingError::Parameter(
+ ParameterErrorKind::ImageBufferSize {
+ expected: buf.len(),
+ actual: self.output_buffer_size(),
+ }
+ .into(),
+ ));
+ }
+
+ let (color_type, bit_depth) = self.output_color_type();
+ let output_info = OutputInfo {
+ width: self.subframe.width,
+ height: self.subframe.height,
+ color_type,
+ bit_depth,
+ line_size: self.output_line_size(self.subframe.width),
+ };
+
+ self.current.clear();
+ self.scan_start = 0;
+ let width = self.info().width;
+ if self.info().interlaced {
+ while let Some(InterlacedRow {
+ data: row,
+ interlace,
+ ..
+ }) = self.next_interlaced_row()?
+ {
+ let (line, pass) = match interlace {
+ InterlaceInfo::Adam7 { line, pass, .. } => (line, pass),
+ InterlaceInfo::Null => unreachable!("expected interlace information"),
+ };
+ let samples = color_type.samples() as u8;
+ utils::expand_pass(buf, width, row, pass, line, samples * (bit_depth as u8));
+ }
+ } else {
+ for row in buf
+ .chunks_exact_mut(output_info.line_size)
+ .take(self.subframe.height as usize)
+ {
+ self.next_interlaced_row_impl(self.subframe.rowlen, row)?;
+ }
+ }
+
+ // Advance over the rest of data for this (sub-)frame.
+ if !self.subframe.consumed_and_flushed {
+ self.decoder.finish_decoding()?;
+ }
+
+ // Advance our state to expect the next frame.
+ let past_end_subframe = self
+ .info()
+ .animation_control()
+ .map(|ac| ac.num_frames)
+ .unwrap_or(0);
+ self.next_frame = match self.next_frame {
+ SubframeIdx::End => unreachable!("Next frame called when already at image end"),
+ // Reached the end of non-animated image.
+ SubframeIdx::Initial if past_end_subframe == 0 => SubframeIdx::End,
+ // An animated image, expecting first subframe.
+ SubframeIdx::Initial => SubframeIdx::Some(0),
+ // This was the last subframe, slightly fuzzy condition in case of programmer error.
+ SubframeIdx::Some(idx) if past_end_subframe <= idx + 1 => SubframeIdx::End,
+ // Expecting next subframe.
+ SubframeIdx::Some(idx) => SubframeIdx::Some(idx + 1),
+ };
+
+ Ok(output_info)
+ }
+
+ /// Returns the next processed row of the image
+ pub fn next_row(&mut self) -> Result<Option<Row>, DecodingError> {
+ self.next_interlaced_row()
+ .map(|v| v.map(|v| Row { data: v.data }))
+ }
+
+ /// Returns the next processed row of the image
+ pub fn next_interlaced_row(&mut self) -> Result<Option<InterlacedRow>, DecodingError> {
+ let (rowlen, interlace) = match self.next_pass() {
+ Some((rowlen, interlace)) => (rowlen, interlace),
+ None => return Ok(None),
+ };
+
+ let width = if let InterlaceInfo::Adam7 { width, .. } = interlace {
+ width
+ } else {
+ self.subframe.width
+ };
+ let output_line_size = self.output_line_size(width);
+
+ // TODO: change the interface of `next_interlaced_row` to take an output buffer instead of
+ // making us return a reference to a buffer that we own.
+ let mut output_buffer = mem::take(&mut self.scratch_buffer);
+ output_buffer.resize(output_line_size, 0u8);
+ let ret = self.next_interlaced_row_impl(rowlen, &mut output_buffer);
+ self.scratch_buffer = output_buffer;
+ ret?;
+
+ Ok(Some(InterlacedRow {
+ data: &self.scratch_buffer[..output_line_size],
+ interlace,
+ }))
+ }
+
+ /// Fetch the next interlaced row and filter it according to our own transformations.
+ fn next_interlaced_row_impl(
+ &mut self,
+ rowlen: usize,
+ output_buffer: &mut [u8],
+ ) -> Result<(), DecodingError> {
+ self.next_raw_interlaced_row(rowlen)?;
+ let row = &self.prev[1..rowlen];
+
+ // Apply transformations and write resulting data to buffer.
+ let (color_type, bit_depth, trns) = {
+ let info = self.info();
+ (
+ info.color_type,
+ info.bit_depth as u8,
+ info.trns.is_some() || self.transform.contains(Transformations::ALPHA),
+ )
+ };
+ let expand = self.transform.contains(Transformations::EXPAND)
+ || self.transform.contains(Transformations::ALPHA);
+ let strip16 = bit_depth == 16 && self.transform.contains(Transformations::STRIP_16);
+ let info = self.decoder.info().unwrap();
+ let trns = if trns {
+ Some(info.trns.as_deref())
+ } else {
+ None
+ };
+ match (color_type, trns) {
+ (ColorType::Indexed, _) if expand => {
+ output_buffer[..row.len()].copy_from_slice(row);
+ expand_paletted(output_buffer, info, trns)?;
+ }
+ (ColorType::Grayscale | ColorType::GrayscaleAlpha, _) if bit_depth < 8 && expand => {
+ output_buffer[..row.len()].copy_from_slice(row);
+ expand_gray_u8(output_buffer, info, trns)
+ }
+ (ColorType::Grayscale | ColorType::Rgb, Some(trns)) if expand => {
+ let channels = color_type.samples();
+ if bit_depth == 8 {
+ utils::expand_trns_line(row, output_buffer, trns, channels);
+ } else if strip16 {
+ utils::expand_trns_and_strip_line16(row, output_buffer, trns, channels);
+ } else {
+ assert_eq!(bit_depth, 16);
+ utils::expand_trns_line16(row, output_buffer, trns, channels);
+ }
+ }
+ (
+ ColorType::Grayscale | ColorType::GrayscaleAlpha | ColorType::Rgb | ColorType::Rgba,
+ _,
+ ) if strip16 => {
+ for i in 0..row.len() / 2 {
+ output_buffer[i] = row[2 * i];
+ }
+ }
+ _ => output_buffer.copy_from_slice(row),
+ }
+
+ Ok(())
+ }
+
+ /// Returns the color type and the number of bits per sample
+ /// of the data returned by `Reader::next_row` and Reader::frames`.
+ pub fn output_color_type(&self) -> (ColorType, BitDepth) {
+ use crate::common::ColorType::*;
+ let t = self.transform;
+ let info = self.info();
+ if t == Transformations::IDENTITY {
+ (info.color_type, info.bit_depth)
+ } else {
+ let bits = match info.bit_depth as u8 {
+ 16 if t.intersects(Transformations::STRIP_16) => 8,
+ n if n < 8
+ && (t.contains(Transformations::EXPAND)
+ || t.contains(Transformations::ALPHA)) =>
+ {
+ 8
+ }
+ n => n,
+ };
+ let color_type =
+ if t.contains(Transformations::EXPAND) || t.contains(Transformations::ALPHA) {
+ let has_trns = info.trns.is_some() || t.contains(Transformations::ALPHA);
+ match info.color_type {
+ Grayscale if has_trns => GrayscaleAlpha,
+ Rgb if has_trns => Rgba,
+ Indexed if has_trns => Rgba,
+ Indexed => Rgb,
+ ct => ct,
+ }
+ } else {
+ info.color_type
+ };
+ (color_type, BitDepth::from_u8(bits).unwrap())
+ }
+ }
+
+ /// Returns the number of bytes required to hold a deinterlaced image frame
+ /// that is decoded using the given input transformations.
+ pub fn output_buffer_size(&self) -> usize {
+ let (width, height) = self.info().size();
+ let size = self.output_line_size(width);
+ size * height as usize
+ }
+
+ /// Returns the number of bytes required to hold a deinterlaced row.
+ pub fn output_line_size(&self, width: u32) -> usize {
+ let (color, depth) = self.output_color_type();
+ color.raw_row_length_from_width(depth, width) - 1
+ }
+
+ fn next_pass(&mut self) -> Option<(usize, InterlaceInfo)> {
+ match self.subframe.interlace {
+ InterlaceIter::Adam7(ref mut adam7) => {
+ let last_pass = adam7.current_pass();
+ let (pass, line, width) = adam7.next()?;
+ let rowlen = self.info().raw_row_length_from_width(width);
+ if last_pass != pass {
+ self.prev.clear();
+ self.prev.resize(rowlen, 0u8);
+ }
+ Some((rowlen, InterlaceInfo::Adam7 { pass, line, width }))
+ }
+ InterlaceIter::None(ref mut height) => {
+ let _ = height.next()?;
+ Some((self.subframe.rowlen, InterlaceInfo::Null))
+ }
+ }
+ }
+
+ /// Write the next raw interlaced row into `self.prev`.
+ ///
+ /// The scanline is filtered against the previous scanline according to the specification.
+ fn next_raw_interlaced_row(&mut self, rowlen: usize) -> Result<(), DecodingError> {
+ // Read image data until we have at least one full row (but possibly more than one).
+ while self.current.len() - self.scan_start < rowlen {
+ if self.subframe.consumed_and_flushed {
+ return Err(DecodingError::Format(
+ FormatErrorInner::NoMoreImageData.into(),
+ ));
+ }
+
+ // Clear the current buffer before appending more data.
+ if self.scan_start > 0 {
+ self.current.drain(..self.scan_start).for_each(drop);
+ self.scan_start = 0;
+ }
+
+ match self.decoder.decode_next(&mut self.current)? {
+ Some(Decoded::ImageData) => {}
+ Some(Decoded::ImageDataFlushed) => {
+ self.subframe.consumed_and_flushed = true;
+ }
+ None => {
+ return Err(DecodingError::Format(
+ if self.current.is_empty() {
+ FormatErrorInner::NoMoreImageData
+ } else {
+ FormatErrorInner::UnexpectedEndOfChunk
+ }
+ .into(),
+ ));
+ }
+ _ => (),
+ }
+ }
+
+ // Get a reference to the current row and point scan_start to the next one.
+ let row = &mut self.current[self.scan_start..];
+ self.scan_start += rowlen;
+
+ // Unfilter the row.
+ let filter = FilterType::from_u8(row[0]).ok_or(DecodingError::Format(
+ FormatErrorInner::UnknownFilterMethod(row[0]).into(),
+ ))?;
+ unfilter(filter, self.bpp, &self.prev[1..rowlen], &mut row[1..rowlen]);
+
+ // Save the current row for the next pass.
+ self.prev[..rowlen].copy_from_slice(&row[..rowlen]);
+
+ Ok(())
+ }
+}
+
+impl SubframeInfo {
+ fn not_yet_init() -> Self {
+ SubframeInfo {
+ width: 0,
+ height: 0,
+ rowlen: 0,
+ interlace: InterlaceIter::None(0..0),
+ consumed_and_flushed: false,
+ }
+ }
+
+ fn new(info: &Info) -> Self {
+ // The apng fctnl overrides width and height.
+ // All other data is set by the main info struct.
+ let (width, height) = if let Some(fc) = info.frame_control {
+ (fc.width, fc.height)
+ } else {
+ (info.width, info.height)
+ };
+
+ let interlace = if info.interlaced {
+ InterlaceIter::Adam7(utils::Adam7Iterator::new(width, height))
+ } else {
+ InterlaceIter::None(0..height)
+ };
+
+ SubframeInfo {
+ width,
+ height,
+ rowlen: info.raw_row_length_from_width(width),
+ interlace,
+ consumed_and_flushed: false,
+ }
+ }
+}
+
+fn expand_paletted(
+ buffer: &mut [u8],
+ info: &Info,
+ trns: Option<Option<&[u8]>>,
+) -> Result<(), DecodingError> {
+ if let Some(palette) = info.palette.as_ref() {
+ if let BitDepth::Sixteen = info.bit_depth {
+ // This should have been caught earlier but let's check again. Can't hurt.
+ Err(DecodingError::Format(
+ FormatErrorInner::InvalidColorBitDepth {
+ color_type: ColorType::Indexed,
+ bit_depth: BitDepth::Sixteen,
+ }
+ .into(),
+ ))
+ } else {
+ let black = [0, 0, 0];
+ if let Some(trns) = trns {
+ let trns = trns.unwrap_or(&[]);
+ // > The tRNS chunk shall not contain more alpha values than there are palette
+ // entries, but a tRNS chunk may contain fewer values than there are palette
+ // entries. In this case, the alpha value for all remaining palette entries is
+ // assumed to be 255.
+ //
+ // It seems, accepted reading is to fully *ignore* an invalid tRNS as if it were
+ // completely empty / all pixels are non-transparent.
+ let trns = if trns.len() <= palette.len() / 3 {
+ trns
+ } else {
+ &[]
+ };
+
+ utils::unpack_bits(buffer, 4, info.bit_depth as u8, |i, chunk| {
+ let (rgb, a) = (
+ palette
+ .get(3 * i as usize..3 * i as usize + 3)
+ .unwrap_or(&black),
+ *trns.get(i as usize).unwrap_or(&0xFF),
+ );
+ chunk[0] = rgb[0];
+ chunk[1] = rgb[1];
+ chunk[2] = rgb[2];
+ chunk[3] = a;
+ });
+ } else {
+ utils::unpack_bits(buffer, 3, info.bit_depth as u8, |i, chunk| {
+ let rgb = palette
+ .get(3 * i as usize..3 * i as usize + 3)
+ .unwrap_or(&black);
+ chunk[0] = rgb[0];
+ chunk[1] = rgb[1];
+ chunk[2] = rgb[2];
+ })
+ }
+ Ok(())
+ }
+ } else {
+ Err(DecodingError::Format(
+ FormatErrorInner::PaletteRequired.into(),
+ ))
+ }
+}
+
+fn expand_gray_u8(buffer: &mut [u8], info: &Info, trns: Option<Option<&[u8]>>) {
+ let rescale = true;
+ let scaling_factor = if rescale {
+ (255) / ((1u16 << info.bit_depth as u8) - 1) as u8
+ } else {
+ 1
+ };
+ if let Some(trns) = trns {
+ utils::unpack_bits(buffer, 2, info.bit_depth as u8, |pixel, chunk| {
+ chunk[1] = if let Some(trns) = trns {
+ if pixel == trns[0] {
+ 0
+ } else {
+ 0xFF
+ }
+ } else {
+ 0xFF
+ };
+ chunk[0] = pixel * scaling_factor
+ })
+ } else {
+ utils::unpack_bits(buffer, 1, info.bit_depth as u8, |val, chunk| {
+ chunk[0] = val * scaling_factor
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Decoder;
+ use std::io::{BufRead, Read, Result};
+ use std::mem::discriminant;
+
+ /// A reader that reads at most `n` bytes.
+ struct SmalBuf<R: BufRead> {
+ inner: R,
+ cap: usize,
+ }
+
+ impl<R: BufRead> SmalBuf<R> {
+ fn new(inner: R, cap: usize) -> Self {
+ SmalBuf { inner, cap }
+ }
+ }
+
+ impl<R: BufRead> Read for SmalBuf<R> {
+ fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
+ let len = buf.len().min(self.cap);
+ self.inner.read(&mut buf[..len])
+ }
+ }
+
+ impl<R: BufRead> BufRead for SmalBuf<R> {
+ fn fill_buf(&mut self) -> Result<&[u8]> {
+ let buf = self.inner.fill_buf()?;
+ let len = buf.len().min(self.cap);
+ Ok(&buf[..len])
+ }
+
+ fn consume(&mut self, amt: usize) {
+ assert!(amt <= self.cap);
+ self.inner.consume(amt)
+ }
+ }
+
+ #[test]
+ fn no_data_dup_on_finish() {
+ const IMG: &[u8] = include_bytes!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/tests/bugfixes/x_issue#214.png"
+ ));
+
+ let mut normal = Decoder::new(IMG).read_info().unwrap();
+
+ let mut buffer = vec![0; normal.output_buffer_size()];
+ let normal = normal.next_frame(&mut buffer).unwrap_err();
+
+ let smal = Decoder::new(SmalBuf::new(IMG, 1))
+ .read_info()
+ .unwrap()
+ .next_frame(&mut buffer)
+ .unwrap_err();
+
+ assert_eq!(discriminant(&normal), discriminant(&smal));
+ }
+}
diff --git a/vendor/png/src/decoder/stream.rs b/vendor/png/src/decoder/stream.rs
new file mode 100644
index 0000000..f5df6e9
--- /dev/null
+++ b/vendor/png/src/decoder/stream.rs
@@ -0,0 +1,1576 @@
+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;
+
+/// TODO check if these size are reasonable
+pub const CHUNCK_BUFFER_SIZE: usize = 32 * 1024;
+
+/// 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.
+const CHECKSUM_DISABLED: bool = cfg!(fuzzing);
+
+#[derive(Debug)]
+enum U32Value {
+ // CHUNKS
+ 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)]
+/// Result of the decoding process
+pub enum Decoded {
+ /// Nothing decoded yet
+ Nothing,
+ Header(u32, u32, BitDepth, ColorType, bool),
+ ChunkBegin(u32, ChunkType),
+ ChunkComplete(u32, ChunkType),
+ PixelDimensions(PixelDimensions),
+ AnimationControl(AnimationControl),
+ FrameControl(FrameControl),
+ /// 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,
+ PartialChunk(ChunkType),
+ ImageEnd,
+}
+
+/// 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.
+ 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,
+ /// End of file, within a chunk event.
+ UnexpectedEof,
+ /// End of file, while expecting more image data.
+ UnexpectedEndOfChunk,
+ // Errors of chunk level ordering, missing etc.
+ /// Ihdr must occur.
+ MissingIhdr,
+ /// 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 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,
+ },
+ /// 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),
+ 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),
+}
+
+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)
+ }
+ // 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.")
+ }
+ }
+ }
+ }
+ }
+}
+
+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,
+}
+
+impl Default for DecodeOptions {
+ fn default() -> Self {
+ Self {
+ ignore_adler32: true,
+ ignore_crc: false,
+ ignore_text_chunk: false,
+ }
+ }
+}
+
+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;
+ }
+}
+
+/// 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>,
+ /// Stores where in decoding an `fdAT` chunk we are.
+ apng_seq_handled: bool,
+ have_idat: bool,
+ decode_options: DecodeOptions,
+}
+
+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::Signature(0, [0; 7])),
+ current_chunk: ChunkState::default(),
+ inflater,
+ info: None,
+ current_seq_no: None,
+ apng_seq_handled: false,
+ have_idat: false,
+ decode_options,
+ }
+ }
+
+ /// Resets the StreamingDecoder
+ 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;
+ }
+
+ /// 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);
+ }
+
+ /// 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)
+ }
+
+ /// 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],
+ 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];
+
+ // Driver should ensure that state is never None
+ 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) => {
+ // 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 {
+ 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)))
+ }
+ // Handle other chunks
+ _ => {
+ if self.current_chunk.remaining == 0 {
+ // complete chunk
+ Ok((0, self.parse_chunk(type_str)?))
+ } else {
+ // Make sure we have room to read more of the chunk.
+ // We need it fully before parsing.
+ self.reserve_current_chunk()?;
+
+ self.state = Some(ReadChunk(type_str));
+ Ok((0, Decoded::PartialChunk(type_str)))
+ }
+ }
+ }
+ }
+ ReadChunk(type_str) => {
+ // The _previous_ event wanted to return the contents of raw_bytes, and let the
+ // caller consume it,
+ 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];
+ if !self.decode_options.ignore_crc {
+ 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> {
+ // FIXME: use limits, also do so in iccp/zlib decompression.
+ const MAX: usize = 0x10_0000;
+ let buffer = &mut self.current_chunk.raw_bytes;
+
+ // Double if necessary, but no more than until the limit is reached.
+ 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) => {
+ // Borrow of self ends here, because Decoding error does not borrow self.
+ 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()?;
+
+ // 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();
+ 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() {
+ // Only one palette is allowed
+ 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 => {
+ // The transparency chunk must be after the palette chunk and
+ // before the data chunk.
+ 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);
+ // Ignore chromaticities if sRGB profile is used.
+ 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);
+ // Ignore chromaticities if sRGB profile is used.
+ 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()?; // 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);
+ 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[..];
+
+ // read profile name
+ let _: u8 = buf.read_be()?;
+ for _ in 1..80 {
+ let raw: u8 = buf.read_be()?;
+ if raw == 0 {
+ break;
+ }
+ }
+
+ match buf.read_be()? {
+ // compression method
+ 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 {
+ // TODO: this should use Limits.bytes
+ 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()? {
+ // 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(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> {
+ // 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()
+ }
+}
+
+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(())
+ }
+}
diff --git a/vendor/png/src/decoder/zlib.rs b/vendor/png/src/decoder/zlib.rs
new file mode 100644
index 0000000..2953c95
--- /dev/null
+++ b/vendor/png/src/decoder/zlib.rs
@@ -0,0 +1,212 @@
+use super::{stream::FormatErrorInner, DecodingError, CHUNCK_BUFFER_SIZE};
+
+use fdeflate::Decompressor;
+
+/// Ergonomics wrapper around `miniz_oxide::inflate::stream` for zlib compressed data.
+pub(super) struct ZlibStream {
+ /// Current decoding state.
+ state: Box<fdeflate::Decompressor>,
+ /// If there has been a call to decompress already.
+ started: bool,
+ /// A buffer of compressed data.
+ /// We use this for a progress guarantee. The data in the input stream is chunked as given by
+ /// the underlying stream buffer. We will not read any more data until the current buffer has
+ /// been fully consumed. The zlib decompression can not fully consume all the data when it is
+ /// in the middle of the stream, it will treat full symbols and maybe the last bytes need to be
+ /// treated in a special way. The exact reason isn't as important but the interface does not
+ /// promise us this. Now, the complication is that the _current_ chunking information of PNG
+ /// alone is not enough to determine this as indeed the compressed stream is the concatenation
+ /// of all consecutive `IDAT`/`fdAT` chunks. We would need to inspect the next chunk header.
+ ///
+ /// Thus, there needs to be a buffer that allows fully clearing a chunk so that the next chunk
+ /// type can be inspected.
+ in_buffer: Vec<u8>,
+ /// The logical start of the `in_buffer`.
+ in_pos: usize,
+ /// Remaining buffered decoded bytes.
+ /// The decoder sometimes wants inspect some already finished bytes for further decoding. So we
+ /// keep a total of 32KB of decoded data available as long as more data may be appended.
+ out_buffer: Vec<u8>,
+ /// The cursor position in the output stream as a buffer index.
+ out_pos: usize,
+ /// Ignore and do not calculate the Adler-32 checksum. Defaults to `true`.
+ ///
+ /// This flag overrides `TINFL_FLAG_COMPUTE_ADLER32`.
+ ///
+ /// This flag should not be modified after decompression has started.
+ ignore_adler32: bool,
+}
+
+impl ZlibStream {
+ pub(crate) fn new() -> Self {
+ ZlibStream {
+ state: Box::new(Decompressor::new()),
+ started: false,
+ in_buffer: Vec::with_capacity(CHUNCK_BUFFER_SIZE),
+ in_pos: 0,
+ out_buffer: vec![0; 2 * CHUNCK_BUFFER_SIZE],
+ out_pos: 0,
+ ignore_adler32: true,
+ }
+ }
+
+ pub(crate) fn reset(&mut self) {
+ self.started = false;
+ self.in_buffer.clear();
+ self.in_pos = 0;
+ self.out_buffer.clear();
+ self.out_pos = 0;
+ *self.state = Decompressor::new();
+ }
+
+ /// Set the `ignore_adler32` flag and return `true` if the flag was
+ /// successfully set.
+ ///
+ /// The default is `true`.
+ ///
+ /// This flag cannot be modified after decompression has started until the
+ /// [ZlibStream] is reset.
+ pub(crate) fn set_ignore_adler32(&mut self, flag: bool) -> bool {
+ if !self.started {
+ self.ignore_adler32 = flag;
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Return the `ignore_adler32` flag.
+ pub(crate) fn ignore_adler32(&self) -> bool {
+ self.ignore_adler32
+ }
+
+ /// Fill the decoded buffer as far as possible from `data`.
+ /// On success returns the number of consumed input bytes.
+ pub(crate) fn decompress(
+ &mut self,
+ data: &[u8],
+ image_data: &mut Vec<u8>,
+ ) -> Result<usize, DecodingError> {
+ self.prepare_vec_for_appending();
+
+ if !self.started && self.ignore_adler32 {
+ self.state.ignore_adler32();
+ }
+
+ let in_data = if self.in_buffer.is_empty() {
+ data
+ } else {
+ &self.in_buffer[self.in_pos..]
+ };
+
+ let (mut in_consumed, out_consumed) = self
+ .state
+ .read(in_data, self.out_buffer.as_mut_slice(), self.out_pos, false)
+ .map_err(|err| {
+ DecodingError::Format(FormatErrorInner::CorruptFlateStream { err }.into())
+ })?;
+
+ if !self.in_buffer.is_empty() {
+ self.in_pos += in_consumed;
+ in_consumed = 0;
+ }
+
+ if self.in_buffer.len() == self.in_pos {
+ self.in_buffer.clear();
+ self.in_pos = 0;
+ }
+
+ if in_consumed == 0 {
+ self.in_buffer.extend_from_slice(data);
+ in_consumed = data.len();
+ }
+
+ self.started = true;
+ self.out_pos += out_consumed;
+ self.transfer_finished_data(image_data);
+
+ Ok(in_consumed)
+ }
+
+ /// Called after all consecutive IDAT chunks were handled.
+ ///
+ /// The compressed stream can be split on arbitrary byte boundaries. This enables some cleanup
+ /// within the decompressor and flushing additional data which may have been kept back in case
+ /// more data were passed to it.
+ pub(crate) fn finish_compressed_chunks(
+ &mut self,
+ image_data: &mut Vec<u8>,
+ ) -> Result<(), DecodingError> {
+ if !self.started {
+ return Ok(());
+ }
+
+ let tail = self.in_buffer.split_off(0);
+ let tail = &tail[self.in_pos..];
+
+ let mut start = 0;
+ loop {
+ self.prepare_vec_for_appending();
+
+ let (in_consumed, out_consumed) = self
+ .state
+ .read(
+ &tail[start..],
+ self.out_buffer.as_mut_slice(),
+ self.out_pos,
+ true,
+ )
+ .map_err(|err| {
+ DecodingError::Format(FormatErrorInner::CorruptFlateStream { err }.into())
+ })?;
+
+ start += in_consumed;
+ self.out_pos += out_consumed;
+
+ if self.state.is_done() {
+ self.out_buffer.truncate(self.out_pos);
+ image_data.append(&mut self.out_buffer);
+ return Ok(());
+ } else {
+ let transferred = self.transfer_finished_data(image_data);
+ assert!(
+ transferred > 0 || in_consumed > 0 || out_consumed > 0,
+ "No more forward progress made in stream decoding."
+ );
+ }
+ }
+ }
+
+ /// Resize the vector to allow allocation of more data.
+ fn prepare_vec_for_appending(&mut self) {
+ if self.out_buffer.len().saturating_sub(self.out_pos) >= CHUNCK_BUFFER_SIZE {
+ return;
+ }
+
+ let buffered_len = self.decoding_size(self.out_buffer.len());
+ debug_assert!(self.out_buffer.len() <= buffered_len);
+ self.out_buffer.resize(buffered_len, 0u8);
+ }
+
+ fn decoding_size(&self, len: usize) -> usize {
+ // Allocate one more chunk size than currently or double the length while ensuring that the
+ // allocation is valid and that any cursor within it will be valid.
+ len
+ // This keeps the buffer size a power-of-two, required by miniz_oxide.
+ .saturating_add(CHUNCK_BUFFER_SIZE.max(len))
+ // Ensure all buffer indices are valid cursor positions.
+ // Note: both cut off and zero extension give correct results.
+ .min(u64::max_value() as usize)
+ // Ensure the allocation request is valid.
+ // TODO: maximum allocation limits?
+ .min(isize::max_value() as usize)
+ }
+
+ fn transfer_finished_data(&mut self, image_data: &mut Vec<u8>) -> usize {
+ let safe = self.out_pos.saturating_sub(CHUNCK_BUFFER_SIZE);
+ // TODO: allocation limits.
+ image_data.extend(self.out_buffer.drain(..safe));
+ self.out_pos -= safe;
+ safe
+ }
+}