//! 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(unsafe_op_in_unsafe_fn)] 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 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::::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 { unsafe fn put(&mut self, x: [T; N]); } impl P for *mut T { #[cfg_attr(debug_assertions, track_caller)] unsafe fn put(&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 { let mut adler = simd_adler32::Adler32::new(); let (chunks, remainder) = data.as_chunks::(); // SAFETY: deflate_size is very correct. let mut out = Vec::::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 }