diff options
Diffstat (limited to 'vendor/png/src/decoder')
-rw-r--r-- | vendor/png/src/decoder/mod.rs | 961 | ||||
-rw-r--r-- | vendor/png/src/decoder/stream.rs | 1576 | ||||
-rw-r--r-- | vendor/png/src/decoder/zlib.rs | 212 |
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 + } +} |