use std::convert::TryFrom;
use std::io::{self, Read, Write};
use num_rational::Ratio;
use png::{BlendOp, DisposeOp};
use crate::{DynamicImage, GenericImage, ImageBuffer, Luma, LumaA, RgbaImage, Rgb, Rgba};
use crate::animation::{Delay, Frame, Frames};
use crate::color::{Blend, ColorType, ExtendedColorType};
use crate::error::{
DecodingError, ImageError, ImageResult, LimitError, LimitErrorKind, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind
};
use crate::image::{AnimationDecoder, ImageDecoder, ImageEncoder, ImageFormat};
pub struct PngReader<R: Read> {
reader: png::Reader<R>,
buffer: Vec<u8>,
index: usize,
}
#[allow(dead_code)]
#[deprecated(note = "Use `PngReader` instead")]
pub type PNGReader<R> = PngReader<R>;
impl<R: Read> PngReader<R> {
fn new(mut reader: png::Reader<R>) -> ImageResult<PngReader<R>> {
let len = reader.output_buffer_size();
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> {
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) => {
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<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);
bytes += row.len();
}
Ok(bytes)
}
}
pub struct PngDecoder<R: Read> {
color_type: ColorType,
reader: png::Reader<R>,
}
impl<R: Read> PngDecoder<R> {
pub fn new(r: R) -> ImageResult<PngDecoder<R>> {
let limits = png::Limits {
bytes: usize::max_value(),
};
let mut decoder = png::Decoder::new_with_limits(r, limits);
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(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 })
}
pub fn apng(self) -> ApngDecoder<R> {
ApngDecoder::new(self)
}
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 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)?;
let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count();
match bpc {
1 => (),
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
}
}
pub struct ApngDecoder<R: Read> {
inner: PngDecoder<R>,
current: RgbaImage,
previous: RgbaImage,
dispose: DisposeOp,
remaining: u32,
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() {
Some(actl) => actl.num_frames,
None => 0,
};
let has_thumbnail = info.frame_control.is_none();
ApngDecoder {
inner,
current: RgbaImage::new(width, height),
previous: RgbaImage::new(width, height),
dispose: DisposeOp::Background,
remaining,
has_thumbnail,
}
}
fn mix_next_frame(&mut self) -> Result<Option<&RgbaImage>, ImageError> {
self.remaining = match self.remaining.checked_sub(1) {
None => return Ok(None),
Some(next) => next,
};
let remaining = self.remaining;
self.remaining = 0;
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()?;
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);
}
}
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();
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;
}
};
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 => {
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 => {
for (x, y, p) in source.enumerate_pixels() {
self.current.get_pixel_mut(x + px, y + py).blend(p);
}
}
}
self.remaining = remaining;
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(()),
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();
let num = u32::from(fc.delay_num) * 1_000u32;
let denom = match fc.delay_den {
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)))
}
}
pub struct PngEncoder<W: Write> {
w: W,
compression: CompressionType,
filter: FilterType,
}
#[allow(dead_code)]
#[deprecated(note = "Use `PngEncoder` instead")]
pub type PNGEncoder<W> = PngEncoder<W>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompressionType {
Default,
Fast,
Best,
Huffman,
Rle,
#[doc(hidden)]
__NonExhaustive(crate::utils::NonExhaustiveMarker),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FilterType {
NoFilter,
Sub,
Up,
Avg,
Paeth,
#[doc(hidden)]
__NonExhaustive(crate::utils::NonExhaustiveMarker),
}
impl<W: Write> PngEncoder<W> {
pub fn new(w: W) -> PngEncoder<W> {
PngEncoder {
w,
compression: CompressionType::Fast,
filter: FilterType::Sub,
}
}
pub fn new_with_quality(w: W, compression: CompressionType, filter: FilterType) -> PngEncoder<W> {
PngEncoder {
w,
compression,
filter,
}
}
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::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Png.into(),
UnsupportedErrorKind::Color(color.into()),
))),
};
let comp = match self.compression {
CompressionType::Default => png::Compression::Default,
CompressionType::Fast => png::Compression::Fast,
CompressionType::Best => png::Compression::Best,
CompressionType::Huffman => png::Compression::Huffman,
CompressionType::Rle => png::Compression::Rle,
CompressionType::__NonExhaustive(marker) => match marker._private {},
};
let filt = match self.filter {
FilterType::NoFilter => png::FilterType::NoFilter,
FilterType::Sub => png::FilterType::Sub,
FilterType::Up => png::FilterType::Up,
FilterType::Avg => png::FilterType::Avg,
FilterType::Paeth => png::FilterType::Paeth,
FilterType::__NonExhaustive(marker) => match marker._private {},
};
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(filt);
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> {
fn write_image(
self,
buf: &[u8],
width: u32,
height: u32,
color_type: ColorType,
) -> ImageResult<()> {
use byteorder::{BigEndian, ByteOrder, NativeEndian};
let bpc = color_type.bytes_per_pixel() / color_type.channel_count();
match bpc {
1 => self.encode(buf, width, height, color_type),
2 => {
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),
err @ Format(_) => ImageError::Decoding(DecodingError::new(
ImageFormat::Png.into(),
err,
)),
LimitsExceeded => ImageError::Limits(LimitError::from_kind(
LimitErrorKind::InsufficientMemory,
)),
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,
))
}
}
}
}
impl Default for CompressionType {
fn default() -> Self {
CompressionType::Fast
}
}
impl Default for FilterType {
fn default() -> Self {
FilterType::Sub
}
}
#[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::<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(¬_png[..]).err().unwrap();
let _ = error
.source()
.unwrap()
.downcast_ref::<png::DecodingError>()
.expect("Caused by a png error");
}
}