repng 0.1.0

The PNG encoder that no one asked for.
Documentation
//! Add some metadata to the image.

use Encoder;
use byteorder::{ByteOrder, NetworkEndian, WriteBytesExt};
use flate2::Crc;
use std::io::{self, Write};

/// Specify the color palette for an indexed image.
///
/// The first 3 bytes specify the RGB value represented by index 0, the next 3
/// bytes specify the RGB value represented by index 1, and so on.
///
/// # Panics
///
/// Panics if the array's length isn't a multiple of 3, or if there are more
/// than 256 palette entries.
pub fn palette<W: Write, F>(enc: &mut Encoder<W, F>, rgb: &[u8])
    -> io::Result<()>
{
    assert!(rgb.len() % 3 == 0);
    assert!(rgb.len() / 3 <= 256);
    enc.chunk(b"PLTE", rgb)?;
    Ok(())
}

/// The same as `palette`, but with RGBA (4 bytes) instead of RGB (3 bytes).
pub fn palette_alpha<W: Write, F>(enc: &mut Encoder<W, F>, rgba: &[u8])
    -> io::Result<()>
{
    assert!(rgba.len() % 4 == 0);
    assert!(rgba.len() / 4 <= 256);

    let len = rgba.len() / 4;

    let mut plte = [0; 256 * 3];
    let mut trns = [0; 256];

    for i in 0..len {
        plte[3*i + 0] = rgba[4*i + 0];
        plte[3*i + 1] = rgba[4*i + 1];
        plte[3*i + 2] = rgba[4*i + 2];
        trns[i]       = rgba[4*i + 3];
    }

    enc.chunk(b"PLTE", &plte[..len * 3])?;
    enc.chunk(b"tRNS", &trns[..len])?;
    Ok(())
}

/// Specify the aspect ratio of the image.
///
/// Essentially, `x : y` is the ratio between image's width and its height.
pub fn aspect_ratio<W: Write, F>(enc: &mut Encoder<W, F>, x: u32, y: u32)
    -> io::Result<()>
{
    let mut phys = [0; 9];

    NetworkEndian::write_u32(&mut phys[0..4], x);
    NetworkEndian::write_u32(&mut phys[4..8], y);

    enc.chunk(b"pHYs", &phys)?;
    Ok(())
}

/// Add some textual metadata to the image.
pub fn text<W: Write, F>(enc: &mut Encoder<W, F>, keyword: Keyword, text: &str)
    -> io::Result<()>
{
    assert!(keyword.valid());
    assert!(text.find('\0').is_none());

    let kind = b"iTXt";
    let sink = enc.writer();
    let data = &[
        keyword.as_str().as_bytes(),
        &[0, 0, 0, 0, 0],
        text.as_bytes(),
    ];

    let mut crc = Crc::new();
    crc.update(kind);
    for sec in data {
        crc.update(sec);
    }

    sink.write_u32::<NetworkEndian>(crc.amount() - 4)?;
    sink.write_all(kind)?;
    for sec in data {
        sink.write_all(sec)?;
    }
    sink.write_u32::<NetworkEndian>(crc.sum())?;

    Ok(())
}

/// A metadata category.
pub enum Keyword<'a> {
    /// Short (one line) title or caption for image.
    Title,
    /// Name of image's creator.
    Author,
    /// Description of image (possibly long).
    Description,
    /// Copyright notice.
    Copyright,
    /// Time of original image creation.
    CreationTime,
    /// Software used to create the image.
    Software,
    /// Legal disclaimer.
    Disclaimer,
    /// Warning of nature of content.
    Warning,
    /// Device used to create the image.
    Source,
    /// Miscellaneous comment.
    Comment,
    /// A custom keyword between 1 and 79 characters in length.
    Other(&'a str),
}

impl<'a> Keyword<'a> {
    /// Whether the keyword is valid.
    ///
    /// - It must be at least 1 character long.
    /// - It must be no more than 79 characters long.
    /// - It must only use the characters in the ASCII range [32, 126].
    /// - It must not contain leading or trailing spaces.
    /// - It must not contain consecutive spaces.
    pub fn valid(&self) -> bool {
        match *self {
            Keyword::Other(s) =>
                1 <= s.len() &&
                s.len() <= 79 &&
                s.bytes().all(|b| 32 <= b && b <= 126) &&
                !(s.starts_with(' ') || s.ends_with(' ')) &&
                s.find("  ").is_none(),
            _ => true
        }
    }

    /// Get the keyword as a string.
    pub fn as_str(&self) -> &str {
        match *self {
            Keyword::Title => "Title",
            Keyword::Author => "Author",
            Keyword::Description => "Description",
            Keyword::Copyright => "Copyright",
            Keyword::CreationTime => "Creation Time",
            Keyword::Software => "Software",
            Keyword::Disclaimer => "Disclaimer",
            Keyword::Warning => "Warning",
            Keyword::Source => "Source",
            Keyword::Comment => "Comment",
            Keyword::Other(s) => s,
        }
    }
}