use image::codecs::png::PngDecoder;
use image::error::{DecodingError, ImageFormatHint, UnsupportedError};
use image::metadata::Orientation;
use image::{ColorType, ExtendedColorType, ImageDecoder, ImageError, ImageResult, Limits};
use ouroboros::self_referencing;
use std::io::{self, BufReader, Read, Seek};
use std::marker::PhantomData;
use zip::read::{ZipArchive, ZipFile};
pub struct OpenRasterDecoder<'a, R>
where
R: Read + Seek + 'a,
{
mergedimg_decoder: PngDecoder<BufReader<SeekableArchiveFile<'a, R>>>,
}
fn openraster_format_hint() -> ImageFormatHint {
ImageFormatHint::Name("OpenRaster".into())
}
fn set_ora_image_type(err: ImageError) -> ImageError {
match err {
ImageError::Decoding(e) => {
ImageError::Decoding(DecodingError::new(openraster_format_hint(), e))
}
ImageError::Encoding(_) => {
unreachable!();
}
ImageError::Parameter(e) => ImageError::Parameter(e),
ImageError::Limits(e) => ImageError::Limits(e),
ImageError::Unsupported(e) => ImageError::Unsupported(
UnsupportedError::from_format_and_kind(openraster_format_hint(), e.kind()),
),
ImageError::IoError(e) => ImageError::IoError(e),
}
}
#[self_referencing]
struct SeekableArchiveCore<'a, R: Read + Seek + 'a> {
archive: ZipArchive<R>,
#[covariant]
#[borrows(mut archive)]
file: ZipFile<'this, R>,
lifetime_helper: PhantomData<&'a R>,
}
struct SeekableArchiveFile<'a, R: Read + Seek + 'a> {
core: Option<SeekableArchiveCore<'a, R>>,
file_index: usize,
position: u64,
file_size: u64,
}
impl<'a, R: Read + Seek + 'a> SeekableArchiveFile<'a, R> {
fn new(
archive: ZipArchive<R>,
file_index: usize,
) -> Result<SeekableArchiveFile<'a, R>, io::Error> {
let core = SeekableArchiveCore::try_new(archive, |x| x.by_index(file_index), PhantomData)
.map_err(|x| io::Error::other(format!("failed to open: {:?}", x)))?;
let file_size = core.with_file(|file| file.size());
Ok(SeekableArchiveFile {
core: Some(core),
file_index,
position: 0,
file_size,
})
}
}
impl<R: Read + Seek> Read for SeekableArchiveFile<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let res = self
.core
.as_mut()
.unwrap()
.with_file_mut(|file| file.read(buf));
let nread = res?;
self.position
.checked_add(nread as u64)
.ok_or_else(|| io::Error::other("seek position overflow"))?;
Ok(nread)
}
}
impl<R: Read + Seek> Seek for SeekableArchiveFile<'_, R> {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let target_pos = match pos {
io::SeekFrom::Start(offset) => offset,
io::SeekFrom::End(offset) => self
.file_size
.checked_add_signed(offset)
.ok_or_else(|| io::Error::other("seek position over or underflow"))?,
io::SeekFrom::Current(offset) => self
.position
.checked_add_signed(offset)
.ok_or_else(|| io::Error::other("seek position over or underflow"))?,
};
if target_pos < self.position {
let core = self.core.take();
let archive = core.unwrap().into_heads().archive;
self.core = Some(
SeekableArchiveCore::try_new(archive, |x| x.by_index(self.file_index), PhantomData)
.map_err(|x| io::Error::other(format!("failed to reopen: {:?}", x)))?,
);
}
while self.position < target_pos {
const TMP_LEN: usize = 1024;
let mut tmp = [0_u8; TMP_LEN];
let cur_pos = self.position;
let nr = self
.read(&mut tmp[..std::cmp::min(TMP_LEN as u64, target_pos - cur_pos) as usize])?;
if nr == 0 {
return Err(io::Error::other("unexpected eof when seeking"));
}
self.position += nr as u64;
}
Ok(0)
}
}
impl<'a, R> OpenRasterDecoder<'a, R>
where
R: Read + Seek + 'a,
{
pub fn with_limits(r: R, limits: Limits) -> Result<OpenRasterDecoder<'a, R>, ImageError> {
let mut archive = ZipArchive::new(r)
.map_err(|e| ImageError::Decoding(DecodingError::new(openraster_format_hint(), e)))?;
let mimetype_index = archive.index_for_name("mimetype").ok_or_else(|| {
ImageError::Decoding(DecodingError::new(
openraster_format_hint(),
"OpenRaster images should contain a mimetype subfile",
))
})?;
let mut mimetype_file = archive
.by_index(mimetype_index)
.map_err(|x| ImageError::Decoding(DecodingError::new(openraster_format_hint(), x)))?;
const EXPECTED_MIMETYPE: &str = "image/openraster";
let mut tmp = [0u8; EXPECTED_MIMETYPE.len()];
mimetype_file.read_exact(&mut tmp)?;
if tmp != EXPECTED_MIMETYPE.as_bytes()
|| mimetype_file.size() != EXPECTED_MIMETYPE.len() as u64
{
return Err(ImageError::Decoding(DecodingError::new(
openraster_format_hint(),
"Image did not have correct mimetype subentry to be identified as OpenRaster",
)));
}
drop(mimetype_file);
let mergedimage_index = archive.index_for_name("mergedimage.png").ok_or_else(|| {
ImageError::Decoding(DecodingError::new(
openraster_format_hint(),
"OpenRaster image missing mergedimage.png entry",
))
})?;
let file = SeekableArchiveFile::new(archive, mergedimage_index)?;
let decoder =
PngDecoder::with_limits(BufReader::new(file), limits).map_err(set_ora_image_type)?;
Ok(OpenRasterDecoder {
mergedimg_decoder: decoder,
})
}
}
impl<'a, R: Read + Seek + 'a> ImageDecoder for OpenRasterDecoder<'a, R> {
fn dimensions(&self) -> (u32, u32) {
self.mergedimg_decoder.dimensions()
}
fn color_type(&self) -> ColorType {
self.mergedimg_decoder.color_type()
}
fn original_color_type(&self) -> ExtendedColorType {
self.mergedimg_decoder.original_color_type()
}
fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
self.mergedimg_decoder.set_limits(limits)
}
fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
self.mergedimg_decoder.icc_profile()
}
fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
self.mergedimg_decoder.exif_metadata()
}
fn orientation(&mut self) -> ImageResult<Orientation> {
self.mergedimg_decoder.orientation()
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
self.mergedimg_decoder.read_image(buf)
}
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
(*self).read_image(buf)
}
}