//! [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; 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 [Image]<[u8], N> 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 { x.encode() } /// Encode this [Image]<[bool], N> to a [PAM](https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) Raw (binary) Image. pub fn encode_bitmap(x: impl PAMBit) -> Vec { x.encode_bitmap() } #[doc(hidden)] pub trait PAM { fn encode(self) -> Vec; #[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; #[doc = include_str!("encode_into.md")] unsafe fn encode_into(x: Self, out: *mut u8) -> usize; } impl> PAM for Image { fn encode(self) -> Vec { 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> PAMBit for Image { fn encode_bitmap(self) -> Vec { 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> PAM for Image { fn encode(self) -> Vec { 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> PAM for Image { fn encode(self) -> Vec { 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> PAM for Image { fn encode(self) -> Vec { 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> PAM for DynImage { fn encode(self) -> Vec { 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( (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>> { 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>> { 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 { 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 { 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::(x)?; test![b"MAXVAL " else MissingMax]; let max = read_til::(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(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::>(), ), ), 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") ); }