pnm 0.1.1

portable anymap format encoding and decoding
//! [Portable Arbitrary Format](https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) RGB (no alpha) image encoding and decoding.
pub type Input<'a> = Image<&'a [u8], 3>;
pub type Uninit = fimg::uninit::Image<u8, 3>;
use std::num::NonZeroU32;

use crate::decode::{read_til, Error, Read, Result};
use crate::encode::{encodeu32, P};
use atools::Join;
use fimg::{DynImage, Image};

pub const MAGIC: u8 = 7;

/// Encode this <code>[Image]<[u8], N></code> to a [PAM](https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) Raw (binary) Image.
///
/// ```
/// # use pnm::pam;
/// # use fimg::Image;
/// let out = pam::encode(
///   Image::<_, 1>::build(20, 15).buf(&include_bytes!("../tdata/fimg-gray.imgbuf")[..])
/// );
/// ```
pub fn encode(x: impl PAM) -> Vec<u8> {
    x.encode()
}

/// Encode this <code>[Image]<[bool], N></code> to a [PAM](https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) Raw (binary) Image.
pub fn encode_bitmap(x: impl PAMBit) -> Vec<u8> {
    x.encode_bitmap()
}

#[doc(hidden)]
pub trait PAM {
    fn encode(self) -> Vec<u8>;
    #[doc = include_str!("encode_into.md")]
    unsafe fn encode_into(x: Self, out: *mut u8) -> usize;
}

#[doc(hidden)]
pub trait PAMBit {
    fn encode_bitmap(self) -> Vec<u8>;
    #[doc = include_str!("encode_into.md")]
    unsafe fn encode_into(x: Self, out: *mut u8) -> usize;
}

impl<T: AsRef<[u8]>> PAM for Image<T, 1> {
    fn encode(self) -> Vec<u8> {
        let mut y = Vec::with_capacity(size(self.bytes()));
        let n = unsafe { PAM::encode_into(self.as_ref(), y.as_mut_ptr()) };
        unsafe { y.set_len(n) };
        y
    }

    unsafe fn encode_into(x: Self, out: *mut u8) -> usize {
        encode_into((x.bytes(), (x.width(), x.height())), out, b"GRAYSCALE", 1)
    }
}

impl<T: AsRef<[bool]>> PAMBit for Image<T, 1> {
    fn encode_bitmap(self) -> Vec<u8> {
        let mut y = Vec::with_capacity(size(self.as_ref().buffer()));
        let n = unsafe { PAMBit::encode_into(self.as_ref(), y.as_mut_ptr()) };
        unsafe { y.set_len(n) };
        y
    }

    unsafe fn encode_into(x: Self, out: *mut u8) -> usize {
        let b = x.buffer().as_ref();
        let b = std::slice::from_raw_parts(b.as_ptr() as *mut u8, b.len());
        encode_into((b, (x.width(), x.height())), out, b"BLACKANDWHITE", 1)
    }
}

impl<T: AsRef<[u8]>> PAM for Image<T, 2> {
    fn encode(self) -> Vec<u8> {
        let mut y = Vec::with_capacity(size(self.bytes()));
        let n = unsafe { PAM::encode_into(self.as_ref(), y.as_mut_ptr()) };
        unsafe { y.set_len(n) };
        y
    }

    unsafe fn encode_into(x: Self, out: *mut u8) -> usize {
        encode_into(
            (x.bytes(), (x.width(), x.height())),
            out,
            b"GRAYSCALE_ALPHA",
            2,
        )
    }
}

impl<T: AsRef<[u8]>> PAM for Image<T, 3> {
    fn encode(self) -> Vec<u8> {
        let mut y = Vec::with_capacity(size(self.bytes()));
        let n = unsafe { PAM::encode_into(self.as_ref(), y.as_mut_ptr()) };
        unsafe { y.set_len(n) };
        y
    }

    unsafe fn encode_into(x: Self, out: *mut u8) -> usize {
        encode_into((x.bytes(), (x.width(), x.height())), out, b"RGB", 3)
    }
}

impl<T: AsRef<[u8]>> PAM for Image<T, 4> {
    fn encode(self) -> Vec<u8> {
        let mut y = Vec::with_capacity(size(self.bytes()));
        let n = unsafe { PAM::encode_into(self.as_ref(), y.as_mut_ptr()) };
        unsafe { y.set_len(n) };
        y
    }

    unsafe fn encode_into(x: Self, out: *mut u8) -> usize {
        encode_into((x.bytes(), (x.width(), x.height())), out, b"RGB_ALPHA", 2)
    }
}

impl<T: AsRef<[u8]>> PAM for DynImage<T> {
    fn encode(self) -> Vec<u8> {
        super::e!(self, |x| encode(x))
    }

    unsafe fn encode_into(x: Self, out: *mut u8) -> usize {
        super::e!(x, |x| PAM::encode_into(x, out))
    }
}

#[inline]
unsafe fn encode_into<const N: usize>(
    (buf, (w, h)): (&[u8], (u32, u32)),
    out: *mut u8,
    tupltype: &[u8; N],
    depth: u8,
) -> usize {
    let mut o = out;
    o.put(b'P'.join(MAGIC + b'0'));
    o.put(*b"\nWIDTH ");
    encodeu32(w, &mut o);
    o.put(*b"\nHEIGHT ");
    encodeu32(h, &mut o);
    o.put(*b"\nDEPTH ");
    o.push(depth + b'0');
    o.put(*b"\nMAXVAL 255\n");
    o.put(*b"TUPLTYPE ");
    o.put(*tupltype);
    o.put(*b"\nENDHDR\n");
    if tupltype[..] == *b"BLACKANDWHITE" {
        for &x in buf {
            o.push(x ^ 1)
        }
        o.offset_from_unsigned(out)
    } else {
        o.copy_from(buf.as_ptr(), buf.len());
        o.offset_from_unsigned(out) + buf.len()
    }
}

#[derive(Copy, Clone, Debug)]
/// Header for PAM images.
pub struct PAMHeader {
    pub width: NonZeroU32,
    pub height: NonZeroU32,
    /// Channel count
    pub depth: u8,
    /// Max value
    pub max: u8,
    /// Data type
    pub tupltype: Type,
}

/// Tupltype. See [pam wikipedia page](https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) for more informaiton.
#[derive(Copy, Clone, Debug)]
pub enum Type {
    /// Black and white bitmap type, corresponding to `BLACKANDWHITE`
    Bit,
    /// Grayscale type, corresponds to `GRAYSCALE`
    Y,
    RGB,
    /// Black and white with alpha, `BLACKANDWHITE_ALPHA`
    BitA,
    /// Gray with alpha. `GRAYSCALE_ALPHA`
    YA,
    RGBA,
}

impl Type {
    const fn bytes(self) -> u8 {
        use Type::*;
        match self {
            Bit | Y => 1,
            BitA | YA => 2,
            RGB => 3,
            RGBA => 4,
        }
    }
}

/// Decode a PAM image into a [`DynImage`].
pub fn decode(x: impl AsRef<[u8]>) -> Result<DynImage<Vec<u8>>> {
    let mut x = x.as_ref();
    crate::decode::magic(&mut x);
    decode_wo_magic(x)
}

/// Decode a magicless PAM image.
pub fn decode_wo_magic(mut x: &[u8]) -> Result<DynImage<Vec<u8>>> {
    let header = decode_pam_header(&mut x)?;
    let mut alloc = Vec::with_capacity(
        header.tupltype.bytes() as usize
            * header.width.get() as usize
            * header.height.get() as usize,
    );
    let n = unsafe { decode_inner(x, alloc.as_mut_ptr(), header)? };
    unsafe { alloc.set_len(n) };
    Ok(match header.tupltype {
        Type::Bit => unsafe { DynImage::Y(Image::new(header.width, header.height, alloc)) },
        Type::Y => unsafe { DynImage::Y(Image::new(header.width, header.height, alloc)) },
        Type::BitA => unsafe { DynImage::Ya(Image::new(header.width, header.height, alloc)) },
        Type::YA => unsafe { DynImage::Ya(Image::new(header.width, header.height, alloc)) },
        Type::RGB => unsafe { DynImage::Rgb(Image::new(header.width, header.height, alloc)) },
        Type::RGBA => unsafe { DynImage::Rgba(Image::new(header.width, header.height, alloc)) },
    })
}

/// Decodes this pam image's body, placing it in the raw pointer.
/// # Safety
///
/// buffer must have [`size`] bytes of space.
pub unsafe fn decode_inner(x: &[u8], mut into: *mut u8, header: PAMHeader) -> Result<usize> {
    let n = header.tupltype.bytes() as usize
        * header.width.get() as usize
        * header.height.get() as usize;
    match header.tupltype {
        Type::Bit => x
            .iter()
            .map(|&x| x.saturating_mul(0xff))
            .take(n)
            .for_each(|x| into.push(x)),
        Type::BitA => x
            .iter()
            .array_chunks::<2>()
            .take(header.width.get() as usize * header.height.get() as usize)
            .map(|[&x, &a]| [x.saturating_mul(0xff), a])
            .for_each(|x| into.put(x)),
        Type::Y | Type::YA | Type::RGB | Type::RGBA => {
            if x.len() < n {
                return Err(Error::MissingData);
            }
            into.copy_from(x.as_ptr(), n);
        }
    }
    Ok(n)
}

/// expects no magic
pub fn decode_pam_header(x: &mut &[u8]) -> Result<PAMHeader> {
    macro_rules! test {
        ($for:literal else $e:ident) => {
            if x.rd().ok_or(Error::$e)? != *$for {
                return Err(Error::$e);
            };
        };
    }
    test![b"WIDTH " else MissingWidth];
    let width = NonZeroU32::new(read_til(x)?).ok_or(Error::ZeroWidth)?;
    test![b"HEIGHT " else MissingHeight];
    let height = NonZeroU32::new(read_til(x)?).ok_or(Error::ZeroHeight)?;
    width.checked_mul(height).ok_or(Error::TooLarge)?;
    test![b"DEPTH " else MissingDepth];
    let depth = read_til::<u8>(x)?;
    test![b"MAXVAL " else MissingMax];
    let max = read_til::<u8>(x)?;
    test![b"TUPLTYPE " else MissingTupltype];
    let end = x
        .iter()
        .position(|&x| x == b'\n')
        .ok_or(Error::MissingTupltype)?;
    let tupltype = match &x[..end] {
        b"BLACKANDWHITE" => Type::Bit,
        b"BLACKANDWHITE_ALPHA" => Type::BitA,
        b"GRAYSCALE" => Type::Y,
        b"GRAYSCALE_ALPHA" => Type::YA,
        b"RGB" => Type::RGB,
        b"RGB_ALPHA" => Type::RGBA,
        _ => return Err(Error::MissingTupltype),
    };
    *x = &x[end..];
    test![b"\nENDHDR\n" else MissingData];
    Ok(PAMHeader {
        width,
        height,
        depth,
        max,
        tupltype,
    })
}

#[doc = include_str!("est.md")]
pub const fn size<T>(x: &[T]) -> usize {
    92 + x.len()
}

#[test]
fn test_bit() {
    assert_eq!(
        PAMBit::encode_bitmap(
            Image::<_, 1>::build(20, 15).buf(
                include_bytes!("../tdata/fimg.imgbuf")
                    .iter()
                    .map(|&x| x <= 128)
                    .collect::<Vec<_>>(),
            ),
        ),
        include_bytes!("../tdata/fimg.pam")
    );

    assert_eq!(
        &**decode(include_bytes!("../tdata/fimg.pam"))
            .unwrap()
            .buffer(),
        include_bytes!("../tdata/fimg.imgbuf")
    );
}

#[test]
fn test_y() {
    assert_eq!(
        PAM::encode(
            Image::<_, 1>::build(20, 15).buf(&include_bytes!("../tdata/fimg-gray.imgbuf")[..])
        ),
        include_bytes!("../tdata/fimg-gray.pam")
    );
    assert_eq!(
        &**decode(include_bytes!("../tdata/fimg-gray.pam"))
            .unwrap()
            .buffer(),
        include_bytes!("../tdata/fimg-gray.imgbuf")
    );
}

#[test]
fn test_ya() {
    assert_eq!(
        PAM::encode(
            Image::<_, 2>::build(20, 15)
                .buf(&include_bytes!("../tdata/fimg-transparent.imgbuf")[..])
        ),
        include_bytes!("../tdata/fimg-transparent.pam")
    );
    assert_eq!(
        &**decode(include_bytes!("../tdata/fimg-transparent.pam"))
            .unwrap()
            .buffer(),
        include_bytes!("../tdata/fimg-transparent.imgbuf")
    );
}

#[test]
fn test_rgb() {
    assert_eq!(
        PAM::encode(
            Image::<_, 3>::build(20, 15).buf(&include_bytes!("../tdata/fimg-rainbow.imgbuf")[..])
        ),
        include_bytes!("../tdata/fimg-rainbow.pam")
    );
    assert_eq!(
        &**decode(include_bytes!("../tdata/fimg-rainbow.pam"))
            .unwrap()
            .buffer(),
        include_bytes!("../tdata/fimg-rainbow.imgbuf")
    );
}

#[test]
fn test_rgba() {
    assert_eq!(
        PAM::encode(
            Image::<_, 4>::build(20, 15)
                .buf(&include_bytes!("../tdata/fimg-rainbow-transparent.imgbuf")[..])
        ),
        include_bytes!("../tdata/fimg-rainbow-transparent.pam")
    );
    assert_eq!(
        &**decode(include_bytes!("../tdata/fimg-rainbow-transparent.pam"))
            .unwrap()
            .buffer(),
        include_bytes!("../tdata/fimg-rainbow-transparent.imgbuf")
    );
}