use bitfrob::{u8_bit_split_1x8_rev, u8_bit_split_2x4_rev, u8_bit_split_4x2_rev};
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum PngColorType {
Y = 0,
RGB = 2,
Index = 3,
YA = 4,
RGBA = 6,
}
impl PngColorType {
#[inline]
pub const fn channel_count(self) -> usize {
match self {
Self::Y => 1,
Self::RGB => 3,
Self::Index => 1,
Self::YA => 2,
Self::RGBA => 4,
}
}
}
impl TryFrom<u8> for PngColorType {
type Error = ();
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => PngColorType::Y,
2 => PngColorType::RGB,
3 => PngColorType::Index,
4 => PngColorType::YA,
6 => PngColorType::RGBA,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IHDR {
pub width: u32,
pub height: u32,
pub bit_depth: u8,
pub color_type: PngColorType,
pub is_interlaced: bool,
}
impl IHDR {
#[doc(hidden)]
#[inline]
pub const fn bytes_per_filterline(&self, width: u32) -> usize {
1 + ((self.bits_per_pixel() * (width as usize)) + 7) / 8
}
#[inline]
pub fn get_zlib_decompression_requirement(&self) -> usize {
#[inline]
#[must_use]
fn temp_bytes_for_image(
width: u32, height: u32, color_type: PngColorType, bit_depth: u8,
) -> usize {
if width == 0 {
return 0;
}
let bits_per_pixel: usize = color_type.channel_count().saturating_mul(bit_depth as usize);
let bits_per_line: usize = bits_per_pixel.saturating_mul(width as usize);
let bytes_per_scanline: usize = (bits_per_line / 8) + (bits_per_line % 8 != 0) as usize;
let bytes_per_filterline: usize = bytes_per_scanline.saturating_add(1);
bytes_per_filterline.saturating_mul(height as usize)
}
if self.is_interlaced {
let mut total = 0_usize;
for (width, height) in reduced_image_dimensions(self.width, self.height).into_iter().skip(1) {
total = total.saturating_add(temp_bytes_for_image(
width,
height,
self.color_type,
self.bit_depth,
));
}
total
} else {
temp_bytes_for_image(self.width, self.height, self.color_type, self.bit_depth)
}
}
#[doc(hidden)]
#[inline]
pub const fn bits_per_pixel(&self) -> usize {
(self.bit_depth as usize) * self.color_type.channel_count()
}
}
impl TryFrom<PngChunk<'_>> for IHDR {
type Error = ();
#[inline]
fn try_from(value: PngChunk<'_>) -> Result<Self, Self::Error> {
match value {
PngChunk::IHDR(ihdr) => Ok(ihdr),
_ => Err(()),
}
}
}
impl TryFrom<&[u8]> for IHDR {
type Error = ();
#[inline]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value {
[w0, w1, w2, w3, h0, h1, h2, h3, bit_depth, color_type, _compression_method, _filter_method, interlace_method] => {
Ok(Self {
width: u32::from_be_bytes([*w0, *w1, *w2, *w3]),
height: u32::from_be_bytes([*h0, *h1, *h2, *h3]),
bit_depth: match *color_type {
0 if [1, 2, 4, 8, 16].contains(bit_depth) => *bit_depth,
2 if [8, 16].contains(bit_depth) => *bit_depth,
3 if [1, 2, 4, 8].contains(bit_depth) => *bit_depth,
4 if [8, 16].contains(bit_depth) => *bit_depth,
6 if [8, 16].contains(bit_depth) => *bit_depth,
_ => return Err(()),
},
color_type: PngColorType::try_from(*color_type)?,
is_interlaced: match interlace_method {
0 => false,
1 => true,
_ => return Err(()),
},
})
}
_ => Err(()),
}
}
}
impl IHDR {
fn send_out_pixel<F: FnMut(u32, u32, &[u8])>(
&self, image_level: usize, reduced_x: u32, reduced_y: u32, data: &[u8], op: &mut F,
) {
let full_width = self.width;
match self.bit_depth {
1 => {
for (plus_x, pix) in u8_bit_split_1x8_rev(data[0]).into_iter().enumerate() {
let (image_x, image_y): (u32, u32) =
interlaced_pos_to_full_pos(image_level, reduced_x * 8 + (plus_x as u32), reduced_y);
if image_x >= full_width {
return;
}
op(image_x, image_y, &[pix]);
}
}
2 => {
for (plus_x, pix) in u8_bit_split_2x4_rev(data[0]).into_iter().enumerate() {
let (image_x, image_y) =
interlaced_pos_to_full_pos(image_level, reduced_x * 4 + (plus_x as u32), reduced_y);
if image_x >= full_width {
return;
}
op(image_x, image_y, &[pix]);
}
}
4 => {
for (plus_x, pix) in u8_bit_split_4x2_rev(data[0]).into_iter().enumerate() {
let (image_x, image_y) =
interlaced_pos_to_full_pos(image_level, reduced_x * 2 + (plus_x as u32), reduced_y);
if image_x >= full_width {
return;
}
op(image_x, image_y, &[pix]);
}
}
8 | 16 => {
let (image_x, image_y) = interlaced_pos_to_full_pos(image_level, reduced_x, reduced_y);
op(image_x, image_y, data);
}
_ => unreachable!(),
}
}
#[allow(clippy::result_unit_err)]
#[allow(clippy::missing_inline_in_public_items)]
pub fn unfilter_decompressed_data<F>(
&self, mut decompressed: &mut [u8], mut op: F,
) -> Result<(), ()>
where
F: FnMut(u32, u32, &[u8]),
{
if self.width == 0 || self.height == 0 {
return Err(());
}
let mut image_it = reduced_image_dimensions(self.width, self.height)
.into_iter()
.enumerate()
.map(|(i, (w, h))| (i, w, h))
.take(if self.is_interlaced { 8 } else { 1 });
if self.is_interlaced {
image_it.next();
}
for (image_level, reduced_width, reduced_height) in image_it {
if reduced_width == 0 || reduced_height == 0 {
continue;
}
let bytes_per_filterline = self.bytes_per_filterline(reduced_width);
let bytes_used_this_image = bytes_per_filterline.saturating_mul(reduced_height as _);
let (these_bytes, bytes_per_filterline) = if decompressed.len() < bytes_used_this_image {
return Err(());
} else {
let (these_bytes, more_bytes) = decompressed.split_at_mut(bytes_used_this_image);
decompressed = more_bytes;
(these_bytes, bytes_per_filterline)
};
let row_iter = these_bytes.chunks_exact_mut(bytes_per_filterline);
match self.color_type {
PngColorType::Y => match self.bit_depth {
16 => png_filters::unfilter_lines::<2>(row_iter),
8 | 4 | 2 | 1 => png_filters::unfilter_lines::<1>(row_iter),
_ => return Err(()),
},
PngColorType::RGB => match self.bit_depth {
8 => png_filters::unfilter_lines::<3>(row_iter),
16 => png_filters::unfilter_lines::<6>(row_iter),
_ => return Err(()),
},
PngColorType::Index => match self.bit_depth {
8 | 4 | 2 | 1 => png_filters::unfilter_lines::<1>(row_iter),
_ => return Err(()),
},
PngColorType::YA => match self.bit_depth {
8 => png_filters::unfilter_lines::<2>(row_iter),
16 => png_filters::unfilter_lines::<4>(row_iter),
_ => return Err(()),
},
PngColorType::RGBA => match self.bit_depth {
8 => png_filters::unfilter_lines::<4>(row_iter),
16 => png_filters::unfilter_lines::<8>(row_iter),
_ => return Err(()),
},
};
let filter_chunk_size = match self.color_type {
PngColorType::Y => match self.bit_depth {
16 => 2,
8 | 4 | 2 | 1 => 1,
_ => return Err(()),
},
PngColorType::RGB => match self.bit_depth {
8 => 3,
16 => 6,
_ => return Err(()),
},
PngColorType::Index => match self.bit_depth {
8 | 4 | 2 | 1 => 1,
_ => return Err(()),
},
PngColorType::YA => match self.bit_depth {
8 => 2,
16 => 4,
_ => return Err(()),
},
PngColorType::RGBA => match self.bit_depth {
8 => 4,
16 => 8,
_ => return Err(()),
},
};
these_bytes
.chunks_exact(bytes_per_filterline)
.enumerate()
.flat_map(|(y, line)| {
let (_, line) = line.split_first().unwrap();
line.chunks_exact(filter_chunk_size).enumerate().map(move |(x, px)| (x, y, px))
})
.for_each(|(reduced_, reduced_y, data)| {
self.send_out_pixel(image_level, reduced_ as _, reduced_y as _, data, &mut op)
});
}
Ok(())
}
}