feim 0.25.1

Modular crate for working with images in Rust
Documentation
mod png_buf;

pub use png_buf::*;

use std::io::{self, Read, Write};

use png::{BitDepth, ColorType, Decoder, DecodingError, Encoder, EncodingError, Transformations};

// re-export this stuff
pub use png::{Compression, FilterType};

use crate::buffer::RawPixBuf;
use crate::color::convert::ConvertInto;
use crate::color::{Gray, Gray16Be, Nrgba, Nrgba64Be, Rgb, Rgb48Be};
use crate::image::{Dimensions, Image, ImageMut};
use crate::impl_format;
use crate::serialize::{Decode, DecodeOptions, Encode, EncodeOptions, EncodeSpecialized};
use crate::specialized;

pub struct Png;

impl_format! {
    name: Png,
    id: "feim:png",
    magic: b"\x89PNG\r\n\x1a\n",
}

impl DecodeOptions for Png {
    type Options = ();
}

impl Decode<PngBuf> for Png {
    type Error = io::Error;

    fn decode<R: Read>(r: R, _opt: ()) -> io::Result<PngBuf> {
        let mut decoder = Decoder::new(r);
        decoder.set_transformations(Transformations::EXPAND);

        let mut reader = decoder.read_info().map_err(|e| match e {
            DecodingError::IoError(e) => e,
            other => io::Error::other(other),
        })?;

        let width = reader.info().width as usize;
        let height = reader.info().height as usize;

        let mut buffer = match reader.output_color_type() {
            (ColorType::Grayscale, BitDepth::Eight) => PngBuf::Gray(RawPixBuf::new(width, height)),
            (ColorType::Grayscale, BitDepth::Sixteen) => {
                PngBuf::Gray16(RawPixBuf::new(width, height))
            }
            (ColorType::Rgba, BitDepth::Eight) => PngBuf::Nrgba(RawPixBuf::new(width, height)),
            (ColorType::Rgba, BitDepth::Sixteen) => PngBuf::Nrgba64(RawPixBuf::new(width, height)),
            (ColorType::Rgb, BitDepth::Eight) => PngBuf::Rgb(RawPixBuf::new(width, height)),
            (ColorType::Rgb, BitDepth::Sixteen) => PngBuf::Rgb48(RawPixBuf::new(width, height)),
            _ => return Err(io::Error::other("Unsupported color type detected")),
        };

        reader.next_frame(buffer.as_mut()).map_err(|e| match e {
            DecodingError::IoError(e) => e,
            other => io::Error::other(other),
        })?;

        Ok(buffer)
    }
}

#[derive(Debug, Copy, Clone)]
pub struct PngEncodeOptions {
    pub filter: FilterType,
    pub compression: Compression,
}

impl Default for PngEncodeOptions {
    fn default() -> Self {
        Self {
            filter: FilterType::NoFilter,
            compression: Compression::Default,
        }
    }
}

impl EncodeOptions for Png {
    type Options = PngEncodeOptions;
}

macro_rules! impl_encode {
    ($type:ty, $depth:expr, $color:expr) => {
        impl Encode<$type> for Png {
            type Error = io::Error;

            fn encode<W: Write>(w: W, opts: PngEncodeOptions, buf: &$type) -> io::Result<()> {
                let width = (buf.width() & u32::MAX as usize) as u32;
                let height = (buf.height() & u32::MAX as usize) as u32;

                let mut encoder = Encoder::new(w, width, height);
                encoder.set_color($color);
                encoder.set_depth($depth);
                encoder.set_filter(opts.filter);
                encoder.set_compression(opts.compression);

                let mut writer = encoder.write_header().map_err(|e| match e {
                    EncodingError::IoError(e) => e,
                    other => io::Error::new(io::ErrorKind::Other, other),
                })?;

                writer.write_image_data(buf.as_ref()).map_err(|e| match e {
                    EncodingError::IoError(e) => e,
                    other => io::Error::new(io::ErrorKind::Other, other),
                })
            }
        }
    };
}

impl_encode!(RawPixBuf<Gray>, BitDepth::Eight, ColorType::Grayscale);
impl_encode!(RawPixBuf<Gray16Be>, BitDepth::Sixteen, ColorType::Grayscale);
impl_encode!(RawPixBuf<Nrgba>, BitDepth::Eight, ColorType::Rgba);
impl_encode!(RawPixBuf<Nrgba64Be>, BitDepth::Sixteen, ColorType::Rgba);
impl_encode!(RawPixBuf<Rgb>, BitDepth::Eight, ColorType::Rgb);
impl_encode!(RawPixBuf<Rgb48Be>, BitDepth::Sixteen, ColorType::Rgb);

#[cfg(feature = "fmt-webp")]
impl_encode!(
    crate::image::webp::RgbWebpBuf,
    BitDepth::Eight,
    ColorType::Rgb
);
#[cfg(feature = "fmt-webp")]
impl_encode!(
    crate::image::webp::NrgbaWebpBuf,
    BitDepth::Eight,
    ColorType::Rgba
);

impl Encode<PngBuf> for Png {
    type Error = io::Error;

    fn encode<W: Write>(w: W, opts: PngEncodeOptions, buf: &PngBuf) -> io::Result<()> {
        match buf {
            PngBuf::Gray(buf) => Png::encode_specialized(w, opts, buf),
            PngBuf::Gray16(buf) => Png::encode_specialized(w, opts, buf),
            PngBuf::Nrgba(buf) => Png::encode_specialized(w, opts, buf),
            PngBuf::Nrgba64(buf) => Png::encode_specialized(w, opts, buf),
            PngBuf::Rgb(buf) => Png::encode_specialized(w, opts, buf),
            PngBuf::Rgb48(buf) => Png::encode_specialized(w, opts, buf),
        }
    }
}

impl<I: Image + Dimensions> Encode<I, specialized::No> for Png {
    type Error = io::Error;

    fn encode<W: Write>(w: W, opts: PngEncodeOptions, buf: &I) -> io::Result<()> {
        let (width, height) = buf.dimensions();
        let mut new_buf = RawPixBuf::new(width, height);
        for y in 0..height {
            for x in 0..width {
                let c = buf.color_get(x, y);
                let c: Nrgba64Be = c.convert_into();
                new_buf.pixel_set(x, y, c);
            }
        }
        Png::encode_specialized(w, opts, &new_buf)
    }
}