repng 0.2.2

The PNG encoder that no one asked for.
Documentation
#![deny(missing_docs)]

//! The PNG encoder that no one asked for.
//!
//! The abstraction is pretty leaky, but it's simple enough that you can make
//! cool things without much effort, such as this program, which creates a very
//! blank image.
//!
//! ```
//! use repng::Options;
//!
//! let mut png = Vec::new();
//!
//! {
//!     let mut encoder = Options::smallest(480, 360)
//!         .build(&mut png)
//!         .unwrap();
//!
//!     let row = [255; 480 * 4];
//!
//!     for y in 0..360 {
//!         encoder.write(&row).unwrap();
//!     }
//!
//!     encoder.finish().unwrap();
//! }
//!
//! println!("{:?}", png);
//! ```

extern crate byteorder;
extern crate flate2;

pub mod filter;
pub mod meta;
pub use options::{Compression, ColorFormat, Options};

mod compress;
mod options;

use byteorder::{ByteOrder, NetworkEndian, WriteBytesExt};
use compress::Compressor;
use filter::Filter;
use flate2::Crc;
use std::io::{self, Write};

/// Encode an RGBA image.
pub fn encode<W: Write>(sink: W, width: u32, height: u32, image: &[u8])
    -> io::Result<()>
{
    let mut encoder = Options::smallest(width, height).build(sink)?;
    encoder.write(&image)?;
    encoder.finish()?;
    Ok(())
}

/// The main object, which does all of the encoding work.
pub struct Encoder<W, F> {
    filter: F,
    sink: W,
    compress: Compressor,
    stride: usize,
}

impl<W: Write, F> Encoder<W, F> {
    /// Finish encoding an image, writing any delayed data.
    pub fn finish(&mut self) -> io::Result<()> {
        let Self { ref mut compress, ref mut sink, .. } = *self;

        compress::Writer::new(
            compress,
            |bytes: &[u8]| Self::sinking_chunk(sink, b"IDAT", bytes),
        ).finish()?;

        Self::sinking_chunk(sink, b"IEND", &[])?;
        Ok(())
    }

    /// Write a chunk with the given type and data.
    pub fn chunk(&mut self, kind: &[u8; 4], data: &[u8]) -> io::Result<()> {
        Self::sinking_chunk(&mut self.sink, kind, data)
    }

    /// Get access to the writer that this was built with.
    ///
    /// Be careful, because you can easily corrupt the image with this method.
    pub fn writer(&mut self) -> &mut W { &mut self.sink }

    fn sinking_chunk(sink: &mut W, kind: &[u8; 4], data: &[u8])
        -> io::Result<()>
    {
        let mut crc = Crc::new();

        crc.update(kind);
        crc.update(data);

        sink.write_u32::<NetworkEndian>(data.len() as u32)?;
        sink.write_all(kind)?;
        sink.write_all(data)?;
        sink.write_u32::<NetworkEndian>(crc.sum())?;

        Ok(())
    }

    fn write_header(&mut self, opts: &Options) -> io::Result<()> {
        let mut ihdr = [0; 13];
        NetworkEndian::write_u32(&mut ihdr, opts.width);
        NetworkEndian::write_u32(&mut ihdr[4..], opts.height);
        ihdr[8]  = opts.depth;
        ihdr[9]  = opts.format as u8;
        ihdr[10] = 0; // Compression
        ihdr[11] = 0; // Filter
        ihdr[12] = 0; // Interlace
        self.chunk(b"IHDR", &ihdr)
    }
}

impl<W: Write, F: Filter> Encoder<W, F> {
    /// Make a new encoder, which writes to a given sink, with a custom filter.
    ///
    /// This method also immediately writes the PNG headers to the sink.
    pub fn new(opts: &Options, mut sink: W) -> io::Result<Self> {
        sink.write_all(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])?;

        let stride = opts.stride();

        let mut this = Self {
            compress: Compressor::new(opts),
            stride: stride,
            filter: F::from(opts),
            sink: sink,
        };

        this.write_header(opts)?;
        Ok(this)
    }

    /// Feed zero or more rows to the encoder.
    ///
    /// Rows are specified in top-to-bottom and left-to-right order.
    ///
    /// # Panics
    ///
    /// This method panics if you try to write data that doesn't fit into a
    /// whole number of rows.
    pub fn write(&mut self, mut rows: &[u8]) -> io::Result<()> {
        assert!(rows.len() % self.stride == 0);

        let r = self.stride;
        let Self {
            ref mut sink,
            ref mut filter,
            ref mut compress,
            ..
        } = *self;

        let mut writer = compress::Writer::new(
            compress,
            |bytes: &[u8]| Self::sinking_chunk(sink, b"IDAT", bytes),
        );

        while rows.len() != 0 {
            filter.apply(&mut writer, &rows[..r])?;
            rows = &rows[r..];
        }

        Ok(())
    }

    /// Reset the encoder to encode another image with the given options.
    ///
    /// There's a good chance that, before calling this method, you'll want to
    /// call `writer()` to get a mutable reference to the writer, and swap that
    /// out with a different writer. This method also writes the headers of the
    /// new image to the sink.
    ///
    /// # Warning
    ///
    /// This currently doesn't change the compression level!
    pub fn reset(&mut self, opts: &Options) -> io::Result<()> {
        self.compress.reset(opts);
        self.stride = opts.stride();
        self.filter.reset(opts);
        self.write_header(opts)?;
        Ok(())
    }
}