repng 0.1.0

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,
    prior: Vec<u8>,
}

impl<W: Write, F> 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, filter: F) -> 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.level, opts.buffer),
            stride: stride,
            prior: vec![0; stride],

            filter: filter,
            sink: sink,
        };

        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
        this.chunk(b"IHDR", &ihdr)?;

        Ok(this)
    }

    /// Flush the encoder, writing any delayed data, and recover the sink.
    pub fn finish(self) -> io::Result<W> {
        let Self { mut compress, mut sink, .. } = self;

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

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

        Ok(sink)
    }

    /// 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
    }

    /// Reset the encoder to encode another image with the exact same options.
    pub fn reset(&mut self) {
        self.compress.reset();
        for x in self.prior.iter_mut() { *x = 0; }
    }

    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(())
    }
}

impl<W: Write, F: Filter> Encoder<W, F> {
    /// 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,
            ref mut prior,
            ..
        } = *self;

        if rows.len() == 0 {
            return Ok(());
        }

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

        filter.apply(&mut writer, &prior, &rows[..r])?;
        prior.copy_from_slice(&rows[rows.len() - r..]);

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

        Ok(())
    }
}