use crate::colors::{BitDepth, ColorType}; use crate::error::PngError; use crate::PngResult; use byteorder::{BigEndian, ReadBytesExt}; use crc::crc32; use indexmap::IndexSet; use std::io::Cursor; #[derive(Debug, Clone, Copy)] /// Headers from the IHDR chunk of the image pub struct IhdrData { /// The width of the image in pixels pub width: u32, /// The height of the image in pixels pub height: u32, /// The color type of the image pub color_type: ColorType, /// The bit depth of the image pub bit_depth: BitDepth, /// The compression method used for this image (0 for DEFLATE) pub compression: u8, /// The filter mode used for this image (currently only 0 is valid) pub filter: u8, /// The interlacing mode of the image (0 = None, 1 = Adam7) pub interlaced: u8, } impl IhdrData { /// Bits per pixel pub fn bpp(&self) -> u8 { self.bit_depth.as_u8() * self.color_type.channels_per_pixel() } /// Byte length of IDAT that is correct for this IHDR pub fn raw_data_size(&self) -> usize { let w = self.width as usize; let h = self.height as usize; let bpp = self.bpp(); fn bitmap_size(bpp: u8, w: usize, h: usize) -> usize { (((w / 8) * bpp as usize) + ((w & 7) * bpp as usize + 7) / 8) * h } if self.interlaced == 0 { bitmap_size(bpp, w, h) + h } else { let mut size = bitmap_size(bpp, (w + 7) >> 3, (h + 7) >> 3) + ((h + 7) >> 3); if w > 4 { size += bitmap_size(bpp, (w + 3) >> 3, (h + 7) >> 3) + ((h + 7) >> 3); } size += bitmap_size(bpp, (w + 3) >> 2, (h + 3) >> 3) + ((h + 3) >> 3); if w > 2 { size += bitmap_size(bpp, (w + 1) >> 2, (h + 3) >> 2) + ((h + 3) >> 2); } size += bitmap_size(bpp, (w + 1) >> 1, (h + 1) >> 2) + ((h + 1) >> 2); if w > 1 { size += bitmap_size(bpp, w >> 1, (h + 1) >> 1) + ((h + 1) >> 1); } size + bitmap_size(bpp, w, h >> 1) + (h >> 1) } } } #[derive(Debug, PartialEq, Clone)] /// Options to use for performing operations on headers (such as stripping) pub enum Headers { /// None None, /// Remove specific chunks Strip(Vec), /// Headers that won't affect rendering (all but cHRM, gAMA, iCCP, sBIT, sRGB, bKGD, hIST, pHYs, sPLT) Safe, /// Remove all non-critical chunks except these Keep(IndexSet), /// All non-critical headers All, } #[inline] pub fn file_header_is_valid(bytes: &[u8]) -> bool { let expected_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; *bytes == expected_header } #[derive(Debug, Clone, Copy)] pub struct RawHeader<'a> { pub name: [u8; 4], pub data: &'a [u8], } pub fn parse_next_header<'a>( byte_data: &'a [u8], byte_offset: &mut usize, fix_errors: bool, ) -> PngResult>> { let mut rdr = Cursor::new( byte_data .get(*byte_offset..*byte_offset + 4) .ok_or(PngError::TruncatedData)?, ); let length = rdr.read_u32::().unwrap(); *byte_offset += 4; let header_start = *byte_offset; let chunk_name = byte_data .get(header_start..header_start + 4) .ok_or(PngError::TruncatedData)?; if chunk_name == b"IEND" { // End of data return Ok(None); } *byte_offset += 4; let data = byte_data .get(*byte_offset..*byte_offset + length as usize) .ok_or(PngError::TruncatedData)?; *byte_offset += length as usize; let mut rdr = Cursor::new( byte_data .get(*byte_offset..*byte_offset + 4) .ok_or(PngError::TruncatedData)?, ); let crc = rdr.read_u32::().unwrap(); *byte_offset += 4; let header_bytes = byte_data .get(header_start..header_start + 4 + length as usize) .ok_or(PngError::TruncatedData)?; if !fix_errors && crc32::checksum_ieee(header_bytes) != crc { return Err(PngError::new(&format!( "CRC Mismatch in {} header; May be recoverable by using --fix", String::from_utf8_lossy(chunk_name) ))); } let mut name = [0u8; 4]; name.copy_from_slice(chunk_name); Ok(Some(RawHeader { name, data })) } pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult { // This eliminates bounds checks for the rest of the function let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?; let mut rdr = Cursor::new(&byte_data[0..8]); Ok(IhdrData { color_type: match byte_data[9] { 0 => ColorType::Grayscale, 2 => ColorType::RGB, 3 => ColorType::Indexed, 4 => ColorType::GrayscaleAlpha, 6 => ColorType::RGBA, _ => return Err(PngError::new("Unexpected color type in header")), }, bit_depth: match byte_data[8] { 1 => BitDepth::One, 2 => BitDepth::Two, 4 => BitDepth::Four, 8 => BitDepth::Eight, 16 => BitDepth::Sixteen, _ => return Err(PngError::new("Unexpected bit depth in header")), }, width: rdr.read_u32::().unwrap(), height: rdr.read_u32::().unwrap(), compression: byte_data[10], filter: byte_data[11], interlaced, }) }