diff options
Diffstat (limited to 'vendor/png/src/encoder.rs')
-rw-r--r-- | vendor/png/src/encoder.rs | 2389 |
1 files changed, 0 insertions, 2389 deletions
diff --git a/vendor/png/src/encoder.rs b/vendor/png/src/encoder.rs deleted file mode 100644 index 812bcaa..0000000 --- a/vendor/png/src/encoder.rs +++ /dev/null @@ -1,2389 +0,0 @@ -use borrow::Cow; -use io::{Read, Write}; -use ops::{Deref, DerefMut}; -use std::{borrow, error, fmt, io, mem, ops, result}; - -use crc32fast::Hasher as Crc32; -use flate2::write::ZlibEncoder; - -use crate::chunk::{self, ChunkType}; -use crate::common::{ - AnimationControl, BitDepth, BlendOp, BytesPerPixel, ColorType, Compression, DisposeOp, - FrameControl, Info, ParameterError, ParameterErrorKind, PixelDimensions, ScaledFloat, -}; -use crate::filter::{filter, AdaptiveFilterType, FilterType}; -use crate::text_metadata::{ - EncodableTextChunk, ITXtChunk, TEXtChunk, TextEncodingError, ZTXtChunk, -}; -use crate::traits::WriteBytesExt; - -pub type Result<T> = result::Result<T, EncodingError>; - -#[derive(Debug)] -pub enum EncodingError { - IoError(io::Error), - Format(FormatError), - Parameter(ParameterError), - LimitsExceeded, -} - -#[derive(Debug)] -pub struct FormatError { - inner: FormatErrorKind, -} - -#[derive(Debug)] -enum FormatErrorKind { - ZeroWidth, - ZeroHeight, - InvalidColorCombination(BitDepth, ColorType), - NoPalette, - // TODO: wait, what? - WrittenTooMuch(usize), - NotAnimated, - OutOfBounds, - EndReached, - ZeroFrames, - MissingFrames, - MissingData(usize), - Unrecoverable, - BadTextEncoding(TextEncodingError), -} - -impl error::Error for EncodingError { - fn cause(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - EncodingError::IoError(err) => Some(err), - _ => None, - } - } -} - -impl fmt::Display for EncodingError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { - use self::EncodingError::*; - match self { - IoError(err) => write!(fmt, "{}", err), - Format(desc) => write!(fmt, "{}", desc), - Parameter(desc) => write!(fmt, "{}", desc), - LimitsExceeded => write!(fmt, "Limits are exceeded."), - } - } -} - -impl fmt::Display for FormatError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { - use FormatErrorKind::*; - match self.inner { - ZeroWidth => write!(fmt, "Zero width not allowed"), - ZeroHeight => write!(fmt, "Zero height not allowed"), - ZeroFrames => write!(fmt, "Zero frames not allowed"), - InvalidColorCombination(depth, color) => write!( - fmt, - "Invalid combination of bit-depth '{:?}' and color-type '{:?}'", - depth, color - ), - NoPalette => write!(fmt, "can't write indexed image without palette"), - WrittenTooMuch(index) => write!(fmt, "wrong data size, got {} bytes too many", index), - NotAnimated => write!(fmt, "not an animation"), - OutOfBounds => write!( - fmt, - "the dimension and position go over the frame boundaries" - ), - EndReached => write!(fmt, "all the frames have been already written"), - MissingFrames => write!(fmt, "there are still frames to be written"), - MissingData(n) => write!(fmt, "there are still {} bytes to be written", n), - Unrecoverable => write!( - fmt, - "a previous error put the writer into an unrecoverable state" - ), - BadTextEncoding(tee) => match tee { - TextEncodingError::Unrepresentable => write!( - fmt, - "The text metadata cannot be encoded into valid ISO 8859-1" - ), - TextEncodingError::InvalidKeywordSize => write!(fmt, "Invalid keyword size"), - TextEncodingError::CompressionError => { - write!(fmt, "Unable to compress text metadata") - } - }, - } - } -} - -impl From<io::Error> for EncodingError { - fn from(err: io::Error) -> EncodingError { - EncodingError::IoError(err) - } -} - -impl From<EncodingError> for io::Error { - fn from(err: EncodingError) -> io::Error { - io::Error::new(io::ErrorKind::Other, err.to_string()) - } -} - -// Private impl. -impl From<FormatErrorKind> for FormatError { - fn from(kind: FormatErrorKind) -> Self { - FormatError { inner: kind } - } -} - -impl From<TextEncodingError> for EncodingError { - fn from(tee: TextEncodingError) -> Self { - EncodingError::Format(FormatError { - inner: FormatErrorKind::BadTextEncoding(tee), - }) - } -} - -/// PNG Encoder. -/// -/// This configures the PNG format options such as animation chunks, palette use, color types, -/// auxiliary chunks etc. -/// -/// FIXME: Configuring APNG might be easier (less individual errors) if we had an _adapter_ which -/// borrows this mutably but guarantees that `info.frame_control` is not `None`. -pub struct Encoder<'a, W: Write> { - w: W, - info: Info<'a>, - options: Options, -} - -/// Decoding options, internal type, forwarded to the Writer. -#[derive(Default)] -struct Options { - filter: FilterType, - adaptive_filter: AdaptiveFilterType, - sep_def_img: bool, - validate_sequence: bool, -} - -impl<'a, W: Write> Encoder<'a, W> { - pub fn new(w: W, width: u32, height: u32) -> Encoder<'static, W> { - Encoder { - w, - info: Info::with_size(width, height), - options: Options::default(), - } - } - - /// Specify that the image is animated. - /// - /// `num_frames` controls how many frames the animation has, while - /// `num_plays` controls how many times the animation should be - /// repeated until it stops, if it's zero then it will repeat - /// infinitely. - /// - /// When this method is returns successfully then the images written will be encoded as fdAT - /// chunks, except for the first image that is still encoded as `IDAT`. You can control if the - /// first frame should be treated as an animation frame with [`Encoder::set_sep_def_img()`]. - /// - /// This method returns an error if `num_frames` is 0. - pub fn set_animated(&mut self, num_frames: u32, num_plays: u32) -> Result<()> { - if num_frames == 0 { - return Err(EncodingError::Format(FormatErrorKind::ZeroFrames.into())); - } - - let actl = AnimationControl { - num_frames, - num_plays, - }; - - let fctl = FrameControl { - sequence_number: 0, - width: self.info.width, - height: self.info.height, - ..Default::default() - }; - - self.info.animation_control = Some(actl); - self.info.frame_control = Some(fctl); - Ok(()) - } - - /// Mark the first animated frame as a 'separate default image'. - /// - /// In APNG each animated frame is preceded by a special control chunk, `fcTL`. It's up to the - /// encoder to decide if the first image, the standard `IDAT` data, should be part of the - /// animation by emitting this chunk or by not doing so. A default image that is _not_ part of - /// the animation is often interpreted as a thumbnail. - /// - /// This method will return an error when animation control was not configured - /// (which is done by calling [`Encoder::set_animated`]). - pub fn set_sep_def_img(&mut self, sep_def_img: bool) -> Result<()> { - if self.info.animation_control.is_some() { - self.options.sep_def_img = sep_def_img; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Sets the raw byte contents of the PLTE chunk. This method accepts - /// both borrowed and owned byte data. - pub fn set_palette<T: Into<Cow<'a, [u8]>>>(&mut self, palette: T) { - self.info.palette = Some(palette.into()); - } - - /// Sets the raw byte contents of the tRNS chunk. This method accepts - /// both borrowed and owned byte data. - pub fn set_trns<T: Into<Cow<'a, [u8]>>>(&mut self, trns: T) { - self.info.trns = Some(trns.into()); - } - - /// Set the display gamma of the source system on which the image was generated or last edited. - pub fn set_source_gamma(&mut self, source_gamma: ScaledFloat) { - self.info.source_gamma = Some(source_gamma); - } - - /// Set the chromaticities for the source system's display channels (red, green, blue) and the whitepoint - /// of the source system on which the image was generated or last edited. - pub fn set_source_chromaticities( - &mut self, - source_chromaticities: super::SourceChromaticities, - ) { - self.info.source_chromaticities = Some(source_chromaticities); - } - - /// Mark the image data as conforming to the SRGB color space with the specified rendering intent. - /// - /// Matching source gamma and chromaticities chunks are added automatically. - /// Any manually specified source gamma or chromaticities will be ignored. - pub fn set_srgb(&mut self, rendering_intent: super::SrgbRenderingIntent) { - self.info.srgb = Some(rendering_intent); - } - - /// Start encoding by writing the header data. - /// - /// The remaining data can be supplied by methods on the returned [`Writer`]. - pub fn write_header(self) -> Result<Writer<W>> { - Writer::new(self.w, PartialInfo::new(&self.info), self.options).init(&self.info) - } - - /// Set the color of the encoded image. - /// - /// These correspond to the color types in the png IHDR data that will be written. The length - /// of the image data that is later supplied must match the color type, otherwise an error will - /// be emitted. - pub fn set_color(&mut self, color: ColorType) { - self.info.color_type = color; - } - - /// Set the indicated depth of the image data. - pub fn set_depth(&mut self, depth: BitDepth) { - self.info.bit_depth = depth; - } - - /// Set compression parameters. - /// - /// Accepts a `Compression` or any type that can transform into a `Compression`. Notably `deflate::Compression` and - /// `deflate::CompressionOptions` which "just work". - pub fn set_compression(&mut self, compression: Compression) { - self.info.compression = compression; - } - - /// Set the used filter type. - /// - /// The default filter is [`FilterType::Sub`] which provides a basic prediction algorithm for - /// sample values based on the previous. For a potentially better compression ratio, at the - /// cost of more complex processing, try out [`FilterType::Paeth`]. - /// - /// [`FilterType::Sub`]: enum.FilterType.html#variant.Sub - /// [`FilterType::Paeth`]: enum.FilterType.html#variant.Paeth - pub fn set_filter(&mut self, filter: FilterType) { - self.options.filter = filter; - } - - /// Set the adaptive filter type. - /// - /// Adaptive filtering attempts to select the best filter for each line - /// based on heuristics which minimize the file size for compression rather - /// than use a single filter for the entire image. The default method is - /// [`AdaptiveFilterType::NonAdaptive`]. - /// - /// [`AdaptiveFilterType::NonAdaptive`]: enum.AdaptiveFilterType.html - pub fn set_adaptive_filter(&mut self, adaptive_filter: AdaptiveFilterType) { - self.options.adaptive_filter = adaptive_filter; - } - - /// Set the fraction of time every frame is going to be displayed, in seconds. - /// - /// *Note that this parameter can be set for each individual frame after - /// [`Encoder::write_header`] is called. (see [`Writer::set_frame_delay`])* - /// - /// If the denominator is 0, it is to be treated as if it were 100 - /// (that is, the numerator then specifies 1/100ths of a second). - /// If the the value of the numerator is 0 the decoder should render the next frame - /// as quickly as possible, though viewers may impose a reasonable lower bound. - /// - /// The default value is 0 for both the numerator and denominator. - /// - /// This method will return an error if the image is not animated. - /// (see [`set_animated`]) - /// - /// [`write_header`]: struct.Encoder.html#method.write_header - /// [`set_animated`]: struct.Encoder.html#method.set_animated - /// [`Writer::set_frame_delay`]: struct.Writer#method.set_frame_delay - pub fn set_frame_delay(&mut self, numerator: u16, denominator: u16) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.delay_den = denominator; - fctl.delay_num = numerator; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the blend operation for every frame. - /// - /// The blend operation specifies whether the frame is to be alpha blended - /// into the current output buffer content, or whether it should completely - /// replace its region in the output buffer. - /// - /// *Note that this parameter can be set for each individual frame after - /// [`write_header`] is called. (see [`Writer::set_blend_op`])* - /// - /// See the [`BlendOp`] documentation for the possible values and their effects. - /// - /// *Note that for the first frame the two blend modes are functionally - /// equivalent due to the clearing of the output buffer at the beginning - /// of each play.* - /// - /// The default value is [`BlendOp::Source`]. - /// - /// This method will return an error if the image is not animated. - /// (see [`set_animated`]) - /// - /// [`BlendOP`]: enum.BlendOp.html - /// [`BlendOP::Source`]: enum.BlendOp.html#variant.Source - /// [`write_header`]: struct.Encoder.html#method.write_header - /// [`set_animated`]: struct.Encoder.html#method.set_animated - /// [`Writer::set_blend_op`]: struct.Writer#method.set_blend_op - pub fn set_blend_op(&mut self, op: BlendOp) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.blend_op = op; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the dispose operation for every frame. - /// - /// The dispose operation specifies how the output buffer should be changed - /// at the end of the delay (before rendering the next frame) - /// - /// *Note that this parameter can be set for each individual frame after - /// [`write_header`] is called (see [`Writer::set_dispose_op`])* - /// - /// See the [`DisposeOp`] documentation for the possible values and their effects. - /// - /// *Note that if the first frame uses [`DisposeOp::Previous`] - /// it will be treated as [`DisposeOp::Background`].* - /// - /// The default value is [`DisposeOp::None`]. - /// - /// This method will return an error if the image is not animated. - /// (see [`set_animated`]) - /// - /// [`DisposeOp`]: ../common/enum.BlendOp.html - /// [`DisposeOp::Previous`]: ../common/enum.BlendOp.html#variant.Previous - /// [`DisposeOp::Background`]: ../common/enum.BlendOp.html#variant.Background - /// [`DisposeOp::None`]: ../common/enum.BlendOp.html#variant.None - /// [`write_header`]: struct.Encoder.html#method.write_header - /// [`set_animated`]: struct.Encoder.html#method.set_animated - /// [`Writer::set_dispose_op`]: struct.Writer#method.set_dispose_op - pub fn set_dispose_op(&mut self, op: DisposeOp) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.dispose_op = op; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - pub fn set_pixel_dims(&mut self, pixel_dims: Option<PixelDimensions>) { - self.info.pixel_dims = pixel_dims - } - /// Convenience function to add tEXt chunks to [`Info`] struct - pub fn add_text_chunk(&mut self, keyword: String, text: String) -> Result<()> { - let text_chunk = TEXtChunk::new(keyword, text); - self.info.uncompressed_latin1_text.push(text_chunk); - Ok(()) - } - - /// Convenience function to add zTXt chunks to [`Info`] struct - pub fn add_ztxt_chunk(&mut self, keyword: String, text: String) -> Result<()> { - let text_chunk = ZTXtChunk::new(keyword, text); - self.info.compressed_latin1_text.push(text_chunk); - Ok(()) - } - - /// Convenience function to add iTXt chunks to [`Info`] struct - /// - /// This function only sets the `keyword` and `text` field of the iTXt chunk. - /// To set the other fields, create a [`ITXtChunk`] directly, and then encode it to the output stream. - pub fn add_itxt_chunk(&mut self, keyword: String, text: String) -> Result<()> { - let text_chunk = ITXtChunk::new(keyword, text); - self.info.utf8_text.push(text_chunk); - Ok(()) - } - - /// Validate the written image sequence. - /// - /// When validation is turned on (it's turned off by default) then attempts to write more than - /// one `IDAT` image or images beyond the number of frames indicated in the animation control - /// chunk will fail and return an error result instead. Attempts to [finish][finish] the image - /// with missing frames will also return an error. - /// - /// [finish]: StreamWriter::finish - /// - /// (It's possible to circumvent these checks by writing raw chunks instead.) - pub fn validate_sequence(&mut self, validate: bool) { - self.options.validate_sequence = validate; - } -} - -/// PNG writer -/// -/// Progresses through the image by writing images, frames, or raw individual chunks. This is -/// constructed through [`Encoder::write_header()`]. -/// -/// FIXME: Writing of animated chunks might be clearer if we had an _adapter_ that you would call -/// to guarantee the next image to be prefaced with a fcTL-chunk, and all other chunks would be -/// guaranteed to be `IDAT`/not affected by APNG's frame control. -pub struct Writer<W: Write> { - /// The underlying writer. - w: W, - /// The local version of the `Info` struct. - info: PartialInfo, - /// Global encoding options. - options: Options, - /// The total number of image frames, counting all consecutive IDAT and fdAT chunks. - images_written: u64, - /// The total number of animation frames, that is equivalent to counting fcTL chunks. - animation_written: u32, - /// A flag to note when the IEND chunk was already added. - /// This is only set on code paths that drop `Self` to control the destructor. - iend_written: bool, -} - -/// Contains the subset of attributes of [Info] needed for [Writer] to function -struct PartialInfo { - width: u32, - height: u32, - bit_depth: BitDepth, - color_type: ColorType, - frame_control: Option<FrameControl>, - animation_control: Option<AnimationControl>, - compression: Compression, - has_palette: bool, -} - -impl PartialInfo { - fn new(info: &Info) -> Self { - PartialInfo { - width: info.width, - height: info.height, - bit_depth: info.bit_depth, - color_type: info.color_type, - frame_control: info.frame_control, - animation_control: info.animation_control, - compression: info.compression, - has_palette: info.palette.is_some(), - } - } - - fn bpp_in_prediction(&self) -> BytesPerPixel { - // Passthrough - self.to_info().bpp_in_prediction() - } - - fn raw_row_length(&self) -> usize { - // Passthrough - self.to_info().raw_row_length() - } - - fn raw_row_length_from_width(&self, width: u32) -> usize { - // Passthrough - self.to_info().raw_row_length_from_width(width) - } - - /// Converts this partial info to an owned Info struct, - /// setting missing values to their defaults - fn to_info(&self) -> Info<'static> { - Info { - width: self.width, - height: self.height, - bit_depth: self.bit_depth, - color_type: self.color_type, - frame_control: self.frame_control, - animation_control: self.animation_control, - compression: self.compression, - ..Default::default() - } - } -} - -const DEFAULT_BUFFER_LENGTH: usize = 4 * 1024; - -pub(crate) fn write_chunk<W: Write>(mut w: W, name: chunk::ChunkType, data: &[u8]) -> Result<()> { - w.write_be(data.len() as u32)?; - w.write_all(&name.0)?; - w.write_all(data)?; - let mut crc = Crc32::new(); - crc.update(&name.0); - crc.update(data); - w.write_be(crc.finalize())?; - Ok(()) -} - -impl<W: Write> Writer<W> { - fn new(w: W, info: PartialInfo, options: Options) -> Writer<W> { - Writer { - w, - info, - options, - images_written: 0, - animation_written: 0, - iend_written: false, - } - } - - fn init(mut self, info: &Info<'_>) -> Result<Self> { - if self.info.width == 0 { - return Err(EncodingError::Format(FormatErrorKind::ZeroWidth.into())); - } - - if self.info.height == 0 { - return Err(EncodingError::Format(FormatErrorKind::ZeroHeight.into())); - } - - if self - .info - .color_type - .is_combination_invalid(self.info.bit_depth) - { - return Err(EncodingError::Format( - FormatErrorKind::InvalidColorCombination(self.info.bit_depth, self.info.color_type) - .into(), - )); - } - - self.w.write_all(&[137, 80, 78, 71, 13, 10, 26, 10])?; // PNG signature - info.encode(&mut self.w)?; - - Ok(self) - } - - /// Write a raw chunk of PNG data. - /// - /// The chunk will have its CRC calculated and correctly. The data is not filtered in any way, - /// but the chunk needs to be short enough to have its length encoded correctly. - pub fn write_chunk(&mut self, name: ChunkType, data: &[u8]) -> Result<()> { - use std::convert::TryFrom; - - if u32::try_from(data.len()).map_or(true, |length| length > i32::MAX as u32) { - let kind = FormatErrorKind::WrittenTooMuch(data.len() - i32::MAX as usize); - return Err(EncodingError::Format(kind.into())); - } - - write_chunk(&mut self.w, name, data) - } - - pub fn write_text_chunk<T: EncodableTextChunk>(&mut self, text_chunk: &T) -> Result<()> { - text_chunk.encode(&mut self.w) - } - - /// Check if we should allow writing another image. - fn validate_new_image(&self) -> Result<()> { - if !self.options.validate_sequence { - return Ok(()); - } - - match self.info.animation_control { - None => { - if self.images_written == 0 { - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::EndReached.into())) - } - } - Some(_) => { - if self.info.frame_control.is_some() { - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::EndReached.into())) - } - } - } - } - - fn validate_sequence_done(&self) -> Result<()> { - if !self.options.validate_sequence { - return Ok(()); - } - - if (self.info.animation_control.is_some() && self.info.frame_control.is_some()) - || self.images_written == 0 - { - Err(EncodingError::Format(FormatErrorKind::MissingFrames.into())) - } else { - Ok(()) - } - } - - const MAX_IDAT_CHUNK_LEN: u32 = std::u32::MAX >> 1; - #[allow(non_upper_case_globals)] - const MAX_fdAT_CHUNK_LEN: u32 = (std::u32::MAX >> 1) - 4; - - /// Writes the next image data. - pub fn write_image_data(&mut self, data: &[u8]) -> Result<()> { - if self.info.color_type == ColorType::Indexed && !self.info.has_palette { - return Err(EncodingError::Format(FormatErrorKind::NoPalette.into())); - } - - self.validate_new_image()?; - - let width: usize; - let height: usize; - if let Some(ref mut fctl) = self.info.frame_control { - width = fctl.width as usize; - height = fctl.height as usize; - } else { - width = self.info.width as usize; - height = self.info.height as usize; - } - - let in_len = self.info.raw_row_length_from_width(width as u32) - 1; - let data_size = in_len * height; - if data_size != data.len() { - return Err(EncodingError::Parameter( - ParameterErrorKind::ImageBufferSize { - expected: data_size, - actual: data.len(), - } - .into(), - )); - } - - let prev = vec![0; in_len]; - let mut prev = prev.as_slice(); - - let bpp = self.info.bpp_in_prediction(); - let filter_method = self.options.filter; - let adaptive_method = self.options.adaptive_filter; - - let zlib_encoded = match self.info.compression { - Compression::Fast => { - let mut compressor = fdeflate::Compressor::new(std::io::Cursor::new(Vec::new()))?; - - let mut current = vec![0; in_len + 1]; - for line in data.chunks(in_len) { - let filter_type = filter( - filter_method, - adaptive_method, - bpp, - prev, - line, - &mut current[1..], - ); - - current[0] = filter_type as u8; - compressor.write_data(¤t)?; - prev = line; - } - - let compressed = compressor.finish()?.into_inner(); - if compressed.len() - > fdeflate::StoredOnlyCompressor::<()>::compressed_size((in_len + 1) * height) - { - // Write uncompressed data since the result from fast compression would take - // more space than that. - // - // We always use FilterType::NoFilter here regardless of the filter method - // requested by the user. Doing filtering again would only add performance - // cost for both encoding and subsequent decoding, without improving the - // compression ratio. - let mut compressor = - fdeflate::StoredOnlyCompressor::new(std::io::Cursor::new(Vec::new()))?; - for line in data.chunks(in_len) { - compressor.write_data(&[0])?; - compressor.write_data(line)?; - } - compressor.finish()?.into_inner() - } else { - compressed - } - } - _ => { - let mut current = vec![0; in_len]; - - let mut zlib = ZlibEncoder::new(Vec::new(), self.info.compression.to_options()); - for line in data.chunks(in_len) { - let filter_type = filter( - filter_method, - adaptive_method, - bpp, - prev, - line, - &mut current, - ); - - zlib.write_all(&[filter_type as u8])?; - zlib.write_all(¤t)?; - prev = line; - } - zlib.finish()? - } - }; - - match self.info.frame_control { - None => { - self.write_zlib_encoded_idat(&zlib_encoded)?; - } - Some(_) if self.should_skip_frame_control_on_default_image() => { - self.write_zlib_encoded_idat(&zlib_encoded)?; - } - Some(ref mut fctl) => { - fctl.encode(&mut self.w)?; - fctl.sequence_number = fctl.sequence_number.wrapping_add(1); - self.animation_written += 1; - - // If the default image is the first frame of an animation, it's still an IDAT. - if self.images_written == 0 { - self.write_zlib_encoded_idat(&zlib_encoded)?; - } else { - let buff_size = zlib_encoded.len().min(Self::MAX_fdAT_CHUNK_LEN as usize); - let mut alldata = vec![0u8; 4 + buff_size]; - for chunk in zlib_encoded.chunks(Self::MAX_fdAT_CHUNK_LEN as usize) { - alldata[..4].copy_from_slice(&fctl.sequence_number.to_be_bytes()); - alldata[4..][..chunk.len()].copy_from_slice(chunk); - write_chunk(&mut self.w, chunk::fdAT, &alldata[..4 + chunk.len()])?; - fctl.sequence_number = fctl.sequence_number.wrapping_add(1); - } - } - } - } - - self.increment_images_written(); - - Ok(()) - } - - fn increment_images_written(&mut self) { - self.images_written = self.images_written.saturating_add(1); - - if let Some(actl) = self.info.animation_control { - if actl.num_frames <= self.animation_written { - // If we've written all animation frames, all following will be normal image chunks. - self.info.frame_control = None; - } - } - } - - fn write_iend(&mut self) -> Result<()> { - self.iend_written = true; - self.write_chunk(chunk::IEND, &[]) - } - - fn should_skip_frame_control_on_default_image(&self) -> bool { - self.options.sep_def_img && self.images_written == 0 - } - - fn write_zlib_encoded_idat(&mut self, zlib_encoded: &[u8]) -> Result<()> { - for chunk in zlib_encoded.chunks(Self::MAX_IDAT_CHUNK_LEN as usize) { - self.write_chunk(chunk::IDAT, chunk)?; - } - Ok(()) - } - - /// Set the used filter type for the following frames. - /// - /// The default filter is [`FilterType::Sub`] which provides a basic prediction algorithm for - /// sample values based on the previous. For a potentially better compression ratio, at the - /// cost of more complex processing, try out [`FilterType::Paeth`]. - /// - /// [`FilterType::Sub`]: enum.FilterType.html#variant.Sub - /// [`FilterType::Paeth`]: enum.FilterType.html#variant.Paeth - pub fn set_filter(&mut self, filter: FilterType) { - self.options.filter = filter; - } - - /// Set the adaptive filter type for the following frames. - /// - /// Adaptive filtering attempts to select the best filter for each line - /// based on heuristics which minimize the file size for compression rather - /// than use a single filter for the entire image. The default method is - /// [`AdaptiveFilterType::NonAdaptive`]. - /// - /// [`AdaptiveFilterType::NonAdaptive`]: enum.AdaptiveFilterType.html - pub fn set_adaptive_filter(&mut self, adaptive_filter: AdaptiveFilterType) { - self.options.adaptive_filter = adaptive_filter; - } - - /// Set the fraction of time the following frames are going to be displayed, - /// in seconds - /// - /// If the denominator is 0, it is to be treated as if it were 100 - /// (that is, the numerator then specifies 1/100ths of a second). - /// If the the value of the numerator is 0 the decoder should render the next frame - /// as quickly as possible, though viewers may impose a reasonable lower bound. - /// - /// This method will return an error if the image is not animated. - pub fn set_frame_delay(&mut self, numerator: u16, denominator: u16) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.delay_den = denominator; - fctl.delay_num = numerator; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the dimension of the following frames. - /// - /// This function will return an error when: - /// - The image is not an animated; - /// - /// - The selected dimension, considering also the current frame position, - /// goes outside the image boundaries; - /// - /// - One or both the width and height are 0; - /// - // ??? TODO ??? - // - The next frame is the default image - pub fn set_frame_dimension(&mut self, width: u32, height: u32) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - if Some(width) > self.info.width.checked_sub(fctl.x_offset) - || Some(height) > self.info.height.checked_sub(fctl.y_offset) - { - return Err(EncodingError::Format(FormatErrorKind::OutOfBounds.into())); - } else if width == 0 { - return Err(EncodingError::Format(FormatErrorKind::ZeroWidth.into())); - } else if height == 0 { - return Err(EncodingError::Format(FormatErrorKind::ZeroHeight.into())); - } - fctl.width = width; - fctl.height = height; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the position of the following frames. - /// - /// An error will be returned if: - /// - The image is not animated; - /// - /// - The selected position, considering also the current frame dimension, - /// goes outside the image boundaries; - /// - // ??? TODO ??? - // - The next frame is the default image - pub fn set_frame_position(&mut self, x: u32, y: u32) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - if Some(x) > self.info.width.checked_sub(fctl.width) - || Some(y) > self.info.height.checked_sub(fctl.height) - { - return Err(EncodingError::Format(FormatErrorKind::OutOfBounds.into())); - } - fctl.x_offset = x; - fctl.y_offset = y; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the frame dimension to occupy all the image, starting from - /// the current position. - /// - /// To reset the frame to the full image size [`reset_frame_position`] - /// should be called first. - /// - /// This method will return an error if the image is not animated. - /// - /// [`reset_frame_position`]: struct.Writer.html#method.reset_frame_position - pub fn reset_frame_dimension(&mut self) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.width = self.info.width - fctl.x_offset; - fctl.height = self.info.height - fctl.y_offset; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the frame position to (0, 0). - /// - /// Equivalent to calling [`set_frame_position(0, 0)`]. - /// - /// This method will return an error if the image is not animated. - /// - /// [`set_frame_position(0, 0)`]: struct.Writer.html#method.set_frame_position - pub fn reset_frame_position(&mut self) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.x_offset = 0; - fctl.y_offset = 0; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the blend operation for the following frames. - /// - /// The blend operation specifies whether the frame is to be alpha blended - /// into the current output buffer content, or whether it should completely - /// replace its region in the output buffer. - /// - /// See the [`BlendOp`] documentation for the possible values and their effects. - /// - /// *Note that for the first frame the two blend modes are functionally - /// equivalent due to the clearing of the output buffer at the beginning - /// of each play.* - /// - /// This method will return an error if the image is not animated. - /// - /// [`BlendOP`]: enum.BlendOp.html - pub fn set_blend_op(&mut self, op: BlendOp) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.blend_op = op; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the dispose operation for the following frames. - /// - /// The dispose operation specifies how the output buffer should be changed - /// at the end of the delay (before rendering the next frame) - /// - /// See the [`DisposeOp`] documentation for the possible values and their effects. - /// - /// *Note that if the first frame uses [`DisposeOp::Previous`] - /// it will be treated as [`DisposeOp::Background`].* - /// - /// This method will return an error if the image is not animated. - /// - /// [`DisposeOp`]: ../common/enum.BlendOp.html - /// [`DisposeOp::Previous`]: ../common/enum.BlendOp.html#variant.Previous - /// [`DisposeOp::Background`]: ../common/enum.BlendOp.html#variant.Background - pub fn set_dispose_op(&mut self, op: DisposeOp) -> Result<()> { - if let Some(ref mut fctl) = self.info.frame_control { - fctl.dispose_op = op; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Create a stream writer. - /// - /// This allows you to create images that do not fit in memory. The default - /// chunk size is 4K, use `stream_writer_with_size` to set another chunk - /// size. - /// - /// This borrows the writer which allows for manually appending additional - /// chunks after the image data has been written. - pub fn stream_writer(&mut self) -> Result<StreamWriter<W>> { - self.stream_writer_with_size(DEFAULT_BUFFER_LENGTH) - } - - /// Create a stream writer with custom buffer size. - /// - /// See [`stream_writer`]. - /// - /// [`stream_writer`]: #fn.stream_writer - pub fn stream_writer_with_size(&mut self, size: usize) -> Result<StreamWriter<W>> { - StreamWriter::new(ChunkOutput::Borrowed(self), size) - } - - /// Turn this into a stream writer for image data. - /// - /// This allows you to create images that do not fit in memory. The default - /// chunk size is 4K, use `stream_writer_with_size` to set another chunk - /// size. - pub fn into_stream_writer(self) -> Result<StreamWriter<'static, W>> { - self.into_stream_writer_with_size(DEFAULT_BUFFER_LENGTH) - } - - /// Turn this into a stream writer with custom buffer size. - /// - /// See [`into_stream_writer`]. - /// - /// [`into_stream_writer`]: #fn.into_stream_writer - pub fn into_stream_writer_with_size(self, size: usize) -> Result<StreamWriter<'static, W>> { - StreamWriter::new(ChunkOutput::Owned(self), size) - } - - /// Consume the stream writer with validation. - /// - /// Unlike a simple drop this ensures that the final chunk was written correctly. When other - /// validation options (chunk sequencing) had been turned on in the configuration then it will - /// also do a check on their correctness _before_ writing the final chunk. - pub fn finish(mut self) -> Result<()> { - self.validate_sequence_done()?; - self.write_iend()?; - self.w.flush()?; - - // Explicitly drop `self` just for clarity. - drop(self); - Ok(()) - } -} - -impl<W: Write> Drop for Writer<W> { - fn drop(&mut self) { - if !self.iend_written { - let _ = self.write_iend(); - } - } -} - -enum ChunkOutput<'a, W: Write> { - Borrowed(&'a mut Writer<W>), - Owned(Writer<W>), -} - -// opted for deref for practical reasons -impl<'a, W: Write> Deref for ChunkOutput<'a, W> { - type Target = Writer<W>; - - fn deref(&self) -> &Self::Target { - match self { - ChunkOutput::Borrowed(writer) => writer, - ChunkOutput::Owned(writer) => writer, - } - } -} - -impl<'a, W: Write> DerefMut for ChunkOutput<'a, W> { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - ChunkOutput::Borrowed(writer) => writer, - ChunkOutput::Owned(writer) => writer, - } - } -} - -/// This writer is used between the actual writer and the -/// ZlibEncoder and has the job of packaging the compressed -/// data into a PNG chunk, based on the image metadata -/// -/// Currently the way it works is that the specified buffer -/// will hold one chunk at the time and buffer the incoming -/// data until `flush` is called or the maximum chunk size -/// is reached. -/// -/// The maximum chunk is the smallest between the selected buffer size -/// and `u32::MAX >> 1` (`0x7fffffff` or `2147483647` dec) -/// -/// When a chunk has to be flushed the length (that is now known) -/// and the CRC will be written at the correct locations in the chunk. -struct ChunkWriter<'a, W: Write> { - writer: ChunkOutput<'a, W>, - buffer: Vec<u8>, - /// keeps track of where the last byte was written - index: usize, - curr_chunk: ChunkType, -} - -impl<'a, W: Write> ChunkWriter<'a, W> { - fn new(writer: ChunkOutput<'a, W>, buf_len: usize) -> ChunkWriter<'a, W> { - // currently buf_len will determine the size of each chunk - // the len is capped to the maximum size every chunk can hold - // (this wont ever overflow an u32) - // - // TODO (maybe): find a way to hold two chunks at a time if `usize` - // is 64 bits. - const CAP: usize = std::u32::MAX as usize >> 1; - let curr_chunk = if writer.images_written == 0 { - chunk::IDAT - } else { - chunk::fdAT - }; - ChunkWriter { - writer, - buffer: vec![0; CAP.min(buf_len)], - index: 0, - curr_chunk, - } - } - - /// Returns the size of each scanline for the next frame - /// paired with the size of the whole frame - /// - /// This is used by the `StreamWriter` to know when the scanline ends - /// so it can filter compress it and also to know when to start - /// the next one - fn next_frame_info(&self) -> (usize, usize) { - let wrt = self.writer.deref(); - - let width: usize; - let height: usize; - if let Some(fctl) = wrt.info.frame_control { - width = fctl.width as usize; - height = fctl.height as usize; - } else { - width = wrt.info.width as usize; - height = wrt.info.height as usize; - } - - let in_len = wrt.info.raw_row_length_from_width(width as u32) - 1; - let data_size = in_len * height; - - (in_len, data_size) - } - - /// NOTE: this bypasses the internal buffer so the flush method should be called before this - /// in the case there is some data left in the buffer when this is called, it will panic - fn write_header(&mut self) -> Result<()> { - assert_eq!(self.index, 0, "Called when not flushed"); - let wrt = self.writer.deref_mut(); - - self.curr_chunk = if wrt.images_written == 0 { - chunk::IDAT - } else { - chunk::fdAT - }; - - match wrt.info.frame_control { - Some(_) if wrt.should_skip_frame_control_on_default_image() => {} - Some(ref mut fctl) => { - fctl.encode(&mut wrt.w)?; - fctl.sequence_number += 1; - } - _ => {} - } - - Ok(()) - } - - /// Set the `FrameControl` for the following frame - /// - /// It will ignore the `sequence_number` of the parameter - /// as it is updated internally. - fn set_fctl(&mut self, f: FrameControl) { - if let Some(ref mut fctl) = self.writer.info.frame_control { - // Ignore the sequence number - *fctl = FrameControl { - sequence_number: fctl.sequence_number, - ..f - }; - } else { - panic!("This function must be called on an animated PNG") - } - } - - /// Flushes the current chunk - fn flush_inner(&mut self) -> io::Result<()> { - if self.index > 0 { - // flush the chunk and reset everything - write_chunk( - &mut self.writer.w, - self.curr_chunk, - &self.buffer[..self.index], - )?; - - self.index = 0; - } - Ok(()) - } -} - -impl<'a, W: Write> Write for ChunkWriter<'a, W> { - fn write(&mut self, mut data: &[u8]) -> io::Result<usize> { - if data.is_empty() { - return Ok(0); - } - - // index == 0 means a chunk has been flushed out - if self.index == 0 { - let wrt = self.writer.deref_mut(); - - // Prepare the next animated frame, if any. - let no_fctl = wrt.should_skip_frame_control_on_default_image(); - if wrt.info.frame_control.is_some() && !no_fctl { - let fctl = wrt.info.frame_control.as_mut().unwrap(); - self.buffer[0..4].copy_from_slice(&fctl.sequence_number.to_be_bytes()); - fctl.sequence_number += 1; - self.index = 4; - } - } - - // Cap the buffer length to the maximum number of bytes that can't still - // be added to the current chunk - let written = data.len().min(self.buffer.len() - self.index); - data = &data[..written]; - - self.buffer[self.index..][..written].copy_from_slice(data); - self.index += written; - - // if the maximum data for this chunk as been reached it needs to be flushed - if self.index == self.buffer.len() { - self.flush_inner()?; - } - - Ok(written) - } - - fn flush(&mut self) -> io::Result<()> { - self.flush_inner() - } -} - -impl<W: Write> Drop for ChunkWriter<'_, W> { - fn drop(&mut self) { - let _ = self.flush(); - } -} - -// TODO: find a better name -// -/// This enum is used to be allow the `StreamWriter` to keep -/// its inner `ChunkWriter` without wrapping it inside a -/// `ZlibEncoder`. This is used in the case that between the -/// change of state that happens when the last write of a frame -/// is performed an error occurs, which obviously has to be returned. -/// This creates the problem of where to store the writer before -/// exiting the function, and this is where `Wrapper` comes in. -/// -/// Unfortunately the `ZlibWriter` can't be used because on the -/// write following the error, `finish` would be called and that -/// would write some data even if 0 bytes where compressed. -/// -/// If the `finish` function fails then there is nothing much to -/// do as the `ChunkWriter` would get lost so the `Unrecoverable` -/// variant is used to signal that. -enum Wrapper<'a, W: Write> { - Chunk(ChunkWriter<'a, W>), - Zlib(ZlibEncoder<ChunkWriter<'a, W>>), - Unrecoverable, - /// This is used in-between, should never be matched - None, -} - -impl<'a, W: Write> Wrapper<'a, W> { - /// Like `Option::take` this returns the `Wrapper` contained - /// in `self` and replaces it with `Wrapper::None` - fn take(&mut self) -> Wrapper<'a, W> { - let mut swap = Wrapper::None; - mem::swap(self, &mut swap); - swap - } -} - -/// Streaming PNG writer -/// -/// This may silently fail in the destructor, so it is a good idea to call -/// [`finish`](#method.finish) or [`flush`] before dropping. -/// -/// [`flush`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html#tymethod.flush -pub struct StreamWriter<'a, W: Write> { - /// The option here is needed in order to access the inner `ChunkWriter` in-between - /// each frame, which is needed for writing the fcTL chunks between each frame - writer: Wrapper<'a, W>, - prev_buf: Vec<u8>, - curr_buf: Vec<u8>, - /// Amount of data already written - index: usize, - /// length of the current scanline - line_len: usize, - /// size of the frame (width * height * sample_size) - to_write: usize, - - width: u32, - height: u32, - - bpp: BytesPerPixel, - filter: FilterType, - adaptive_filter: AdaptiveFilterType, - fctl: Option<FrameControl>, - compression: Compression, -} - -impl<'a, W: Write> StreamWriter<'a, W> { - fn new(writer: ChunkOutput<'a, W>, buf_len: usize) -> Result<StreamWriter<'a, W>> { - let PartialInfo { - width, - height, - frame_control: fctl, - compression, - .. - } = writer.info; - - let bpp = writer.info.bpp_in_prediction(); - let in_len = writer.info.raw_row_length() - 1; - let filter = writer.options.filter; - let adaptive_filter = writer.options.adaptive_filter; - let prev_buf = vec![0; in_len]; - let curr_buf = vec![0; in_len]; - - let mut chunk_writer = ChunkWriter::new(writer, buf_len); - let (line_len, to_write) = chunk_writer.next_frame_info(); - chunk_writer.write_header()?; - let zlib = ZlibEncoder::new(chunk_writer, compression.to_options()); - - Ok(StreamWriter { - writer: Wrapper::Zlib(zlib), - index: 0, - prev_buf, - curr_buf, - bpp, - filter, - width, - height, - adaptive_filter, - line_len, - to_write, - fctl, - compression, - }) - } - - /// Set the used filter type for the next frame. - /// - /// The default filter is [`FilterType::Sub`] which provides a basic prediction algorithm for - /// sample values based on the previous. For a potentially better compression ratio, at the - /// cost of more complex processing, try out [`FilterType::Paeth`]. - /// - /// [`FilterType::Sub`]: enum.FilterType.html#variant.Sub - /// [`FilterType::Paeth`]: enum.FilterType.html#variant.Paeth - pub fn set_filter(&mut self, filter: FilterType) { - self.filter = filter; - } - - /// Set the adaptive filter type for the next frame. - /// - /// Adaptive filtering attempts to select the best filter for each line - /// based on heuristics which minimize the file size for compression rather - /// than use a single filter for the entire image. The default method is - /// [`AdaptiveFilterType::NonAdaptive`]. - /// - /// [`AdaptiveFilterType::NonAdaptive`]: enum.AdaptiveFilterType.html - pub fn set_adaptive_filter(&mut self, adaptive_filter: AdaptiveFilterType) { - self.adaptive_filter = adaptive_filter; - } - - /// Set the fraction of time the following frames are going to be displayed, - /// in seconds - /// - /// If the denominator is 0, it is to be treated as if it were 100 - /// (that is, the numerator then specifies 1/100ths of a second). - /// If the the value of the numerator is 0 the decoder should render the next frame - /// as quickly as possible, though viewers may impose a reasonable lower bound. - /// - /// This method will return an error if the image is not animated. - pub fn set_frame_delay(&mut self, numerator: u16, denominator: u16) -> Result<()> { - if let Some(ref mut fctl) = self.fctl { - fctl.delay_den = denominator; - fctl.delay_num = numerator; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the dimension of the following frames. - /// - /// This function will return an error when: - /// - The image is not an animated; - /// - /// - The selected dimension, considering also the current frame position, - /// goes outside the image boundaries; - /// - /// - One or both the width and height are 0; - /// - pub fn set_frame_dimension(&mut self, width: u32, height: u32) -> Result<()> { - if let Some(ref mut fctl) = self.fctl { - if Some(width) > self.width.checked_sub(fctl.x_offset) - || Some(height) > self.height.checked_sub(fctl.y_offset) - { - return Err(EncodingError::Format(FormatErrorKind::OutOfBounds.into())); - } else if width == 0 { - return Err(EncodingError::Format(FormatErrorKind::ZeroWidth.into())); - } else if height == 0 { - return Err(EncodingError::Format(FormatErrorKind::ZeroHeight.into())); - } - fctl.width = width; - fctl.height = height; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the position of the following frames. - /// - /// An error will be returned if: - /// - The image is not animated; - /// - /// - The selected position, considering also the current frame dimension, - /// goes outside the image boundaries; - /// - pub fn set_frame_position(&mut self, x: u32, y: u32) -> Result<()> { - if let Some(ref mut fctl) = self.fctl { - if Some(x) > self.width.checked_sub(fctl.width) - || Some(y) > self.height.checked_sub(fctl.height) - { - return Err(EncodingError::Format(FormatErrorKind::OutOfBounds.into())); - } - fctl.x_offset = x; - fctl.y_offset = y; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the frame dimension to occupy all the image, starting from - /// the current position. - /// - /// To reset the frame to the full image size [`reset_frame_position`] - /// should be called first. - /// - /// This method will return an error if the image is not animated. - /// - /// [`reset_frame_position`]: struct.Writer.html#method.reset_frame_position - pub fn reset_frame_dimension(&mut self) -> Result<()> { - if let Some(ref mut fctl) = self.fctl { - fctl.width = self.width - fctl.x_offset; - fctl.height = self.height - fctl.y_offset; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the frame position to (0, 0). - /// - /// Equivalent to calling [`set_frame_position(0, 0)`]. - /// - /// This method will return an error if the image is not animated. - /// - /// [`set_frame_position(0, 0)`]: struct.Writer.html#method.set_frame_position - pub fn reset_frame_position(&mut self) -> Result<()> { - if let Some(ref mut fctl) = self.fctl { - fctl.x_offset = 0; - fctl.y_offset = 0; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the blend operation for the following frames. - /// - /// The blend operation specifies whether the frame is to be alpha blended - /// into the current output buffer content, or whether it should completely - /// replace its region in the output buffer. - /// - /// See the [`BlendOp`] documentation for the possible values and their effects. - /// - /// *Note that for the first frame the two blend modes are functionally - /// equivalent due to the clearing of the output buffer at the beginning - /// of each play.* - /// - /// This method will return an error if the image is not animated. - /// - /// [`BlendOP`]: enum.BlendOp.html - pub fn set_blend_op(&mut self, op: BlendOp) -> Result<()> { - if let Some(ref mut fctl) = self.fctl { - fctl.blend_op = op; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - /// Set the dispose operation for the following frames. - /// - /// The dispose operation specifies how the output buffer should be changed - /// at the end of the delay (before rendering the next frame) - /// - /// See the [`DisposeOp`] documentation for the possible values and their effects. - /// - /// *Note that if the first frame uses [`DisposeOp::Previous`] - /// it will be treated as [`DisposeOp::Background`].* - /// - /// This method will return an error if the image is not animated. - /// - /// [`DisposeOp`]: ../common/enum.BlendOp.html - /// [`DisposeOp::Previous`]: ../common/enum.BlendOp.html#variant.Previous - /// [`DisposeOp::Background`]: ../common/enum.BlendOp.html#variant.Background - pub fn set_dispose_op(&mut self, op: DisposeOp) -> Result<()> { - if let Some(ref mut fctl) = self.fctl { - fctl.dispose_op = op; - Ok(()) - } else { - Err(EncodingError::Format(FormatErrorKind::NotAnimated.into())) - } - } - - pub fn finish(mut self) -> Result<()> { - if self.to_write > 0 { - let err = FormatErrorKind::MissingData(self.to_write).into(); - return Err(EncodingError::Format(err)); - } - - // TODO: call `writer.finish` somehow? - self.flush()?; - - if let Wrapper::Chunk(wrt) = self.writer.take() { - wrt.writer.validate_sequence_done()?; - } - - Ok(()) - } - - /// Flushes the buffered chunk, checks if it was the last frame, - /// writes the next frame header and gets the next frame scanline size - /// and image size. - /// NOTE: This method must only be called when the writer is the variant Chunk(_) - fn new_frame(&mut self) -> Result<()> { - let wrt = match &mut self.writer { - Wrapper::Chunk(wrt) => wrt, - Wrapper::Unrecoverable => { - let err = FormatErrorKind::Unrecoverable.into(); - return Err(EncodingError::Format(err)); - } - Wrapper::Zlib(_) => unreachable!("never called on a half-finished frame"), - Wrapper::None => unreachable!(), - }; - wrt.flush()?; - wrt.writer.validate_new_image()?; - - if let Some(fctl) = self.fctl { - wrt.set_fctl(fctl); - } - let (scansize, size) = wrt.next_frame_info(); - self.line_len = scansize; - self.to_write = size; - - wrt.write_header()?; - wrt.writer.increment_images_written(); - - // now it can be taken because the next statements cannot cause any errors - match self.writer.take() { - Wrapper::Chunk(wrt) => { - let encoder = ZlibEncoder::new(wrt, self.compression.to_options()); - self.writer = Wrapper::Zlib(encoder); - } - _ => unreachable!(), - }; - - Ok(()) - } -} - -impl<'a, W: Write> Write for StreamWriter<'a, W> { - fn write(&mut self, mut data: &[u8]) -> io::Result<usize> { - if let Wrapper::Unrecoverable = self.writer { - let err = FormatErrorKind::Unrecoverable.into(); - return Err(EncodingError::Format(err).into()); - } - - if data.is_empty() { - return Ok(0); - } - - if self.to_write == 0 { - match self.writer.take() { - Wrapper::Zlib(wrt) => match wrt.finish() { - Ok(chunk) => self.writer = Wrapper::Chunk(chunk), - Err(err) => { - self.writer = Wrapper::Unrecoverable; - return Err(err); - } - }, - chunk @ Wrapper::Chunk(_) => self.writer = chunk, - Wrapper::Unrecoverable => unreachable!(), - Wrapper::None => unreachable!(), - }; - - // Transition Wrapper::Chunk to Wrapper::Zlib. - self.new_frame()?; - } - - let written = data.read(&mut self.curr_buf[..self.line_len][self.index..])?; - self.index += written; - self.to_write -= written; - - if self.index == self.line_len { - // TODO: reuse this buffer between rows. - let mut filtered = vec![0; self.curr_buf.len()]; - let filter_type = filter( - self.filter, - self.adaptive_filter, - self.bpp, - &self.prev_buf, - &self.curr_buf, - &mut filtered, - ); - // This can't fail as the other variant is used only to allow the zlib encoder to finish - let wrt = match &mut self.writer { - Wrapper::Zlib(wrt) => wrt, - _ => unreachable!(), - }; - - wrt.write_all(&[filter_type as u8])?; - wrt.write_all(&filtered)?; - mem::swap(&mut self.prev_buf, &mut self.curr_buf); - self.index = 0; - } - - Ok(written) - } - - fn flush(&mut self) -> io::Result<()> { - match &mut self.writer { - Wrapper::Zlib(wrt) => wrt.flush()?, - Wrapper::Chunk(wrt) => wrt.flush()?, - // This handles both the case where we entered an unrecoverable state after zlib - // decoding failure and after a panic while we had taken the chunk/zlib reader. - Wrapper::Unrecoverable | Wrapper::None => { - let err = FormatErrorKind::Unrecoverable.into(); - return Err(EncodingError::Format(err).into()); - } - } - - if self.index > 0 { - let err = FormatErrorKind::WrittenTooMuch(self.index).into(); - return Err(EncodingError::Format(err).into()); - } - - Ok(()) - } -} - -impl<W: Write> Drop for StreamWriter<'_, W> { - fn drop(&mut self) { - let _ = self.flush(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Decoder; - - use rand::{thread_rng, Rng}; - use std::fs::File; - use std::io::{Cursor, Write}; - use std::{cmp, io}; - - #[test] - fn roundtrip() { - // More loops = more random testing, but also more test wait time - for _ in 0..10 { - for path in glob::glob("tests/pngsuite/*.png") - .unwrap() - .map(|r| r.unwrap()) - { - if path.file_name().unwrap().to_str().unwrap().starts_with('x') { - // x* files are expected to fail to decode - continue; - } - eprintln!("{}", path.display()); - // Decode image - let decoder = Decoder::new(File::open(path).unwrap()); - let mut reader = decoder.read_info().unwrap(); - let mut buf = vec![0; reader.output_buffer_size()]; - let info = reader.next_frame(&mut buf).unwrap(); - // Encode decoded image - let mut out = Vec::new(); - { - let mut wrapper = RandomChunkWriter { - rng: thread_rng(), - w: &mut out, - }; - - let mut encoder = Encoder::new(&mut wrapper, info.width, info.height); - encoder.set_color(info.color_type); - encoder.set_depth(info.bit_depth); - if let Some(palette) = &reader.info().palette { - encoder.set_palette(palette.clone()); - } - let mut encoder = encoder.write_header().unwrap(); - encoder.write_image_data(&buf).unwrap(); - } - // Decode encoded decoded image - let decoder = Decoder::new(&*out); - let mut reader = decoder.read_info().unwrap(); - let mut buf2 = vec![0; reader.output_buffer_size()]; - reader.next_frame(&mut buf2).unwrap(); - // check if the encoded image is ok: - assert_eq!(buf, buf2); - } - } - } - - #[test] - fn roundtrip_stream() { - // More loops = more random testing, but also more test wait time - for _ in 0..10 { - for path in glob::glob("tests/pngsuite/*.png") - .unwrap() - .map(|r| r.unwrap()) - { - if path.file_name().unwrap().to_str().unwrap().starts_with('x') { - // x* files are expected to fail to decode - continue; - } - // Decode image - let decoder = Decoder::new(File::open(path).unwrap()); - let mut reader = decoder.read_info().unwrap(); - let mut buf = vec![0; reader.output_buffer_size()]; - let info = reader.next_frame(&mut buf).unwrap(); - // Encode decoded image - let mut out = Vec::new(); - { - let mut wrapper = RandomChunkWriter { - rng: thread_rng(), - w: &mut out, - }; - - let mut encoder = Encoder::new(&mut wrapper, info.width, info.height); - encoder.set_color(info.color_type); - encoder.set_depth(info.bit_depth); - if let Some(palette) = &reader.info().palette { - encoder.set_palette(palette.clone()); - } - let mut encoder = encoder.write_header().unwrap(); - let mut stream_writer = encoder.stream_writer().unwrap(); - - let mut outer_wrapper = RandomChunkWriter { - rng: thread_rng(), - w: &mut stream_writer, - }; - - outer_wrapper.write_all(&buf).unwrap(); - } - // Decode encoded decoded image - let decoder = Decoder::new(&*out); - let mut reader = decoder.read_info().unwrap(); - let mut buf2 = vec![0; reader.output_buffer_size()]; - reader.next_frame(&mut buf2).unwrap(); - // check if the encoded image is ok: - assert_eq!(buf, buf2); - } - } - } - - #[test] - fn image_palette() -> Result<()> { - for &bit_depth in &[1u8, 2, 4, 8] { - // Do a reference decoding, choose a fitting palette image from pngsuite - let path = format!("tests/pngsuite/basn3p0{}.png", bit_depth); - let decoder = Decoder::new(File::open(&path).unwrap()); - let mut reader = decoder.read_info().unwrap(); - - let mut decoded_pixels = vec![0; reader.output_buffer_size()]; - let info = reader.info(); - assert_eq!( - info.width as usize * info.height as usize * usize::from(bit_depth), - decoded_pixels.len() * 8 - ); - let info = reader.next_frame(&mut decoded_pixels).unwrap(); - let indexed_data = decoded_pixels; - - let palette = reader.info().palette.as_ref().unwrap(); - let mut out = Vec::new(); - { - let mut encoder = Encoder::new(&mut out, info.width, info.height); - encoder.set_depth(BitDepth::from_u8(bit_depth).unwrap()); - encoder.set_color(ColorType::Indexed); - encoder.set_palette(palette.as_ref()); - - let mut writer = encoder.write_header().unwrap(); - writer.write_image_data(&indexed_data).unwrap(); - } - - // Decode re-encoded image - let decoder = Decoder::new(&*out); - let mut reader = decoder.read_info().unwrap(); - let mut redecoded = vec![0; reader.output_buffer_size()]; - reader.next_frame(&mut redecoded).unwrap(); - // check if the encoded image is ok: - assert_eq!(indexed_data, redecoded); - } - Ok(()) - } - - #[test] - fn expect_error_on_wrong_image_len() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - let mut encoder = Encoder::new(writer, width as u32, height as u32); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Rgb); - let mut png_writer = encoder.write_header()?; - - let correct_image_size = width * height * 3; - let image = vec![0u8; correct_image_size + 1]; - let result = png_writer.write_image_data(image.as_ref()); - assert!(result.is_err()); - - Ok(()) - } - - #[test] - fn expect_error_on_empty_image() -> Result<()> { - let output = vec![0u8; 1024]; - let mut writer = Cursor::new(output); - - let encoder = Encoder::new(&mut writer, 0, 0); - assert!(encoder.write_header().is_err()); - - let encoder = Encoder::new(&mut writer, 100, 0); - assert!(encoder.write_header().is_err()); - - let encoder = Encoder::new(&mut writer, 0, 100); - assert!(encoder.write_header().is_err()); - - Ok(()) - } - - #[test] - fn expect_error_on_invalid_bit_depth_color_type_combination() -> Result<()> { - let output = vec![0u8; 1024]; - let mut writer = Cursor::new(output); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::One); - encoder.set_color(ColorType::Rgb); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::One); - encoder.set_color(ColorType::GrayscaleAlpha); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::One); - encoder.set_color(ColorType::Rgba); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Two); - encoder.set_color(ColorType::Rgb); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Two); - encoder.set_color(ColorType::GrayscaleAlpha); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Two); - encoder.set_color(ColorType::Rgba); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Four); - encoder.set_color(ColorType::Rgb); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Four); - encoder.set_color(ColorType::GrayscaleAlpha); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Four); - encoder.set_color(ColorType::Rgba); - assert!(encoder.write_header().is_err()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Sixteen); - encoder.set_color(ColorType::Indexed); - assert!(encoder.write_header().is_err()); - - Ok(()) - } - - #[test] - fn can_write_header_with_valid_bit_depth_color_type_combination() -> Result<()> { - let output = vec![0u8; 1024]; - let mut writer = Cursor::new(output); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::One); - encoder.set_color(ColorType::Grayscale); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::One); - encoder.set_color(ColorType::Indexed); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Two); - encoder.set_color(ColorType::Grayscale); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Two); - encoder.set_color(ColorType::Indexed); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Four); - encoder.set_color(ColorType::Grayscale); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Four); - encoder.set_color(ColorType::Indexed); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Rgb); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Indexed); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::GrayscaleAlpha); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Rgba); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Sixteen); - encoder.set_color(ColorType::Grayscale); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Sixteen); - encoder.set_color(ColorType::Rgb); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Sixteen); - encoder.set_color(ColorType::GrayscaleAlpha); - assert!(encoder.write_header().is_ok()); - - let mut encoder = Encoder::new(&mut writer, 1, 1); - encoder.set_depth(BitDepth::Sixteen); - encoder.set_color(ColorType::Rgba); - assert!(encoder.write_header().is_ok()); - - Ok(()) - } - - #[test] - fn all_filters_roundtrip() -> io::Result<()> { - let pixel: Vec<_> = (0..48).collect(); - - let roundtrip = |filter: FilterType| -> io::Result<()> { - let mut buffer = vec![]; - let mut encoder = Encoder::new(&mut buffer, 4, 4); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Rgb); - encoder.set_filter(filter); - encoder.write_header()?.write_image_data(&pixel)?; - - let decoder = crate::Decoder::new(Cursor::new(buffer)); - let mut reader = decoder.read_info()?; - let info = reader.info(); - assert_eq!(info.width, 4); - assert_eq!(info.height, 4); - let mut dest = vec![0; pixel.len()]; - reader.next_frame(&mut dest)?; - assert_eq!(dest, pixel, "Deviation with filter type {:?}", filter); - - Ok(()) - }; - - roundtrip(FilterType::NoFilter)?; - roundtrip(FilterType::Sub)?; - roundtrip(FilterType::Up)?; - roundtrip(FilterType::Avg)?; - roundtrip(FilterType::Paeth)?; - - Ok(()) - } - - #[test] - fn some_gamma_roundtrip() -> io::Result<()> { - let pixel: Vec<_> = (0..48).collect(); - - let roundtrip = |gamma: Option<ScaledFloat>| -> io::Result<()> { - let mut buffer = vec![]; - let mut encoder = Encoder::new(&mut buffer, 4, 4); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Rgb); - encoder.set_filter(FilterType::Avg); - if let Some(gamma) = gamma { - encoder.set_source_gamma(gamma); - } - encoder.write_header()?.write_image_data(&pixel)?; - - let decoder = crate::Decoder::new(Cursor::new(buffer)); - let mut reader = decoder.read_info()?; - assert_eq!( - reader.info().source_gamma, - gamma, - "Deviation with gamma {:?}", - gamma - ); - let mut dest = vec![0; pixel.len()]; - let info = reader.next_frame(&mut dest)?; - assert_eq!(info.width, 4); - assert_eq!(info.height, 4); - - Ok(()) - }; - - roundtrip(None)?; - roundtrip(Some(ScaledFloat::new(0.35)))?; - roundtrip(Some(ScaledFloat::new(0.45)))?; - roundtrip(Some(ScaledFloat::new(0.55)))?; - roundtrip(Some(ScaledFloat::new(0.7)))?; - roundtrip(Some(ScaledFloat::new(1.0)))?; - roundtrip(Some(ScaledFloat::new(2.5)))?; - - Ok(()) - } - - #[test] - fn write_image_chunks_beyond_first() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - - // Not an animation but we should still be able to write multiple images - // See issue: <https://github.com/image-rs/image-png/issues/301> - // This is technically all valid png so there is no issue with correctness. - let mut encoder = Encoder::new(writer, width, height); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - let mut png_writer = encoder.write_header()?; - - for _ in 0..3 { - let correct_image_size = (width * height) as usize; - let image = vec![0u8; correct_image_size]; - png_writer.write_image_data(image.as_ref())?; - } - - Ok(()) - } - - #[test] - fn image_validate_sequence_without_animation() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - - let mut encoder = Encoder::new(writer, width, height); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - encoder.validate_sequence(true); - let mut png_writer = encoder.write_header()?; - - let correct_image_size = (width * height) as usize; - let image = vec![0u8; correct_image_size]; - png_writer.write_image_data(image.as_ref())?; - - assert!(png_writer.write_image_data(image.as_ref()).is_err()); - Ok(()) - } - - #[test] - fn image_validate_animation() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - let correct_image_size = (width * height) as usize; - let image = vec![0u8; correct_image_size]; - - let mut encoder = Encoder::new(writer, width, height); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - encoder.set_animated(1, 0)?; - encoder.validate_sequence(true); - let mut png_writer = encoder.write_header()?; - - png_writer.write_image_data(image.as_ref())?; - - Ok(()) - } - - #[test] - fn image_validate_animation2() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - let correct_image_size = (width * height) as usize; - let image = vec![0u8; correct_image_size]; - - let mut encoder = Encoder::new(writer, width, height); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - encoder.set_animated(2, 0)?; - encoder.validate_sequence(true); - let mut png_writer = encoder.write_header()?; - - png_writer.write_image_data(image.as_ref())?; - png_writer.write_image_data(image.as_ref())?; - png_writer.finish()?; - - Ok(()) - } - - #[test] - fn image_validate_animation_sep_def_image() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - let correct_image_size = (width * height) as usize; - let image = vec![0u8; correct_image_size]; - - let mut encoder = Encoder::new(writer, width, height); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - encoder.set_animated(1, 0)?; - encoder.set_sep_def_img(true)?; - encoder.validate_sequence(true); - let mut png_writer = encoder.write_header()?; - - png_writer.write_image_data(image.as_ref())?; - png_writer.write_image_data(image.as_ref())?; - png_writer.finish()?; - - Ok(()) - } - - #[test] - fn image_validate_missing_image() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - - let mut encoder = Encoder::new(writer, width, height); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - encoder.validate_sequence(true); - let png_writer = encoder.write_header()?; - - assert!(png_writer.finish().is_err()); - Ok(()) - } - - #[test] - fn image_validate_missing_animated_frame() -> Result<()> { - let width = 10; - let height = 10; - - let output = vec![0u8; 1024]; - let writer = Cursor::new(output); - let correct_image_size = (width * height) as usize; - let image = vec![0u8; correct_image_size]; - - let mut encoder = Encoder::new(writer, width, height); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - encoder.set_animated(2, 0)?; - encoder.validate_sequence(true); - let mut png_writer = encoder.write_header()?; - - png_writer.write_image_data(image.as_ref())?; - assert!(png_writer.finish().is_err()); - - Ok(()) - } - - #[test] - fn issue_307_stream_validation() -> Result<()> { - let output = vec![0u8; 1024]; - let mut cursor = Cursor::new(output); - - let encoder = Encoder::new(&mut cursor, 1, 1); // Create a 1-pixel image - let mut writer = encoder.write_header()?; - let mut stream = writer.stream_writer()?; - - let written = stream.write(&[1, 2, 3, 4])?; - assert_eq!(written, 1); - stream.finish()?; - drop(writer); - - { - cursor.set_position(0); - let mut decoder = Decoder::new(cursor).read_info().expect("A valid image"); - let mut buffer = [0u8; 1]; - decoder.next_frame(&mut buffer[..]).expect("Valid read"); - assert_eq!(buffer, [1]); - } - - Ok(()) - } - - #[test] - fn stream_filtering() -> Result<()> { - let output = vec![0u8; 1024]; - let mut cursor = Cursor::new(output); - - let mut encoder = Encoder::new(&mut cursor, 8, 8); - encoder.set_color(ColorType::Rgba); - encoder.set_filter(FilterType::Paeth); - let mut writer = encoder.write_header()?; - let mut stream = writer.stream_writer()?; - - for _ in 0..8 { - let written = stream.write(&[1; 32])?; - assert_eq!(written, 32); - } - stream.finish()?; - drop(writer); - - { - cursor.set_position(0); - let mut decoder = Decoder::new(cursor).read_info().expect("A valid image"); - let mut buffer = [0u8; 256]; - decoder.next_frame(&mut buffer[..]).expect("Valid read"); - assert_eq!(buffer, [1; 256]); - } - - Ok(()) - } - - #[test] - #[cfg(all(unix, not(target_pointer_width = "32")))] - fn exper_error_on_huge_chunk() -> Result<()> { - // Okay, so we want a proper 4 GB chunk but not actually spend the memory for reserving it. - // Let's rely on overcommit? Otherwise we got the rather dumb option of mmap-ing /dev/zero. - let empty = vec![0; 1usize << 31]; - let writer = Cursor::new(vec![0u8; 1024]); - - let mut encoder = Encoder::new(writer, 10, 10); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - let mut png_writer = encoder.write_header()?; - - assert!(png_writer.write_chunk(chunk::fdAT, &empty).is_err()); - Ok(()) - } - - #[test] - #[cfg(all(unix, not(target_pointer_width = "32")))] - fn exper_error_on_non_u32_chunk() -> Result<()> { - // Okay, so we want a proper 4 GB chunk but not actually spend the memory for reserving it. - // Let's rely on overcommit? Otherwise we got the rather dumb option of mmap-ing /dev/zero. - let empty = vec![0; 1usize << 32]; - let writer = Cursor::new(vec![0u8; 1024]); - - let mut encoder = Encoder::new(writer, 10, 10); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - let mut png_writer = encoder.write_header()?; - - assert!(png_writer.write_chunk(chunk::fdAT, &empty).is_err()); - Ok(()) - } - - #[test] - fn finish_drops_inner_writer() -> Result<()> { - struct NoWriter<'flag>(&'flag mut bool); - - impl Write for NoWriter<'_> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - impl Drop for NoWriter<'_> { - fn drop(&mut self) { - *self.0 = true; - } - } - - let mut flag = false; - - { - let mut encoder = Encoder::new(NoWriter(&mut flag), 10, 10); - encoder.set_depth(BitDepth::Eight); - encoder.set_color(ColorType::Grayscale); - - let mut writer = encoder.write_header()?; - writer.write_image_data(&[0; 100])?; - writer.finish()?; - } - - assert!(flag, "PNG finished but writer was not dropped"); - Ok(()) - } - - /// A Writer that only writes a few bytes at a time - struct RandomChunkWriter<R: Rng, W: Write> { - rng: R, - w: W, - } - - impl<R: Rng, W: Write> Write for RandomChunkWriter<R, W> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - // choose a random length to write - let len = cmp::min(self.rng.gen_range(1..50), buf.len()); - - self.w.write(&buf[0..len]) - } - - fn flush(&mut self) -> io::Result<()> { - self.w.flush() - } - } -} - -/// Mod to encapsulate the converters depending on the `deflate` crate. -/// -/// Since this only contains trait impls, there is no need to make this public, they are simply -/// available when the mod is compiled as well. -impl Compression { - fn to_options(self) -> flate2::Compression { - #[allow(deprecated)] - match self { - Compression::Default => flate2::Compression::default(), - Compression::Fast => flate2::Compression::fast(), - Compression::Best => flate2::Compression::best(), - #[allow(deprecated)] - Compression::Huffman => flate2::Compression::none(), - #[allow(deprecated)] - Compression::Rle => flate2::Compression::none(), - } - } -} |