summaryrefslogtreecommitdiff
path: root/vendor/image/src/codecs/png.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/codecs/png.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/codecs/png.rs')
-rw-r--r--vendor/image/src/codecs/png.rs778
1 files changed, 778 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/png.rs b/vendor/image/src/codecs/png.rs
new file mode 100644
index 0000000..b9f98ce
--- /dev/null
+++ b/vendor/image/src/codecs/png.rs
@@ -0,0 +1,778 @@
+//! Decoding and Encoding of PNG Images
+//!
+//! PNG (Portable Network Graphics) is an image format that supports lossless compression.
+//!
+//! # Related Links
+//! * <http://www.w3.org/TR/PNG/> - The PNG Specification
+//!
+
+use std::convert::TryFrom;
+use std::fmt;
+use std::io::{self, Read, Write};
+
+use num_rational::Ratio;
+use png::{BlendOp, DisposeOp};
+
+use crate::animation::{Delay, Frame, Frames};
+use crate::color::{Blend, ColorType, ExtendedColorType};
+use crate::error::{
+ DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind,
+ ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
+};
+use crate::image::{AnimationDecoder, ImageDecoder, ImageEncoder, ImageFormat};
+use crate::io::Limits;
+use crate::{DynamicImage, GenericImage, ImageBuffer, Luma, LumaA, Rgb, Rgba, RgbaImage};
+
+// http://www.w3.org/TR/PNG-Structure.html
+// The first eight bytes of a PNG file always contain the following (decimal) values:
+pub(crate) const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
+
+/// Png Reader
+///
+/// This reader will try to read the png one row at a time,
+/// however for interlaced png files this is not possible and
+/// these are therefore read at once.
+pub struct PngReader<R: Read> {
+ reader: png::Reader<R>,
+ buffer: Vec<u8>,
+ index: usize,
+}
+
+impl<R: Read> PngReader<R> {
+ fn new(mut reader: png::Reader<R>) -> ImageResult<PngReader<R>> {
+ let len = reader.output_buffer_size();
+ // Since interlaced images do not come in
+ // scanline order it is almost impossible to
+ // read them in a streaming fashion, however
+ // this shouldn't be a too big of a problem
+ // as most interlaced images should fit in memory.
+ let buffer = if reader.info().interlaced {
+ let mut buffer = vec![0; len];
+ reader
+ .next_frame(&mut buffer)
+ .map_err(ImageError::from_png)?;
+ buffer
+ } else {
+ Vec::new()
+ };
+
+ Ok(PngReader {
+ reader,
+ buffer,
+ index: 0,
+ })
+ }
+}
+
+impl<R: Read> Read for PngReader<R> {
+ fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
+ // io::Write::write for slice cannot fail
+ let readed = buf.write(&self.buffer[self.index..]).unwrap();
+
+ let mut bytes = readed;
+ self.index += readed;
+
+ while self.index >= self.buffer.len() {
+ match self.reader.next_row()? {
+ Some(row) => {
+ // Faster to copy directly to external buffer
+ let readed = buf.write(row.data()).unwrap();
+ bytes += readed;
+
+ self.buffer = row.data()[readed..].to_owned();
+ self.index = 0;
+ }
+ None => return Ok(bytes),
+ }
+ }
+
+ Ok(bytes)
+ }
+
+ fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
+ let mut bytes = self.buffer.len();
+ if buf.is_empty() {
+ std::mem::swap(&mut self.buffer, buf);
+ } else {
+ buf.extend_from_slice(&self.buffer);
+ self.buffer.clear();
+ }
+
+ self.index = 0;
+
+ while let Some(row) = self.reader.next_row()? {
+ buf.extend_from_slice(row.data());
+ bytes += row.data().len();
+ }
+
+ Ok(bytes)
+ }
+}
+
+/// PNG decoder
+pub struct PngDecoder<R: Read> {
+ color_type: ColorType,
+ reader: png::Reader<R>,
+}
+
+impl<R: Read> PngDecoder<R> {
+ /// Creates a new decoder that decodes from the stream ```r```
+ pub fn new(r: R) -> ImageResult<PngDecoder<R>> {
+ Self::with_limits(r, Limits::default())
+ }
+
+ /// Creates a new decoder that decodes from the stream ```r``` with the given limits.
+ pub fn with_limits(r: R, limits: Limits) -> ImageResult<PngDecoder<R>> {
+ limits.check_support(&crate::io::LimitSupport::default())?;
+
+ let max_bytes = usize::try_from(limits.max_alloc.unwrap_or(u64::MAX)).unwrap_or(usize::MAX);
+ let mut decoder = png::Decoder::new_with_limits(r, png::Limits { bytes: max_bytes });
+
+ let info = decoder.read_header_info().map_err(ImageError::from_png)?;
+ limits.check_dimensions(info.width, info.height)?;
+
+ // By default the PNG decoder will scale 16 bpc to 8 bpc, so custom
+ // transformations must be set. EXPAND preserves the default behavior
+ // expanding bpc < 8 to 8 bpc.
+ decoder.set_transformations(png::Transformations::EXPAND);
+ let reader = decoder.read_info().map_err(ImageError::from_png)?;
+ let (color_type, bits) = reader.output_color_type();
+ let color_type = match (color_type, bits) {
+ (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8,
+ (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16,
+ (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8,
+ (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16,
+ (png::ColorType::Rgb, png::BitDepth::Eight) => ColorType::Rgb8,
+ (png::ColorType::Rgb, png::BitDepth::Sixteen) => ColorType::Rgb16,
+ (png::ColorType::Rgba, png::BitDepth::Eight) => ColorType::Rgba8,
+ (png::ColorType::Rgba, png::BitDepth::Sixteen) => ColorType::Rgba16,
+
+ (png::ColorType::Grayscale, png::BitDepth::One) => {
+ return Err(unsupported_color(ExtendedColorType::L1))
+ }
+ (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => {
+ return Err(unsupported_color(ExtendedColorType::La1))
+ }
+ (png::ColorType::Rgb, png::BitDepth::One) => {
+ return Err(unsupported_color(ExtendedColorType::Rgb1))
+ }
+ (png::ColorType::Rgba, png::BitDepth::One) => {
+ return Err(unsupported_color(ExtendedColorType::Rgba1))
+ }
+
+ (png::ColorType::Grayscale, png::BitDepth::Two) => {
+ return Err(unsupported_color(ExtendedColorType::L2))
+ }
+ (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => {
+ return Err(unsupported_color(ExtendedColorType::La2))
+ }
+ (png::ColorType::Rgb, png::BitDepth::Two) => {
+ return Err(unsupported_color(ExtendedColorType::Rgb2))
+ }
+ (png::ColorType::Rgba, png::BitDepth::Two) => {
+ return Err(unsupported_color(ExtendedColorType::Rgba2))
+ }
+
+ (png::ColorType::Grayscale, png::BitDepth::Four) => {
+ return Err(unsupported_color(ExtendedColorType::L4))
+ }
+ (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => {
+ return Err(unsupported_color(ExtendedColorType::La4))
+ }
+ (png::ColorType::Rgb, png::BitDepth::Four) => {
+ return Err(unsupported_color(ExtendedColorType::Rgb4))
+ }
+ (png::ColorType::Rgba, png::BitDepth::Four) => {
+ return Err(unsupported_color(ExtendedColorType::Rgba4))
+ }
+
+ (png::ColorType::Indexed, bits) => {
+ return Err(unsupported_color(ExtendedColorType::Unknown(bits as u8)))
+ }
+ };
+
+ Ok(PngDecoder { color_type, reader })
+ }
+
+ /// Turn this into an iterator over the animation frames.
+ ///
+ /// Reading the complete animation requires more memory than reading the data from the IDAT
+ /// frame–multiple frame buffers need to be reserved at the same time. We further do not
+ /// support compositing 16-bit colors. In any case this would be lossy as the interface of
+ /// animation decoders does not support 16-bit colors.
+ ///
+ /// If something is not supported or a limit is violated then the decoding step that requires
+ /// them will fail and an error will be returned instead of the frame. No further frames will
+ /// be returned.
+ pub fn apng(self) -> ApngDecoder<R> {
+ ApngDecoder::new(self)
+ }
+
+ /// Returns if the image contains an animation.
+ ///
+ /// Note that the file itself decides if the default image is considered to be part of the
+ /// animation. When it is not the common interpretation is to use it as a thumbnail.
+ ///
+ /// If a non-animated image is converted into an `ApngDecoder` then its iterator is empty.
+ pub fn is_apng(&self) -> bool {
+ self.reader.info().animation_control.is_some()
+ }
+}
+
+fn unsupported_color(ect: ExtendedColorType) -> ImageError {
+ ImageError::Unsupported(UnsupportedError::from_format_and_kind(
+ ImageFormat::Png.into(),
+ UnsupportedErrorKind::Color(ect),
+ ))
+}
+
+impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder<R> {
+ type Reader = PngReader<R>;
+
+ fn dimensions(&self) -> (u32, u32) {
+ self.reader.info().size()
+ }
+
+ fn color_type(&self) -> ColorType {
+ self.color_type
+ }
+
+ fn icc_profile(&mut self) -> Option<Vec<u8>> {
+ self.reader.info().icc_profile.as_ref().map(|x| x.to_vec())
+ }
+
+ fn into_reader(self) -> ImageResult<Self::Reader> {
+ PngReader::new(self.reader)
+ }
+
+ fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
+ use byteorder::{BigEndian, ByteOrder, NativeEndian};
+
+ assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
+ self.reader.next_frame(buf).map_err(ImageError::from_png)?;
+ // PNG images are big endian. For 16 bit per channel and larger types,
+ // the buffer may need to be reordered to native endianness per the
+ // contract of `read_image`.
+ // TODO: assumes equal channel bit depth.
+ let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count();
+
+ match bpc {
+ 1 => (), // No reodering necessary for u8
+ 2 => buf.chunks_mut(2).for_each(|c| {
+ let v = BigEndian::read_u16(c);
+ NativeEndian::write_u16(c, v)
+ }),
+ _ => unreachable!(),
+ }
+ Ok(())
+ }
+
+ fn scanline_bytes(&self) -> u64 {
+ let width = self.reader.info().width;
+ self.reader.output_line_size(width) as u64
+ }
+}
+
+/// An [`AnimationDecoder`] adapter of [`PngDecoder`].
+///
+/// See [`PngDecoder::apng`] for more information.
+///
+/// [`AnimationDecoder`]: ../trait.AnimationDecoder.html
+/// [`PngDecoder`]: struct.PngDecoder.html
+/// [`PngDecoder::apng`]: struct.PngDecoder.html#method.apng
+pub struct ApngDecoder<R: Read> {
+ inner: PngDecoder<R>,
+ /// The current output buffer.
+ current: RgbaImage,
+ /// The previous output buffer, used for dispose op previous.
+ previous: RgbaImage,
+ /// The dispose op of the current frame.
+ dispose: DisposeOp,
+ /// The number of image still expected to be able to load.
+ remaining: u32,
+ /// The next (first) image is the thumbnail.
+ has_thumbnail: bool,
+}
+
+impl<R: Read> ApngDecoder<R> {
+ fn new(inner: PngDecoder<R>) -> Self {
+ let (width, height) = inner.dimensions();
+ let info = inner.reader.info();
+ let remaining = match info.animation_control() {
+ // The expected number of fcTL in the remaining image.
+ Some(actl) => actl.num_frames,
+ None => 0,
+ };
+ // If the IDAT has no fcTL then it is not part of the animation counted by
+ // num_frames. All following fdAT chunks must be preceded by an fcTL
+ let has_thumbnail = info.frame_control.is_none();
+ ApngDecoder {
+ inner,
+ // TODO: should we delay this allocation? At least if we support limits we should.
+ current: RgbaImage::new(width, height),
+ previous: RgbaImage::new(width, height),
+ dispose: DisposeOp::Background,
+ remaining,
+ has_thumbnail,
+ }
+ }
+
+ // TODO: thumbnail(&mut self) -> Option<impl ImageDecoder<'_>>
+
+ /// Decode one subframe and overlay it on the canvas.
+ fn mix_next_frame(&mut self) -> Result<Option<&RgbaImage>, ImageError> {
+ // Remove this image from remaining.
+ self.remaining = match self.remaining.checked_sub(1) {
+ None => return Ok(None),
+ Some(next) => next,
+ };
+
+ // Shorten ourselves to 0 in case of error.
+ let remaining = self.remaining;
+ self.remaining = 0;
+
+ // Skip the thumbnail that is not part of the animation.
+ if self.has_thumbnail {
+ self.has_thumbnail = false;
+ let mut buffer = vec![0; self.inner.reader.output_buffer_size()];
+ self.inner
+ .reader
+ .next_frame(&mut buffer)
+ .map_err(ImageError::from_png)?;
+ }
+
+ self.animatable_color_type()?;
+
+ // Dispose of the previous frame.
+ match self.dispose {
+ DisposeOp::None => {
+ self.previous.clone_from(&self.current);
+ }
+ DisposeOp::Background => {
+ self.previous.clone_from(&self.current);
+ self.current
+ .pixels_mut()
+ .for_each(|pixel| *pixel = Rgba([0, 0, 0, 0]));
+ }
+ DisposeOp::Previous => {
+ self.current.clone_from(&self.previous);
+ }
+ }
+
+ // Read next frame data.
+ let mut buffer = vec![0; self.inner.reader.output_buffer_size()];
+ self.inner
+ .reader
+ .next_frame(&mut buffer)
+ .map_err(ImageError::from_png)?;
+ let info = self.inner.reader.info();
+
+ // Find out how to interpret the decoded frame.
+ let (width, height, px, py, blend);
+ match info.frame_control() {
+ None => {
+ width = info.width;
+ height = info.height;
+ px = 0;
+ py = 0;
+ blend = BlendOp::Source;
+ }
+ Some(fc) => {
+ width = fc.width;
+ height = fc.height;
+ px = fc.x_offset;
+ py = fc.y_offset;
+ blend = fc.blend_op;
+ self.dispose = fc.dispose_op;
+ }
+ };
+
+ // Turn the data into an rgba image proper.
+ let source = match self.inner.color_type {
+ ColorType::L8 => {
+ let image = ImageBuffer::<Luma<_>, _>::from_raw(width, height, buffer).unwrap();
+ DynamicImage::ImageLuma8(image).into_rgba8()
+ }
+ ColorType::La8 => {
+ let image = ImageBuffer::<LumaA<_>, _>::from_raw(width, height, buffer).unwrap();
+ DynamicImage::ImageLumaA8(image).into_rgba8()
+ }
+ ColorType::Rgb8 => {
+ let image = ImageBuffer::<Rgb<_>, _>::from_raw(width, height, buffer).unwrap();
+ DynamicImage::ImageRgb8(image).into_rgba8()
+ }
+ ColorType::Rgba8 => ImageBuffer::<Rgba<_>, _>::from_raw(width, height, buffer).unwrap(),
+ ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => {
+ // TODO: to enable remove restriction in `animatable_color_type` method.
+ unreachable!("16-bit apng not yet support")
+ }
+ _ => unreachable!("Invalid png color"),
+ };
+
+ match blend {
+ BlendOp::Source => {
+ self.current
+ .copy_from(&source, px, py)
+ .expect("Invalid png image not detected in png");
+ }
+ BlendOp::Over => {
+ // TODO: investigate speed, speed-ups, and bounds-checks.
+ for (x, y, p) in source.enumerate_pixels() {
+ self.current.get_pixel_mut(x + px, y + py).blend(p);
+ }
+ }
+ }
+
+ // Ok, we can proceed with actually remaining images.
+ self.remaining = remaining;
+ // Return composited output buffer.
+ Ok(Some(&self.current))
+ }
+
+ fn animatable_color_type(&self) -> Result<(), ImageError> {
+ match self.inner.color_type {
+ ColorType::L8 | ColorType::Rgb8 | ColorType::La8 | ColorType::Rgba8 => Ok(()),
+ // TODO: do not handle multi-byte colors. Remember to implement it in `mix_next_frame`.
+ ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => {
+ Err(unsupported_color(self.inner.color_type.into()))
+ }
+ _ => unreachable!("{:?} not a valid png color", self.inner.color_type),
+ }
+ }
+}
+
+impl<'a, R: Read + 'a> AnimationDecoder<'a> for ApngDecoder<R> {
+ fn into_frames(self) -> Frames<'a> {
+ struct FrameIterator<R: Read>(ApngDecoder<R>);
+
+ impl<R: Read> Iterator for FrameIterator<R> {
+ type Item = ImageResult<Frame>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let image = match self.0.mix_next_frame() {
+ Ok(Some(image)) => image.clone(),
+ Ok(None) => return None,
+ Err(err) => return Some(Err(err)),
+ };
+
+ let info = self.0.inner.reader.info();
+ let fc = info.frame_control().unwrap();
+ // PNG delays are rations in seconds.
+ let num = u32::from(fc.delay_num) * 1_000u32;
+ let denom = match fc.delay_den {
+ // The standard dictates to replace by 100 when the denominator is 0.
+ 0 => 100,
+ d => u32::from(d),
+ };
+ let delay = Delay::from_ratio(Ratio::new(num, denom));
+ Some(Ok(Frame::from_parts(image, 0, 0, delay)))
+ }
+ }
+
+ Frames::new(Box::new(FrameIterator(self)))
+ }
+}
+
+/// PNG encoder
+pub struct PngEncoder<W: Write> {
+ w: W,
+ compression: CompressionType,
+ filter: FilterType,
+}
+
+/// Compression level of a PNG encoder. The default setting is `Fast`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum CompressionType {
+ /// Default compression level
+ Default,
+ /// Fast, minimal compression
+ Fast,
+ /// High compression level
+ Best,
+ /// Huffman coding compression
+ #[deprecated(note = "use one of the other compression levels instead, such as 'Fast'")]
+ Huffman,
+ /// Run-length encoding compression
+ #[deprecated(note = "use one of the other compression levels instead, such as 'Fast'")]
+ Rle,
+}
+
+/// Filter algorithms used to process image data to improve compression.
+///
+/// The default filter is `Adaptive`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum FilterType {
+ /// No processing done, best used for low bit depth grayscale or data with a
+ /// low color count
+ NoFilter,
+ /// Filters based on previous pixel in the same scanline
+ Sub,
+ /// Filters based on the scanline above
+ Up,
+ /// Filters based on the average of left and right neighbor pixels
+ Avg,
+ /// Algorithm that takes into account the left, upper left, and above pixels
+ Paeth,
+ /// Uses a heuristic to select one of the preceding filters for each
+ /// scanline rather than one filter for the entire image
+ Adaptive,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[non_exhaustive]
+enum BadPngRepresentation {
+ ColorType(ColorType),
+}
+
+impl<W: Write> PngEncoder<W> {
+ /// Create a new encoder that writes its output to ```w```
+ pub fn new(w: W) -> PngEncoder<W> {
+ PngEncoder {
+ w,
+ compression: CompressionType::default(),
+ filter: FilterType::default(),
+ }
+ }
+
+ /// Create a new encoder that writes its output to `w` with `CompressionType` `compression` and
+ /// `FilterType` `filter`.
+ ///
+ /// It is best to view the options as a _hint_ to the implementation on the smallest or fastest
+ /// option for encoding a particular image. That is, using options that map directly to a PNG
+ /// image parameter will use this parameter where possible. But variants that have no direct
+ /// mapping may be interpreted differently in minor versions. The exact output is expressly
+ /// __not__ part the SemVer stability guarantee.
+ ///
+ /// Note that it is not optimal to use a single filter type, so an adaptive
+ /// filter type is selected as the default. The filter which best minimizes
+ /// file size may change with the type of compression used.
+ pub fn new_with_quality(
+ w: W,
+ compression: CompressionType,
+ filter: FilterType,
+ ) -> PngEncoder<W> {
+ PngEncoder {
+ w,
+ compression,
+ filter,
+ }
+ }
+
+ /// Encodes the image `data` that has dimensions `width` and `height` and `ColorType` `c`.
+ ///
+ /// Expects data in big endian.
+ #[deprecated = "Use `PngEncoder::write_image` instead. Beware that `write_image` has a different endianness convention"]
+ pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> {
+ self.encode_inner(data, width, height, color)
+ }
+
+ fn encode_inner(
+ self,
+ data: &[u8],
+ width: u32,
+ height: u32,
+ color: ColorType,
+ ) -> ImageResult<()> {
+ let (ct, bits) = match color {
+ ColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight),
+ ColorType::L16 => (png::ColorType::Grayscale, png::BitDepth::Sixteen),
+ ColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight),
+ ColorType::La16 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen),
+ ColorType::Rgb8 => (png::ColorType::Rgb, png::BitDepth::Eight),
+ ColorType::Rgb16 => (png::ColorType::Rgb, png::BitDepth::Sixteen),
+ ColorType::Rgba8 => (png::ColorType::Rgba, png::BitDepth::Eight),
+ ColorType::Rgba16 => (png::ColorType::Rgba, png::BitDepth::Sixteen),
+ _ => {
+ return Err(ImageError::Unsupported(
+ UnsupportedError::from_format_and_kind(
+ ImageFormat::Png.into(),
+ UnsupportedErrorKind::Color(color.into()),
+ ),
+ ))
+ }
+ };
+ let comp = match self.compression {
+ CompressionType::Default => png::Compression::Default,
+ CompressionType::Best => png::Compression::Best,
+ _ => png::Compression::Fast,
+ };
+ let (filter, adaptive_filter) = match self.filter {
+ FilterType::NoFilter => (
+ png::FilterType::NoFilter,
+ png::AdaptiveFilterType::NonAdaptive,
+ ),
+ FilterType::Sub => (png::FilterType::Sub, png::AdaptiveFilterType::NonAdaptive),
+ FilterType::Up => (png::FilterType::Up, png::AdaptiveFilterType::NonAdaptive),
+ FilterType::Avg => (png::FilterType::Avg, png::AdaptiveFilterType::NonAdaptive),
+ FilterType::Paeth => (png::FilterType::Paeth, png::AdaptiveFilterType::NonAdaptive),
+ FilterType::Adaptive => (png::FilterType::Sub, png::AdaptiveFilterType::Adaptive),
+ };
+
+ let mut encoder = png::Encoder::new(self.w, width, height);
+ encoder.set_color(ct);
+ encoder.set_depth(bits);
+ encoder.set_compression(comp);
+ encoder.set_filter(filter);
+ encoder.set_adaptive_filter(adaptive_filter);
+ let mut writer = encoder
+ .write_header()
+ .map_err(|e| ImageError::IoError(e.into()))?;
+ writer
+ .write_image_data(data)
+ .map_err(|e| ImageError::IoError(e.into()))
+ }
+}
+
+impl<W: Write> ImageEncoder for PngEncoder<W> {
+ /// Write a PNG image with the specified width, height, and color type.
+ ///
+ /// For color types with 16-bit per channel or larger, the contents of `buf` should be in
+ /// native endian. PngEncoder will automatically convert to big endian as required by the
+ /// underlying PNG format.
+ fn write_image(
+ self,
+ buf: &[u8],
+ width: u32,
+ height: u32,
+ color_type: ColorType,
+ ) -> ImageResult<()> {
+ use byteorder::{BigEndian, ByteOrder, NativeEndian};
+ use ColorType::*;
+
+ // PNG images are big endian. For 16 bit per channel and larger types,
+ // the buffer may need to be reordered to big endian per the
+ // contract of `write_image`.
+ // TODO: assumes equal channel bit depth.
+ match color_type {
+ L8 | La8 | Rgb8 | Rgba8 => {
+ // No reodering necessary for u8
+ self.encode_inner(buf, width, height, color_type)
+ }
+ L16 | La16 | Rgb16 | Rgba16 => {
+ // Because the buffer is immutable and the PNG encoder does not
+ // yet take Write/Read traits, create a temporary buffer for
+ // big endian reordering.
+ let mut reordered = vec![0; buf.len()];
+ buf.chunks(2)
+ .zip(reordered.chunks_mut(2))
+ .for_each(|(b, r)| BigEndian::write_u16(r, NativeEndian::read_u16(b)));
+ self.encode_inner(&reordered, width, height, color_type)
+ }
+ _ => Err(ImageError::Encoding(EncodingError::new(
+ ImageFormat::Png.into(),
+ BadPngRepresentation::ColorType(color_type),
+ ))),
+ }
+ }
+}
+
+impl ImageError {
+ fn from_png(err: png::DecodingError) -> ImageError {
+ use png::DecodingError::*;
+ match err {
+ IoError(err) => ImageError::IoError(err),
+ // The input image was not a valid PNG.
+ err @ Format(_) => {
+ ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err))
+ }
+ // Other is used 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.
+ err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind(
+ ParameterErrorKind::Generic(err.to_string()),
+ )),
+ LimitsExceeded => {
+ ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
+ }
+ }
+ }
+}
+
+impl Default for CompressionType {
+ fn default() -> Self {
+ CompressionType::Fast
+ }
+}
+
+impl Default for FilterType {
+ fn default() -> Self {
+ FilterType::Adaptive
+ }
+}
+
+impl fmt::Display for BadPngRepresentation {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::ColorType(color_type) => write!(
+ f,
+ "The color {:?} can not be represented in PNG.",
+ color_type
+ ),
+ }
+ }
+}
+
+impl std::error::Error for BadPngRepresentation {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::image::ImageDecoder;
+ use crate::ImageOutputFormat;
+
+ use std::io::{Cursor, Read};
+
+ #[test]
+ fn ensure_no_decoder_off_by_one() {
+ let dec = PngDecoder::new(
+ std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png")
+ .unwrap(),
+ )
+ .expect("Unable to read PNG file (does it exist?)");
+
+ assert_eq![(2000, 1000), dec.dimensions()];
+
+ assert_eq![
+ ColorType::Rgb8,
+ dec.color_type(),
+ "Image MUST have the Rgb8 format"
+ ];
+
+ let correct_bytes = dec
+ .into_reader()
+ .expect("Unable to read file")
+ .bytes()
+ .map(|x| x.expect("Unable to read byte"))
+ .collect::<Vec<u8>>();
+
+ assert_eq![6_000_000, correct_bytes.len()];
+ }
+
+ #[test]
+ fn underlying_error() {
+ use std::error::Error;
+
+ let mut not_png =
+ std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png")
+ .unwrap();
+ not_png[0] = 0;
+
+ let error = PngDecoder::new(&not_png[..]).err().unwrap();
+ let _ = error
+ .source()
+ .unwrap()
+ .downcast_ref::<png::DecodingError>()
+ .expect("Caused by a png error");
+ }
+
+ #[test]
+ fn encode_bad_color_type() {
+ // regression test for issue #1663
+ let image = DynamicImage::new_rgb32f(1, 1);
+ let mut target = Cursor::new(vec![]);
+ let _ = image.write_to(&mut target, ImageOutputFormat::Png);
+ }
+}