use core::convert::TryInto;
use core::fmt;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use crc32fast::Hasher;
use crate::encoder::{EncodeAt, ImageEncoder};
use crate::util::{read_checked, read_u8_len4_array, split_to_checked};
use crate::{Error, Result};
#[derive(Clone, PartialEq, Eq)]
pub struct PngChunk {
kind: [u8; 4],
contents: Bytes,
crc: [u8; 4],
}
#[allow(clippy::len_without_is_empty)]
impl PngChunk {
pub fn new(kind: [u8; 4], contents: Bytes) -> PngChunk {
let crc = compute_crc(kind, &contents);
Self::new_with_crc(kind, contents, crc)
}
#[inline]
fn new_with_crc(kind: [u8; 4], contents: Bytes, crc: [u8; 4]) -> PngChunk {
PngChunk {
kind,
contents,
crc,
}
}
pub fn from_bytes(b: &mut Bytes) -> Result<PngChunk> {
let size = read_checked(b, |b| b.get_u32())?;
let kind = read_u8_len4_array(b)?;
let contents = split_to_checked(b, size as usize)?;
let crc = read_u8_len4_array(b)?;
if crc != compute_crc(kind, &contents) {
return Err(Error::BadCRC);
}
Ok(PngChunk::new_with_crc(kind, contents, crc))
}
pub fn len(&self) -> usize {
4 + 4 + self.contents.len() + 4
}
#[inline]
pub fn kind(&self) -> [u8; 4] {
self.kind
}
#[inline]
pub fn contents(&self) -> &Bytes {
&self.contents
}
#[inline]
pub fn encoder(self) -> ImageEncoder<Self> {
ImageEncoder::from(self)
}
}
impl EncodeAt for PngChunk {
fn encode_at(&self, pos: &mut usize) -> Option<Bytes> {
match pos {
0 => {
let mut bytes = BytesMut::with_capacity(8);
bytes.put_u32(self.contents.len().try_into().unwrap());
bytes.extend_from_slice(&self.kind);
Some(bytes.freeze())
}
1 => Some(self.contents.clone()),
2 => Some(Bytes::copy_from_slice(&self.crc)),
_ => {
*pos -= 3;
None
}
}
}
fn len(&self) -> usize {
self.len()
}
}
impl fmt::Debug for PngChunk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PngChunk")
.field("kind", &self.kind)
.finish()
}
}
fn compute_crc(kind: [u8; 4], contents: &[u8]) -> [u8; 4] {
let mut hasher = Hasher::new();
hasher.update(&kind);
hasher.update(contents);
let crc = hasher.finalize();
crc.to_be_bytes()
}