use std::io::Cursor;
use ::image::error::ImageError as CodecError;
use ::image::{ColorType, ImageDecoder, ImageFormat, ImageReader, Limits};
use bytes::Bytes;
use super::buffer::{invalid, too_large, with_capacity};
use super::{Image, ImageError};
use crate::{error::Result, transform::Transform};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DecodeLimits {
pub max_width: u32,
pub max_height: u32,
pub max_alloc: u64,
}
impl Default for DecodeLimits {
fn default() -> Self {
Self {
max_width: 16_384,
max_height: 16_384,
max_alloc: 256 << 20,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Decode {
limits: DecodeLimits,
}
impl Decode {
pub fn new() -> Self {
Self::default()
}
pub fn with_limits(limits: DecodeLimits) -> Self {
Self { limits }
}
pub fn limits(&self) -> DecodeLimits {
self.limits
}
pub fn decode(&self, bytes: &[u8]) -> Result<Image<u8>> {
let reader = ImageReader::new(Cursor::new(bytes))
.with_guessed_format()
.map_err(|e| decode_error(e.to_string()))?;
match reader.format() {
None => {
return Err(decode_error(
"unknown format (expected PNG, JPEG, WebP, TIFF or BMP)".into(),
));
}
Some(ImageFormat::Jpeg) if !jpeg_is_complete(bytes) => {
return Err(decode_error(
"truncated JPEG: no end-of-image marker after the last scan".into(),
));
}
Some(_) => {}
}
let mut reader = reader;
reader.limits(self.codec_limits());
let decoder = reader.into_decoder().map_err(codec_error)?;
let (width, height) = decoder.dimensions();
self.check_size(width, height)?;
let color = decoder.color_type();
let channels = channels(color)?;
let total = decoder.total_bytes();
if total > self.limits.max_alloc {
return Err(too_large(format!(
"{width}x{height} {color:?} is {total} bytes, above the limit of {}",
self.limits.max_alloc
)));
}
let total = usize::try_from(total).map_err(|_| too_large(format!("{total} bytes")))?;
let mut raw = with_capacity(total)?;
raw.resize(total, 0);
decoder.read_image(&mut raw).map_err(codec_error)?;
let data = match color.bytes_per_pixel() as usize == channels {
true => raw,
false => raw
.as_chunks::<2>()
.0
.iter()
.map(|&pair| (u16::from_ne_bytes(pair) >> 8) as u8)
.collect(),
};
Image::new(width as usize, height as usize, channels, data)
}
fn codec_limits(&self) -> Limits {
let mut limits = Limits::default();
limits.max_image_width = Some(self.limits.max_width);
limits.max_image_height = Some(self.limits.max_height);
limits.max_alloc = Some(self.limits.max_alloc);
limits
}
fn check_size(&self, width: u32, height: u32) -> Result<()> {
if width == 0 || height == 0 {
return Err(decode_error(format!("empty image of {width}x{height}")));
}
let DecodeLimits {
max_width,
max_height,
..
} = self.limits;
if width > max_width || height > max_height {
return Err(too_large(format!(
"{width}x{height} is above the limit of {max_width}x{max_height}"
)));
}
Ok(())
}
}
impl Transform<Bytes> for Decode {
type Output = Image<u8>;
fn apply(&self, _index: usize, bytes: Bytes) -> Result<Image<u8>> {
self.decode(&bytes)
}
}
fn channels(color: ColorType) -> Result<usize> {
match color {
ColorType::L8 | ColorType::L16 => Ok(1),
ColorType::La8 | ColorType::La16 => Ok(2),
ColorType::Rgb8 | ColorType::Rgb16 => Ok(3),
ColorType::Rgba8 | ColorType::Rgba16 => Ok(4),
other => Err(invalid(format!("unsupported pixel type {other:?}"))),
}
}
fn jpeg_is_complete(bytes: &[u8]) -> bool {
let Some(end) = bytes.windows(2).rposition(|w| w == [0xFF, 0xD9]) else {
return false;
};
!bytes[end..].windows(2).any(|w| w == [0xFF, 0xDA])
}
fn decode_error(message: String) -> crate::Error {
ImageError::Decode(message).into()
}
fn codec_error(error: CodecError) -> crate::Error {
match error {
CodecError::Limits(limits) => too_large(limits.to_string()),
other => decode_error(other.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::{Decode, DecodeLimits, jpeg_is_complete};
use crate::image::ImageError;
#[test]
fn unknown_bytes_are_an_error() {
for bytes in [&b""[..], b"hello", &[0u8; 64]] {
let err = Decode::new().decode(bytes).unwrap_err();
let err = err.downcast_ref::<ImageError>().unwrap();
assert!(matches!(err, ImageError::Decode(_)), "{err}");
}
}
#[test]
fn a_jpeg_is_complete_when_it_ends_after_its_last_scan() {
assert!(jpeg_is_complete(&[
0xFF, 0xD8, 0xFF, 0xDA, 1, 2, 0xFF, 0xD9
]));
assert!(!jpeg_is_complete(&[0xFF, 0xD8, 0xFF, 0xDA, 1, 2]));
assert!(!jpeg_is_complete(&[0xFF, 0xD9, 0xFF, 0xDA, 1, 2]));
}
#[test]
fn default_limits() {
let limits = Decode::new().limits();
assert_eq!(limits, DecodeLimits::default());
assert_eq!((limits.max_width, limits.max_height), (16_384, 16_384));
assert_eq!(limits.max_alloc, 256 << 20);
}
}