//! Decoding and Encoding of PNG Images //! //! PNG (Portable Network Graphics) is an image format that supports lossless compression. //! //! # Related Links //! * - The PNG Specification //! use std::convert::TryFrom; use std::io::{self, Read, Write}; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{DecodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::image::{ImageDecoder, ImageEncoder, ImageFormat}; /// 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 { reader: png::Reader, buffer: Vec, index: usize, } impl PNGReader { fn new(mut reader: png::Reader) -> ImageResult> { 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 Read for PNGReader { fn read(&mut self, mut buf: &mut [u8]) -> io::Result { // 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).unwrap(); bytes += readed; self.buffer = (&row[readed..]).to_owned(); self.index = 0; } None => return Ok(bytes) } } Ok(bytes) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { let mut bytes = self.buffer.len(); buf.extend_from_slice(&self.buffer); self.buffer = Vec::new(); self.index = 0; while let Some(row) = self.reader.next_row()? { buf.extend_from_slice(row); bytes += row.len(); } Ok(bytes) } } /// PNG decoder pub struct PngDecoder { color_type: ColorType, reader: png::Reader, } impl PngDecoder { /// Creates a new decoder that decodes from the stream ```r``` pub fn new(r: R) -> ImageResult> { let limits = png::Limits { bytes: usize::max_value(), }; let mut decoder = png::Decoder::new_with_limits(r, limits); // 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 (_, mut 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(ImageError::UnsupportedColor(ExtendedColorType::L1)), (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => return Err(ImageError::UnsupportedColor(ExtendedColorType::La1)), (png::ColorType::RGB, png::BitDepth::One) => return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgb1)), (png::ColorType::RGBA, png::BitDepth::One) => return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba1)), (png::ColorType::Grayscale, png::BitDepth::Two) => return Err(ImageError::UnsupportedColor(ExtendedColorType::L2)), (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => return Err(ImageError::UnsupportedColor(ExtendedColorType::La2)), (png::ColorType::RGB, png::BitDepth::Two) => return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgb2)), (png::ColorType::RGBA, png::BitDepth::Two) => return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba2)), (png::ColorType::Grayscale, png::BitDepth::Four) => return Err(ImageError::UnsupportedColor(ExtendedColorType::L4)), (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => return Err(ImageError::UnsupportedColor(ExtendedColorType::La4)), (png::ColorType::RGB, png::BitDepth::Four) => return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgb4)), (png::ColorType::RGBA, png::BitDepth::Four) => return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba4)), (png::ColorType::Indexed, bits) => return Err(ImageError::UnsupportedColor(ExtendedColorType::Unknown(bits as u8))), }; Ok(PngDecoder { color_type, reader }) } } impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { type Reader = PNGReader; fn dimensions(&self) -> (u32, u32) { self.reader.info().size() } fn color_type(&self) -> ColorType { self.color_type } fn into_reader(self) -> ImageResult { 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 } } /// PNG encoder pub struct PNGEncoder { w: W, } impl PNGEncoder { /// Create a new encoder that writes its output to ```w``` pub fn new(w: W) -> PNGEncoder { PNGEncoder { w } } /// Encodes the image ```image``` /// that has dimensions ```width``` and ```height``` /// and ```ColorType``` ```c``` pub fn encode(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::UnsupportedColor(color.into())), }; let mut encoder = png::Encoder::new(self.w, width, height); encoder.set_color(ct); encoder.set_depth(bits); 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 ImageEncoder for PNGEncoder { fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { use byteorder::{BigEndian, ByteOrder, NativeEndian}; // 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. let bpc = color_type.bytes_per_pixel() / color_type.channel_count(); match bpc { 1 => self.encode(buf, width, height, color_type), // No reodering necessary for u8 2 => { // 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(&reordered, width, height, color_type) }, _ => unreachable!(), } } } impl ImageError { fn from_png(err: png::DecodingError) -> ImageError { use png::DecodingError::*; match err { IoError(err) => ImageError::IoError(err), Format(message) => ImageError::Decoding(DecodingError::with_message( ImageFormat::Png.into(), message.into_owned(), )), LimitsExceeded => ImageError::InsufficientMemory, // Other is used when the buffer to `Reader::next_frame` is too small. Other(message) => ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic(message.into_owned()) )), err @ InvalidSignature | err @ CrcMismatch { .. } | err @ CorruptFlateStream => { ImageError::Decoding(DecodingError::new( ImageFormat::Png.into(), err, )) } } } } #[cfg(test)] mod tests { use crate::image::ImageDecoder; use std::io::Read; use super::*; #[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::>(); 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(¬_png[..]).err().unwrap(); let _ = error .source() .unwrap() .downcast_ref::() .expect("Caused by a png error"); } }