//! A set of test utilities. //! //! There is some overlap between this module and `src/encoder.rs` module, but: //! //! * This module (unlike `src/encoder.rs`) performs no validation of the data being written - this //! allows building testcases that use arbitrary, potentially invalid PNGs as input. //! * This module can be reused from `benches/decoder.rs` (a separate crate). use byteorder::WriteBytesExt; use std::io::Write; /// Generates a store-only, non-compressed image: /// /// * `00` compression mode (i.e.`BTYPE` = `00` = no compression) is used /// * No filter is applied to the image rows /// /// Currently the image always has the following properties: /// /// * Single `IDAT` chunk /// * Zlib chunks of maximum possible size /// * 8-bit RGBA /// /// These images are somewhat artificial, but may be useful for benchmarking performance of parts /// outside of `fdeflate` crate and/or the `unfilter` function (e.g. these images were originally /// used to evaluate changes to minimize copying of image pixels between various buffers - see /// [this /// discussion](https://github.com/image-rs/image-png/discussions/416#discussioncomment-7436871) /// for more details). #[allow(dead_code)] // Used from `benches/decoder.rs` pub fn write_noncompressed_png(w: &mut impl Write, size: u32, idat_bytes: usize) { write_png_sig(w); write_rgba8_ihdr_with_width(w, size); write_rgba8_idats(w, size, idat_bytes); write_iend(w); } /// Writes PNG signature. /// See http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#PNG-file-signature pub fn write_png_sig(w: &mut impl Write) { const SIG: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; w.write_all(&SIG).unwrap(); } /// Writes an arbitrary PNG chunk. pub fn write_chunk(w: &mut impl Write, chunk_type: &[u8], data: &[u8]) { assert_eq!(chunk_type.len(), 4); let crc = { let input = chunk_type .iter() .copied() .chain(data.iter().copied()) .collect::>(); crc32fast::hash(input.as_slice()) }; w.write_u32::(data.len() as u32) .unwrap(); w.write_all(chunk_type).unwrap(); w.write_all(data).unwrap(); w.write_u32::(crc).unwrap(); } /// Writes an IHDR chunk that indicates a non-interlaced RGBA8 that uses the same height and /// `width`. See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR pub fn write_rgba8_ihdr_with_width(w: &mut impl Write, width: u32) { let mut data = Vec::new(); data.write_u32::(width).unwrap(); data.write_u32::(width).unwrap(); // height data.write_u8(8).unwrap(); // bit depth = always 8-bits per channel data.write_u8(6).unwrap(); // color type = color + alpha data.write_u8(0).unwrap(); // compression method (0 is the only allowed value) data.write_u8(0).unwrap(); // filter method (0 is the only allowed value) data.write_u8(0).unwrap(); // interlace method = no interlacing write_chunk(w, b"IHDR", &data); } /// Generates RGBA8 `width` x `height` image and wraps it in a store-only zlib container. pub fn generate_rgba8_with_width_and_height(width: u32, height: u32) -> Vec { // Generate arbitrary test pixels. let image_pixels = { let mut row = Vec::new(); row.write_u8(0).unwrap(); // filter = no filter let row_pixels = (0..width).flat_map(|i| { let color: u8 = (i * 255 / width) as u8; let alpha: u8 = 0xff; [color, 255 - color, color / 2, alpha] }); row.extend(row_pixels); std::iter::repeat(row) .take(height as usize) .flatten() .collect::>() }; let mut zlib_data = Vec::new(); let mut store_only_compressor = fdeflate::StoredOnlyCompressor::new(std::io::Cursor::new(&mut zlib_data)).unwrap(); store_only_compressor.write_data(&image_pixels).unwrap(); store_only_compressor.finish().unwrap(); zlib_data } /// Writes an IDAT chunk. pub fn write_rgba8_idats(w: &mut impl Write, size: u32, idat_bytes: usize) { let data = generate_rgba8_with_width_and_height(size, size); for chunk in data.chunks(idat_bytes) { write_chunk(w, b"IDAT", chunk); } } /// Writes an IEND chunk. /// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IEND pub fn write_iend(w: &mut impl Write) { write_chunk(w, b"IEND", &[]); }