pnm decoding and encoding
Diffstat (limited to 'src/pam.rs')
| -rw-r--r-- | src/pam.rs | 360 |
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") + ); +} |