png is a bitmap format? who knew!
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..41d3e86 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,207 @@ +//! uncompressing png encoding crate +//! ``` +//! # let mut v = vec![]; +//! pngenc::ode( +//! pngenc::RGB, // color type +//! (2144, 1424), // width and height +//! include_bytes!("../benches/cat"), // image to encode +//! # /* +//! &mut std::fs::File::create("hey.png").unwrap(), // output writer +//! # */ +//! # &mut v, +//! ).unwrap(); +//! # assert_eq!(crc32fast::hash(&v), 0xd7d47c0e); +//! ``` +#![allow(incomplete_features)] +#![feature(generic_const_exprs, test, slice_as_chunks, array_chunks)] +use atools::prelude::*; +use std::{ + io::{self, Write}, + iter::once, +}; + +pub use Color::*; + +#[derive(Copy, Debug, Clone)] +#[repr(u8)] +/// Color types. +pub enum Color { + /// Grayscale + Y = 1, + /// Grayscale with alpha + YA, + /// Red, green, blue + RGB, + /// RGB with alpha + RGBA, +} + +impl Color { + /// Color depth (number of channels) + #[must_use] + pub const fn depth(self) -> u8 { + self as u8 + } + + const fn ty(self) -> u8 { + match self { + Color::Y => 0, + Color::YA => 4, + Color::RGB => 2, + Color::RGBA => 6, + } + } +} + +trait W: Write { + fn u32(&mut self, x: u32) -> io::Result<()> { + self.write_all(&x.to_be_bytes()) + } + fn w(&mut self, x: impl AsRef<[u8]>) -> io::Result<()> { + self.write_all(x.as_ref()) + } +} + +impl<T: Write> W for T {} + +const HEADER: &[u8; 8] = b"\x89PNG\x0d\x0a\x1a\x0a"; + +fn chunk_len(x: usize) -> usize { + 4 // length + + 4 // type + + x // data + + 4 // crc +} + +/// Get the size of an encoded png. Guaranteed to exactly equal the size of the encoded png. +pub fn size(color: Color, (width, height): (u32, u32)) -> usize { + HEADER.len() + + chunk_len(13) // IHDR + + chunk_len(deflate_size(((width * color.depth() as u32 + 1) * height) as usize)) // IDAT + + chunk_len(1) // sRGB + + chunk_len(0) // IEND +} + +#[doc(alias = "encode")] +/// Encode a png without any compression. +/// Takes advantage of the [Non-compressed blocks](http://www.zlib.org/rfc-deflate.html#noncompressed) deflate feature. +/// +/// If you *do* want a compressed image, I recommend the [oxipng](https://docs.rs/oxipng/latest/oxipng/struct.RawImage.html) raw image api. +/// +/// # Panics +/// +/// if your width * height * color depth isnt data's length +pub fn ode( + color: Color, + (width, height): (u32, u32), + data: &[u8], + to: &mut impl Write, +) -> std::io::Result<()> { + assert_eq!( + (width as usize * height as usize) + .checked_mul(color.depth() as usize) + .unwrap(), + data.len(), + "please dont lie to me" + ); + to.w(HEADER)?; + chunk( + *b"IHDR", + &width + .to_be_bytes() + .couple(height.to_be_bytes()) + .join(8) // bit depth + .join(color.ty()) + .join(0) + .join(0) + .join(0), + to, + )?; + + // removing this allocation is not a performance gain + let mut scanned = Vec::<u8>::with_capacity(((width * color.depth() as u32 + 1) * height) as _); + let mut out = scanned.as_mut_ptr(); + + data.chunks(width as usize * color.depth() as usize) + // set filter type for each scanline + .flat_map(|x| once(0).chain(x.iter().copied())) + .for_each(|x| unsafe { + out.write(x); + out = out.add(1); + }); + unsafe { scanned.set_len(((width * color.depth() as u32 + 1) * height) as _) }; + + let data = deflate(&scanned); + chunk(*b"sRGB", &[0], to)?; + chunk(*b"IDAT", &data, to)?; + chunk(*b"IEND", &[], to)?; + Ok(()) +} + +fn chunk(ty: [u8; 4], data: &[u8], to: &mut impl Write) -> std::io::Result<()> { + to.u32(data.len() as _)?; + to.w(ty)?; + to.w(data)?; + let mut crc = crc32fast::Hasher::new(); + crc.update(&ty); + crc.update(data); + to.u32(crc.finalize())?; + Ok(()) +} + +fn deflate_size(x: usize) -> usize { + // 2 header bytes, each header of chunk, and add remainder chunk, along with 4 bytes for adler32 + 2 + 5 * (x / CHUNK_SIZE) + usize::from(x != (x / CHUNK_SIZE) * CHUNK_SIZE || x == 0) + x + 4 + 4 +} + +trait P<T: Copy> { + unsafe fn put<const N: usize>(&mut self, x: [T; N]); +} + +impl<T: Copy> P<T> for *mut T { + #[cfg_attr(debug_assertions, track_caller)] + unsafe fn put<const N: usize>(&mut self, x: [T; N]) { + self.copy_from(x.as_ptr(), N); + *self = self.add(N); + } +} + +const CHUNK_SIZE: usize = 0xffff; +fn deflate(data: &[u8]) -> Vec<u8> { + let mut adler = simd_adler32::Adler32::new(); + let (chunks, remainder) = data.as_chunks::<CHUNK_SIZE>(); + // SAFETY: deflate_size is very correct. + let mut out = Vec::<u8>::with_capacity(deflate_size(data.len())); + let mut optr = out.as_mut_ptr(); + /// return LSB and SLSB + fn split(n: u16) -> [u8; 2] { + [(n & 0xff) as u8, ((n >> 8) & 0xff) as u8] + } + // 32k window + unsafe { optr.put([0b1_111_000, 1]) }; + chunks.iter().for_each(|x| unsafe { + adler.write(x); + // http://www.zlib.org/rfc-deflate.html#noncompressed + optr.put( + [0b000] + // lsb and slsb [255, 255] + .couple(split(CHUNK_SIZE as _)) + // ones complement -- [0, 0] + .couple(split(CHUNK_SIZE as _).map(|x| !x)), + ); + optr.put(*x); + }); + unsafe { + adler.write(remainder); + optr.put( + [0b001] + .couple(split(CHUNK_SIZE as _)) + .couple(split(CHUNK_SIZE as _).map(|x| !x)), + ); + optr.copy_from(remainder.as_ptr(), remainder.len()); + optr = optr.add(remainder.len()); + }; + unsafe { optr.put(adler.finish().to_be_bytes()) }; + unsafe { out.set_len(deflate_size(data.len())) } + out +} |