image_extras/
ora.rs

1//! Decoding of OpenRaster Images (*.ora)
2//!
3//! OpenRaster is an a file format used to communicate layered images; the
4//! decoder provided herein only extracts and displays the final merged raster
5//! image cached by the OpenRaster file, and does not expose the details of
6//! layers (which may be either raster or vector graphics) or render the merged
7//! image itself.
8//!
9//! # Related Links
10//! * <https://en.wikipedia.org/wiki/OpenRaster> - The OpenRaster format on Wikipedia
11//! * <https://www.openraster.org/> - OpenRaster specification
12
13use image::codecs::png::PngDecoder;
14use image::error::{DecodingError, ImageFormatHint, UnsupportedError};
15use image::metadata::Orientation;
16use image::{ColorType, ExtendedColorType, ImageDecoder, ImageError, ImageResult, Limits};
17use ouroboros::self_referencing;
18use std::io::{self, BufReader, Read, Seek};
19use std::marker::PhantomData;
20use zip::read::{ZipArchive, ZipFile};
21
22pub struct OpenRasterDecoder<'a, R>
23where
24    R: Read + Seek + 'a,
25{
26    mergedimg_decoder: PngDecoder<BufReader<SeekableArchiveFile<'a, R>>>,
27}
28
29fn openraster_format_hint() -> ImageFormatHint {
30    ImageFormatHint::Name("OpenRaster".into())
31}
32
33/// Adjust the format of the PngDecoder's errors to indicate OpenRaster instead
34fn set_ora_image_type(err: ImageError) -> ImageError {
35    match err {
36        ImageError::Decoding(e) => {
37            // DecodingError does not directly expose the underlying type,
38            // so nest the error
39            ImageError::Decoding(DecodingError::new(openraster_format_hint(), e))
40        }
41        ImageError::Encoding(_) => {
42            // Should not be encoding any files
43            unreachable!();
44        }
45        ImageError::Parameter(e) => ImageError::Parameter(e),
46        ImageError::Limits(e) => ImageError::Limits(e),
47        ImageError::Unsupported(e) => ImageError::Unsupported(
48            UnsupportedError::from_format_and_kind(openraster_format_hint(), e.kind()),
49        ),
50        ImageError::IoError(e) => ImageError::IoError(e),
51    }
52}
53
54#[self_referencing]
55struct SeekableArchiveCore<'a, R: Read + Seek + 'a> {
56    archive: ZipArchive<R>,
57    #[covariant]
58    #[borrows(mut archive)]
59    file: ZipFile<'this, R>,
60    lifetime_helper: PhantomData<&'a R>,
61}
62
63/// The zip crate does not provide a seekable reader that works on compressed
64/// entries, while png::Decoder requires the Seek bound (but does not currently
65/// use it). This structure implements Seek by reopening and reading the zip
66/// archive entry whenever it seeks backwards.
67struct SeekableArchiveFile<'a, R: Read + Seek + 'a> {
68    core: Option<SeekableArchiveCore<'a, R>>,
69    file_index: usize,
70    position: u64,
71    file_size: u64,
72}
73
74impl<'a, R: Read + Seek + 'a> SeekableArchiveFile<'a, R> {
75    fn new(
76        archive: ZipArchive<R>,
77        file_index: usize,
78    ) -> Result<SeekableArchiveFile<'a, R>, io::Error> {
79        let core = SeekableArchiveCore::try_new(archive, |x| x.by_index(file_index), PhantomData)
80            .map_err(|x| io::Error::other(format!("failed to open: {:?}", x)))?;
81        let file_size = core.with_file(|file| file.size());
82        Ok(SeekableArchiveFile {
83            core: Some(core),
84            file_index,
85            position: 0,
86            file_size,
87        })
88    }
89}
90
91impl<R: Read + Seek> Read for SeekableArchiveFile<'_, R> {
92    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
93        let res = self
94            .core
95            .as_mut()
96            .unwrap()
97            .with_file_mut(|file| file.read(buf));
98        let nread = res?;
99        self.position
100            .checked_add(nread as u64)
101            .ok_or_else(|| io::Error::other("seek position overflow"))?;
102        Ok(nread)
103    }
104}
105
106impl<R: Read + Seek> Seek for SeekableArchiveFile<'_, R> {
107    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
108        let target_pos = match pos {
109            io::SeekFrom::Start(offset) => offset,
110            io::SeekFrom::End(offset) => self
111                .file_size
112                .checked_add_signed(offset)
113                .ok_or_else(|| io::Error::other("seek position over or underflow"))?,
114            io::SeekFrom::Current(offset) => self
115                .position
116                .checked_add_signed(offset)
117                .ok_or_else(|| io::Error::other("seek position over or underflow"))?,
118        };
119
120        if target_pos < self.position {
121            let core = self.core.take();
122            let archive = core.unwrap().into_heads().archive;
123
124            self.core = Some(
125                SeekableArchiveCore::try_new(archive, |x| x.by_index(self.file_index), PhantomData)
126                    .map_err(|x| io::Error::other(format!("failed to reopen: {:?}", x)))?,
127            );
128        }
129        while self.position < target_pos {
130            const TMP_LEN: usize = 1024;
131            let mut tmp = [0_u8; TMP_LEN];
132            let cur_pos = self.position;
133            let nr = self
134                .read(&mut tmp[..std::cmp::min(TMP_LEN as u64, target_pos - cur_pos) as usize])?;
135            if nr == 0 {
136                return Err(io::Error::other("unexpected eof when seeking"));
137            }
138            self.position += nr as u64;
139        }
140
141        Ok(0)
142    }
143}
144
145impl<'a, R> OpenRasterDecoder<'a, R>
146where
147    R: Read + Seek + 'a,
148{
149    /// Create a new `OpenRasterDecoder` with the provided limits.
150    ///
151    /// (Limits need to be specified in advance, because determining the
152    /// minimum information needed for the ImageDecoder trait (image size and
153    /// color type) may require reading through and remembering image-dependent
154    /// amount of data.)
155    ///
156    /// Warning: While decoding limits apply to the header parsing and decoding
157    /// of the merged imaged component (a PNG file inside the ZIP archive that
158    /// forms an OpenRaster file), memory constraints on the ZIP file decoding
159    /// process have not yet been implemented; input ZIP files with very many
160    /// entries may require significant amounts of memory to read.
161    pub fn with_limits(r: R, limits: Limits) -> Result<OpenRasterDecoder<'a, R>, ImageError> {
162        let mut archive = ZipArchive::new(r)
163            .map_err(|e| ImageError::Decoding(DecodingError::new(openraster_format_hint(), e)))?;
164
165        /* Verify that this _is_ an OpenRaster file, and not some unrelated ZIP archive */
166        let mimetype_index = archive.index_for_name("mimetype").ok_or_else(|| {
167            ImageError::Decoding(DecodingError::new(
168                openraster_format_hint(),
169                "OpenRaster images should contain a mimetype subfile",
170            ))
171        })?;
172
173        let mut mimetype_file = archive
174            .by_index(mimetype_index)
175            .map_err(|x| ImageError::Decoding(DecodingError::new(openraster_format_hint(), x)))?;
176
177        const EXPECTED_MIMETYPE: &str = "image/openraster";
178        let mut tmp = [0u8; EXPECTED_MIMETYPE.len()];
179
180        mimetype_file.read_exact(&mut tmp)?;
181
182        if tmp != EXPECTED_MIMETYPE.as_bytes()
183            || mimetype_file.size() != EXPECTED_MIMETYPE.len() as u64
184        {
185            return Err(ImageError::Decoding(DecodingError::new(
186                openraster_format_hint(),
187                "Image did not have correct mimetype subentry to be identified as OpenRaster",
188            )));
189        }
190
191        drop(mimetype_file);
192
193        let mergedimage_index = archive.index_for_name("mergedimage.png").ok_or_else(|| {
194            ImageError::Decoding(DecodingError::new(
195                openraster_format_hint(),
196                "OpenRaster image missing mergedimage.png entry",
197            ))
198        })?;
199
200        let file = SeekableArchiveFile::new(archive, mergedimage_index)?;
201        let decoder =
202            PngDecoder::with_limits(BufReader::new(file), limits).map_err(set_ora_image_type)?;
203
204        Ok(OpenRasterDecoder {
205            mergedimg_decoder: decoder,
206        })
207    }
208}
209
210impl<'a, R: Read + Seek + 'a> ImageDecoder for OpenRasterDecoder<'a, R> {
211    fn dimensions(&self) -> (u32, u32) {
212        self.mergedimg_decoder.dimensions()
213    }
214
215    fn color_type(&self) -> ColorType {
216        self.mergedimg_decoder.color_type()
217    }
218
219    fn original_color_type(&self) -> ExtendedColorType {
220        self.mergedimg_decoder.original_color_type()
221    }
222
223    fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
224        // Warning: this does not account for any ZIP reading overhead
225        self.mergedimg_decoder.set_limits(limits)
226    }
227
228    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
229        self.mergedimg_decoder.icc_profile()
230    }
231
232    fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
233        self.mergedimg_decoder.exif_metadata()
234    }
235
236    fn orientation(&mut self) -> ImageResult<Orientation> {
237        self.mergedimg_decoder.orientation()
238    }
239
240    fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
241        self.mergedimg_decoder.read_image(buf)
242    }
243
244    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
245        (*self).read_image(buf)
246    }
247}