pnm decoding and encoding
Diffstat (limited to 'src/pgm.rs')
| -rw-r--r-- | src/pgm.rs | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/src/pgm.rs b/src/pgm.rs new file mode 100644 index 0000000..0b844d8 --- /dev/null +++ b/src/pgm.rs @@ -0,0 +1,180 @@ +//! [Portable GreyMap Format](https://en.wikipedia.org/wiki/Netpbm#PGM_example) grey image encoding and decoding. +pub(crate) const CHANNELS: usize = 1; +pub type Input<'a> = Image<&'a [u8], 1>; +pub type Output = Image<Vec<u8>, 1>; +pub type Uninit = fimg::uninit::Image<u8, 1>; +use crate::encode::{encodeu32, P}; +use atools::prelude::*; +use fimg::Image; + +#[cfg(test)] +fn tdata() -> &'static [u8] { + include_bytes!("../tdata/fimg-gray.imgbuf") +} + +/// Module for handling plain ascii (human readable) [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) (Y) images. +pub mod plain { + + use crate::encode::encode_; + + use super::*; + pub const MAGIC: u8 = 2; + + /// Encode an <code>[Image]<[u8], 1></code> into a [PBM](https://en.wikipedia.org/wiki/Netpbm#PBM_example) ASCII Image. + pub fn encode<T: AsRef<[u8]>>(x: Image<T, 1>) -> String { + let mut y = Vec::with_capacity(size(x.as_ref())); + let n = unsafe { encode_into(x.as_ref(), y.as_mut_ptr()) }; + unsafe { y.set_len(n) }; + unsafe { String::from_utf8_unchecked(y) } + } + + crate::decode::dec_fn! { + max "Decode an ASCII [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) image into an <code>[Image]<[Box]<[u8]>, 1></code>" + } + + #[doc = include_str!("decode_body_into.md")] + pub fn decode_body_into(x: &[u8], mut into: Uninit, max: u8) -> Result<Output> { + let mut out = into.buf().as_mut_ptr() as *mut u8; + let pixels = into.width() * into.height(); + for b in x + .split(u8::is_ascii_whitespace) + .filter(|x| !x.is_empty() && x.len() <= 3) + .filter(|x| x.iter().all(u8::is_ascii_digit)) + .map(|x| x.iter().fold(0, |acc, &x| acc * 10 + (x - b'0'))) + .map(|x| { + if max == 255 { + x + } else { + ((x as f32 / max as f32) * 255.) as u8 + } + }) + .take(pixels as usize) + { + // SAFETY: iterator over `pixels` elements. + unsafe { out.push(b) }; + } + if unsafe { out.sub_ptr(into.buf().as_mut_ptr().cast()) < pixels as usize } { + return Err(Error::MissingData); + } + // SAFETY: checked that the pixels have been initialized. + Ok(unsafe { into.assume_init() }) + } + + #[doc = include_str!("encode_into.md")] + pub unsafe fn encode_into(x: Input, out: *mut u8) -> usize { + let mut o = out; + o.put(b'P'.join(MAGIC + b'0')); + o.push(b' '); + encodeu32(x.width(), &mut o); + o.push(b' '); + encodeu32(x.height(), &mut o); + o.put(*b" 255\n"); + for row in x.buffer().chunks_exact(x.width() as _) { + for &on in row { + o.put(encode_(on)); + } + // cosmetic + o.push(b'\n'); + } + o.sub_ptr(out) + } + + #[doc = include_str!("est.md")] + pub fn size(x: Input) -> usize { + 2 // P1 + + 23 // \n4294967295 4294967295\n + + x.height() as usize // \n + + x.len() * 4 // '255 ' + } + + #[test] + fn test_encode() { + assert_eq!( + encode(Image::build(20, 15).buf(tdata())), + include_str!("../tdata/fimgA.pgm") + ); + } + + #[test] + fn test_decode() { + assert_eq!( + &**decode(include_bytes!("../tdata/fimgA.pgm")) + .unwrap() + .buffer(), + tdata() + ) + } +} + +/// Module for handling raw (binary) [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) (gray) images. +pub mod raw { + use super::*; + pub const MAGIC: u8 = 5; + /// Encode an <code>[Image]<[u8], 1></code> [PBM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) Raw (binary) Image. + pub fn encode<T: AsRef<[u8]>>(x: Image<T, 1>) -> Vec<u8> { + let mut y = Vec::with_capacity(size(x.as_ref())); + let n = unsafe { encode_into(x.as_ref(), y.as_mut_ptr()) }; + unsafe { y.set_len(n) }; + y + } + + crate::decode::dec_fn! { + "Decode a raw binary [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) image into an <code>[Image]<[Box]<[u8]>, 1></code>" + } + + #[doc = include_str!("encode_into.md")] + pub unsafe fn encode_into(x: Input, out: *mut u8) -> usize { + let mut o = out; + o.put(b'P'.join(MAGIC + b'0')); + o.push(b' '); + encodeu32(x.width(), &mut o); + o.push(b' '); + encodeu32(x.height(), &mut o); + o.put(*b" 255\n"); + for &x in *x.buffer() { + o.push(x) + } + + o.sub_ptr(out) + } + + #[doc = include_str!("decode_body_into.md")] + pub fn decode_body_into(x: &[u8], mut into: Uninit) -> Result<Output> { + let mut out = into.buf().as_mut_ptr() as *mut u8; + let pixels = into.width() * into.height(); + for b in x.iter().copied().take(pixels as _) { + // SAFETY: took `pixels` pixels. + unsafe { out.push(b) }; + } + if unsafe { out.sub_ptr(into.buf().as_mut_ptr().cast()) < pixels as usize } { + return Err(Error::MissingData); + } + // SAFETY: checked that the pixels have been initialized. + Ok(unsafe { into.assume_init() }) + } + + #[doc = include_str!("est.md")] + pub fn size(x: Input) -> usize { + 2 // magic + + 23 // w h + + x.len() // data + } + + #[test] + fn test_decode() { + assert_eq!( + &**decode(include_bytes!("../tdata/fimgR.pgm")) + .unwrap() + .buffer(), + tdata() + ) + } + + #[test] + fn test_encode() { + assert_eq!( + encode(Image::build(20, 15).buf(tdata())), + include_bytes!("../tdata/fimgR.pgm") + ); + } +} |