use anyhow::{Result, bail};
use png::{BitDepth, ColorType, Transformations};
use std::io::Cursor;
use tiny_skia::{ColorU8, IntSize, Pixmap};
const PNG_SIGNATURE: &[u8] = b"\x89PNG\r\n\x1a\n";
pub(super) const MAX_IMAGE_SIDE: u32 = 4096;
pub(super) const MAX_TOTAL_PIXELS: u64 = 64 * 1024 * 1024;
const BYTES_PER_PIXEL: usize = 4;
const DECODER_BYTES: usize = 96 * 1024 * 1024;
pub(super) struct Decoded {
pub pixels: u64,
pub pixmap: Option<Pixmap>,
}
const UNUSABLE: Decoded = Decoded {
pixels: 0,
pixmap: None,
};
pub(super) fn decode(bytes: &[u8], budget: u64) -> Result<Decoded> {
if !bytes.starts_with(PNG_SIGNATURE) {
return Ok(UNUSABLE);
}
let mut decoder = png::Decoder::new(Cursor::new(bytes));
decoder.set_limits(png::Limits {
bytes: DECODER_BYTES,
});
decoder.set_transformations(
Transformations::EXPAND | Transformations::STRIP_16 | Transformations::ALPHA,
);
let Ok(mut reader) = decoder.read_info() else {
return Ok(UNUSABLE);
};
let (width, height) = (reader.info().width, reader.info().height);
if width > MAX_IMAGE_SIDE || height > MAX_IMAGE_SIDE {
bail!("svga_render_image_too_large");
}
let pixels = u64::from(width) * u64::from(height);
if pixels > budget {
bail!("svga_render_image_pixels_exceed_limit");
}
let pixmap = reader.output_buffer_size().and_then(|size| {
let mut buffer = vec![0; size];
let info = reader.next_frame(&mut buffer).ok()?;
pixmap(&info, buffer.get(..info.buffer_size())?)
});
Ok(Decoded { pixels, pixmap })
}
fn pixmap(info: &png::OutputInfo, samples: &[u8]) -> Option<Pixmap> {
if info.bit_depth != BitDepth::Eight {
return None;
}
let rgba = match info.color_type {
ColorType::Rgba => premultiplied(samples.as_chunks::<4>().0.iter().copied()),
ColorType::GrayscaleAlpha => premultiplied(
(samples.as_chunks::<2>().0.iter()).map(|[gray, alpha]| [*gray, *gray, *gray, *alpha]),
),
_ => return None,
};
IntSize::from_wh(info.width, info.height)
.filter(|_| rgba.len() == pixel_bytes(info.width, info.height))
.and_then(|size| Pixmap::from_vec(rgba, size))
}
pub(super) fn ink(pixmap: &Pixmap) -> f64 {
let pixels = pixmap.pixels();
let total: u64 = pixels.iter().map(|pixel| u64::from(pixel.alpha())).sum();
total as f64 / (pixels.len().max(1) as f64 * f64::from(u8::MAX))
}
fn pixel_bytes(width: u32, height: u32) -> usize {
(width as usize)
.saturating_mul(height as usize)
.saturating_mul(BYTES_PER_PIXEL)
}
fn premultiplied(pixels: impl Iterator<Item = [u8; 4]>) -> Vec<u8> {
pixels
.flat_map(|[red, green, blue, alpha]| {
let color = ColorU8::from_rgba(red, green, blue, alpha).premultiply();
[color.red(), color.green(), color.blue(), color.alpha()]
})
.collect()
}