use alloc::vec::Vec;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use miniz_oxide::deflate::compress_to_vec_zlib;
use miniz_oxide::inflate::decompress_to_vec_zlib;
use super::PngChunk;
use crate::encoder::{EncodeAt, ImageEncoder};
use crate::util::read_u8_len8_array;
use crate::{Error, ImageEXIF, ImageICC, Result};
pub(crate) const SIGNATURE: &[u8] = &[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
pub const CHUNK_ICCP: [u8; 4] = [b'i', b'C', b'C', b'P'];
pub const CHUNK_EXIF: [u8; 4] = [b'e', b'X', b'I', b'f'];
pub const CHUNK_IEND: [u8; 4] = [b'I', b'E', b'N', b'D'];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Png {
chunks: Vec<PngChunk>,
}
#[allow(clippy::len_without_is_empty)]
impl Png {
pub fn from_bytes(mut b: Bytes) -> Result<Png> {
let signature: [u8; SIGNATURE.len()] = read_u8_len8_array(&mut b)?;
if signature != SIGNATURE {
return Err(Error::WrongSignature);
}
let mut chunks = Vec::with_capacity(8);
while !b.is_empty() {
let chunk = PngChunk::from_bytes(&mut b)?;
let is_end = chunk.kind() == CHUNK_IEND;
chunks.push(chunk);
if is_end {
break;
}
}
Ok(Png { chunks })
}
#[inline]
pub fn chunks(&self) -> &Vec<PngChunk> {
&self.chunks
}
#[inline]
pub fn chunks_mut(&mut self) -> &mut Vec<PngChunk> {
&mut self.chunks
}
pub fn chunk_by_type(&self, kind: [u8; 4]) -> Option<&PngChunk> {
self.chunks.iter().find(|chunk| chunk.kind() == kind)
}
pub fn chunks_by_type(&self, kind: [u8; 4]) -> impl Iterator<Item = &PngChunk> {
self.chunks.iter().filter(move |chunk| chunk.kind() == kind)
}
pub fn remove_chunks_by_type(&mut self, kind: [u8; 4]) {
self.chunks_mut().retain(|chunk| chunk.kind() != kind);
}
pub fn len(&self) -> usize {
8 + self.chunks.iter().map(|chunk| chunk.len()).sum::<usize>()
}
#[inline]
pub fn encoder(self) -> ImageEncoder<Self> {
ImageEncoder::from(self)
}
}
impl EncodeAt for Png {
fn encode_at(&self, pos: &mut usize) -> Option<Bytes> {
match pos {
0 => Some(Bytes::from_static(SIGNATURE)),
_ => {
*pos -= 1;
for chunk in &self.chunks {
if let Some(bytes) = chunk.encode_at(pos) {
return Some(bytes);
}
}
None
}
}
}
fn len(&self) -> usize {
self.len()
}
}
impl ImageICC for Png {
fn icc_profile(&self) -> Option<Bytes> {
let mut contents = self.chunk_by_type(CHUNK_ICCP)?.contents().clone();
while contents.get_u8() != 0 {}
match contents.get_u8() {
0 => decompress_to_vec_zlib(&contents).ok().map(Bytes::from),
_ => None,
}
}
fn set_icc_profile(&mut self, profile: Option<Bytes>) {
self.remove_chunks_by_type(CHUNK_ICCP);
if let Some(profile) = profile {
let mut contents = BytesMut::with_capacity(profile.len());
contents.extend_from_slice(b"icc\0");
contents.put_u8(0);
let compressed = compress_to_vec_zlib(&profile, 10);
contents.extend_from_slice(&compressed);
let chunk = PngChunk::new(CHUNK_ICCP, contents.freeze());
self.chunks.insert(1, chunk);
}
}
}
impl ImageEXIF for Png {
fn exif(&self) -> Option<Bytes> {
Some(self.chunk_by_type(CHUNK_EXIF)?.contents().clone())
}
fn set_exif(&mut self, exif: Option<Bytes>) {
self.remove_chunks_by_type(CHUNK_EXIF);
if let Some(exif) = exif {
let chunk = PngChunk::new(CHUNK_EXIF, exif);
self.chunks.insert(self.chunks.len() - 1, chunk);
}
}
}