pnm decoding and encoding
Diffstat (limited to 'src/pam.rs')
-rw-r--r--src/pam.rs360
1 files changed, 360 insertions, 0 deletions
diff --git a/src/pam.rs b/src/pam.rs
new file mode 100644
index 0000000..f55b4b8
--- /dev/null
+++ b/src/pam.rs
@@ -0,0 +1,360 @@
+//! [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 an <code>[Image]<[u8], N></code> to [PAM](https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) Raw (binary) Image.
+/// And decode.
+pub trait PAM {
+ /// Encode this image to pam.
+ fn encode(self) -> Vec<u8>;
+ #[doc = include_str!("encode_into.md")]
+ unsafe fn encode_into(x: Self, out: *mut u8) -> usize;
+}
+
+/// Bool images.
+/// Unstable api.
+pub trait PAMBit {
+ /// Encode this bit image to pam.
+ 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)
+ }
+}
+
+#[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");
+ for &x in buf {
+ if &tupltype[..] == b"BLACKANDWHITE" {
+ o.push(x ^ 1)
+ } else {
+ o.push(x)
+ }
+ }
+
+ o.sub_ptr(out)
+}
+
+#[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(mut x: &[u8]) -> Result<DynImage<Vec<u8>>> {
+ 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 * 0xff)
+ .take(n)
+ .for_each(|x| into.push(x)),
+ Type::BitA => x
+ .iter()
+ .array_chunks::<2>()
+ .take(n)
+ .map(|[&x, &a]| [x * 0xff, a])
+ .for_each(|x| into.put(x)),
+ Type::Y | Type::YA | Type::RGB | Type::RGBA => {
+ 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")
+ );
+}