image/io/
image_reader_type.rs

1use std::ffi::OsString;
2use std::fs::File;
3use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom};
4use std::iter;
5use std::path::Path;
6
7use crate::error::{ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind};
8use crate::hooks::{GenericReader, DECODING_HOOKS, GUESS_FORMAT_HOOKS};
9use crate::io::limits::Limits;
10use crate::{DynamicImage, ImageDecoder, ImageError, ImageFormat};
11
12use super::free_functions;
13
14#[derive(Clone)]
15enum Format {
16    BuiltIn(ImageFormat),
17    Extension(OsString),
18}
19
20/// A multi-format image reader.
21///
22/// Wraps an input reader to facilitate automatic detection of an image's format, appropriate
23/// decoding method, and dispatches into the set of supported [`ImageDecoder`] implementations.
24///
25/// ## Usage
26///
27/// Opening a file, deducing the format based on the file path automatically, and trying to decode
28/// the image contained can be performed by constructing the reader and immediately consuming it.
29///
30/// ```no_run
31/// # use image::ImageError;
32/// # use image::ImageReader;
33/// # fn main() -> Result<(), ImageError> {
34/// let image = ImageReader::open("path/to/image.png")?
35///     .decode()?;
36/// # Ok(()) }
37/// ```
38///
39/// It is also possible to make a guess based on the content. This is especially handy if the
40/// source is some blob in memory and you have constructed the reader in another way. Here is an
41/// example with a `pnm` black-and-white subformat that encodes its pixel matrix with ascii values.
42///
43/// ```
44/// # use image::ImageError;
45/// # use image::ImageReader;
46/// # fn main() -> Result<(), ImageError> {
47/// use std::io::Cursor;
48/// use image::ImageFormat;
49///
50/// let raw_data = b"P1 2 2\n\
51///     0 1\n\
52///     1 0\n";
53///
54/// let mut reader = ImageReader::new(Cursor::new(raw_data))
55///     .with_guessed_format()
56///     .expect("Cursor io never fails");
57/// assert_eq!(reader.format(), Some(ImageFormat::Pnm));
58///
59/// # #[cfg(feature = "pnm")]
60/// let image = reader.decode()?;
61/// # Ok(()) }
62/// ```
63///
64/// As a final fallback or if only a specific format must be used, the reader always allows manual
65/// specification of the supposed image format with [`set_format`].
66///
67/// [`set_format`]: #method.set_format
68/// [`ImageDecoder`]: ../trait.ImageDecoder.html
69pub struct ImageReader<R: Read + Seek> {
70    /// The reader. Should be buffered.
71    inner: R,
72    /// The format, if one has been set or deduced.
73    format: Option<Format>,
74    /// Decoding limits
75    limits: Limits,
76}
77
78impl<'a, R: 'a + BufRead + Seek> ImageReader<R> {
79    /// Create a new image reader without a preset format.
80    ///
81    /// Assumes the reader is already buffered. For optimal performance,
82    /// consider wrapping the reader with a `BufReader::new()`.
83    ///
84    /// It is possible to guess the format based on the content of the read object with
85    /// [`with_guessed_format`], or to set the format directly with [`set_format`].
86    ///
87    /// [`with_guessed_format`]: #method.with_guessed_format
88    /// [`set_format`]: method.set_format
89    pub fn new(buffered_reader: R) -> Self {
90        ImageReader {
91            inner: buffered_reader,
92            format: None,
93            limits: Limits::default(),
94        }
95    }
96
97    /// Construct a reader with specified format.
98    ///
99    /// Assumes the reader is already buffered. For optimal performance,
100    /// consider wrapping the reader with a `BufReader::new()`.
101    pub fn with_format(buffered_reader: R, format: ImageFormat) -> Self {
102        ImageReader {
103            inner: buffered_reader,
104            format: Some(Format::BuiltIn(format)),
105            limits: Limits::default(),
106        }
107    }
108
109    /// Get the currently determined format.
110    pub fn format(&self) -> Option<ImageFormat> {
111        match self.format {
112            Some(Format::BuiltIn(ref format)) => Some(*format),
113            Some(Format::Extension(ref ext)) => ImageFormat::from_extension(ext),
114            None => None,
115        }
116    }
117
118    /// Supply the format as which to interpret the read image.
119    pub fn set_format(&mut self, format: ImageFormat) {
120        self.format = Some(Format::BuiltIn(format));
121    }
122
123    /// Remove the current information on the image format.
124    ///
125    /// Note that many operations require format information to be present and will return e.g. an
126    /// `ImageError::Unsupported` when the image format has not been set.
127    pub fn clear_format(&mut self) {
128        self.format = None;
129    }
130
131    /// Disable all decoding limits.
132    pub fn no_limits(&mut self) {
133        self.limits = Limits::no_limits();
134    }
135
136    /// Set a custom set of decoding limits.
137    pub fn limits(&mut self, limits: Limits) {
138        self.limits = limits;
139    }
140
141    /// Unwrap the reader.
142    pub fn into_inner(self) -> R {
143        self.inner
144    }
145
146    /// Makes a decoder.
147    ///
148    /// For all formats except PNG, the limits are ignored and can be set with
149    /// `ImageDecoder::set_limits` after calling this function. PNG is handled specially because that
150    /// decoder has a different API which does not allow setting limits after construction.
151    fn make_decoder(
152        format: Format,
153        reader: R,
154        limits_for_png: Limits,
155    ) -> ImageResult<Box<dyn ImageDecoder + 'a>> {
156        #[allow(unused)]
157        use crate::codecs::*;
158
159        let format = match format {
160            Format::BuiltIn(format) => format,
161            Format::Extension(ext) => {
162                {
163                    let hooks = DECODING_HOOKS.read().unwrap();
164                    if let Some(hooks) = hooks.as_ref() {
165                        if let Some(hook) = hooks.get(&ext) {
166                            return hook(GenericReader(BufReader::new(Box::new(reader))));
167                        }
168                    }
169                }
170
171                ImageFormat::from_extension(&ext).ok_or(ImageError::Unsupported(
172                    ImageFormatHint::PathExtension(ext.into()).into(),
173                ))?
174            }
175        };
176
177        #[allow(unreachable_patterns)]
178        // Default is unreachable if all features are supported.
179        Ok(match format {
180            #[cfg(feature = "avif-native")]
181            ImageFormat::Avif => Box::new(avif::AvifDecoder::new(reader)?),
182            #[cfg(feature = "png")]
183            ImageFormat::Png => Box::new(png::PngDecoder::with_limits(reader, limits_for_png)?),
184            #[cfg(feature = "gif")]
185            ImageFormat::Gif => Box::new(gif::GifDecoder::new(reader)?),
186            #[cfg(feature = "jpeg")]
187            ImageFormat::Jpeg => Box::new(jpeg::JpegDecoder::new(reader)?),
188            #[cfg(feature = "webp")]
189            ImageFormat::WebP => Box::new(webp::WebPDecoder::new(reader)?),
190            #[cfg(feature = "tiff")]
191            ImageFormat::Tiff => Box::new(tiff::TiffDecoder::new(reader)?),
192            #[cfg(feature = "tga")]
193            ImageFormat::Tga => Box::new(tga::TgaDecoder::new(reader)?),
194            #[cfg(feature = "dds")]
195            ImageFormat::Dds => Box::new(dds::DdsDecoder::new(reader)?),
196            #[cfg(feature = "bmp")]
197            ImageFormat::Bmp => Box::new(bmp::BmpDecoder::new(reader)?),
198            #[cfg(feature = "ico")]
199            ImageFormat::Ico => Box::new(ico::IcoDecoder::new(reader)?),
200            #[cfg(feature = "hdr")]
201            ImageFormat::Hdr => Box::new(hdr::HdrDecoder::new(reader)?),
202            #[cfg(feature = "exr")]
203            ImageFormat::OpenExr => Box::new(openexr::OpenExrDecoder::new(reader)?),
204            #[cfg(feature = "pnm")]
205            ImageFormat::Pnm => Box::new(pnm::PnmDecoder::new(reader)?),
206            #[cfg(feature = "ff")]
207            ImageFormat::Farbfeld => Box::new(farbfeld::FarbfeldDecoder::new(reader)?),
208            #[cfg(feature = "qoi")]
209            ImageFormat::Qoi => Box::new(qoi::QoiDecoder::new(reader)?),
210            #[cfg(feature = "pcx")]
211            ImageFormat::Pcx => Box::new(pcx::PCXDecoder::new(reader)?),
212            format => {
213                return Err(ImageError::Unsupported(
214                    ImageFormatHint::Exact(format).into(),
215                ));
216            }
217        })
218    }
219
220    /// Convert the reader into a decoder.
221    pub fn into_decoder(mut self) -> ImageResult<impl ImageDecoder + 'a> {
222        let mut decoder =
223            Self::make_decoder(self.require_format()?, self.inner, self.limits.clone())?;
224        decoder.set_limits(self.limits)?;
225        Ok(decoder)
226    }
227
228    /// Make a format guess based on the content, replacing it on success.
229    ///
230    /// Returns `Ok` with the guess if no io error occurs. Additionally, replaces the current
231    /// format if the guess was successful. If the guess was unable to determine a format then
232    /// the current format of the reader is unchanged.
233    ///
234    /// Returns an error if the underlying reader fails. The format is unchanged. The error is a
235    /// `std::io::Error` and not `ImageError` since the only error case is an error when the
236    /// underlying reader seeks.
237    ///
238    /// When an error occurs, the reader may not have been properly reset and it is potentially
239    /// hazardous to continue with more io.
240    ///
241    /// ## Usage
242    ///
243    /// This supplements the path based type deduction from [`ImageReader::open()`] with content based deduction.
244    /// This is more common in Linux and UNIX operating systems and also helpful if the path can
245    /// not be directly controlled.
246    ///
247    /// ```no_run
248    /// # use image::ImageError;
249    /// # use image::ImageReader;
250    /// # fn main() -> Result<(), ImageError> {
251    /// let image = ImageReader::open("image.unknown")?
252    ///     .with_guessed_format()?
253    ///     .decode()?;
254    /// # Ok(()) }
255    /// ```
256    pub fn with_guessed_format(mut self) -> io::Result<Self> {
257        let format = self.guess_format()?;
258        // Replace format if found, keep current state if not.
259        self.format = format.or(self.format);
260        Ok(self)
261    }
262
263    fn guess_format(&mut self) -> io::Result<Option<Format>> {
264        let mut start = [0; 16];
265
266        // Save current offset, read start, restore offset.
267        let cur = self.inner.stream_position()?;
268        let len = io::copy(
269            // Accept shorter files but read at most 16 bytes.
270            &mut self.inner.by_ref().take(16),
271            &mut Cursor::new(&mut start[..]),
272        )?;
273        self.inner.seek(SeekFrom::Start(cur))?;
274
275        let hooks = GUESS_FORMAT_HOOKS.read().unwrap();
276        for &(signature, mask, ref extension) in &*hooks {
277            if mask.is_empty() {
278                if start.starts_with(signature) {
279                    return Ok(Some(Format::Extension(extension.clone())));
280                }
281            } else if start.len() >= signature.len()
282                && start
283                    .iter()
284                    .zip(signature.iter())
285                    .zip(mask.iter().chain(iter::repeat(&0xFF)))
286                    .all(|((&byte, &sig), &mask)| byte & mask == sig)
287            {
288                return Ok(Some(Format::Extension(extension.clone())));
289            }
290        }
291
292        if let Some(format) = free_functions::guess_format_impl(&start[..len as usize]) {
293            return Ok(Some(Format::BuiltIn(format)));
294        }
295
296        Ok(None)
297    }
298
299    /// Read the image dimensions.
300    ///
301    /// Uses the current format to construct the correct reader for the format.
302    ///
303    /// If no format was determined, returns an `ImageError::Unsupported`.
304    pub fn into_dimensions(self) -> ImageResult<(u32, u32)> {
305        self.into_decoder().map(|d| d.dimensions())
306    }
307
308    /// Read the image (replaces `load`).
309    ///
310    /// Uses the current format to construct the correct reader for the format.
311    ///
312    /// If no format was determined, returns an `ImageError::Unsupported`.
313    pub fn decode(mut self) -> ImageResult<DynamicImage> {
314        let format = self.require_format()?;
315
316        let mut limits = self.limits;
317        let mut decoder = Self::make_decoder(format, self.inner, limits.clone())?;
318
319        // Check that we do not allocate a bigger buffer than we are allowed to
320        // FIXME: should this rather go in `DynamicImage::from_decoder` somehow?
321        limits.reserve(decoder.total_bytes())?;
322        decoder.set_limits(limits)?;
323
324        DynamicImage::from_decoder(decoder)
325    }
326
327    fn require_format(&mut self) -> ImageResult<Format> {
328        self.format.clone().ok_or_else(|| {
329            ImageError::Unsupported(UnsupportedError::from_format_and_kind(
330                ImageFormatHint::Unknown,
331                UnsupportedErrorKind::Format(ImageFormatHint::Unknown),
332            ))
333        })
334    }
335}
336
337impl ImageReader<BufReader<File>> {
338    /// Open a file to read, format will be guessed from path.
339    ///
340    /// This will not attempt any io operation on the opened file.
341    ///
342    /// If you want to inspect the content for a better guess on the format, which does not depend
343    /// on file extensions, follow this call with a call to [`with_guessed_format`].
344    ///
345    /// [`with_guessed_format`]: #method.with_guessed_format
346    pub fn open<P>(path: P) -> io::Result<Self>
347    where
348        P: AsRef<Path>,
349    {
350        Self::open_impl(path.as_ref())
351    }
352
353    fn open_impl(path: &Path) -> io::Result<Self> {
354        let format = path
355            .extension()
356            .filter(|ext| !ext.is_empty())
357            .map(|ext| Format::Extension(ext.to_owned()));
358
359        Ok(ImageReader {
360            inner: BufReader::new(File::open(path)?),
361            format,
362            limits: Limits::default(),
363        })
364    }
365}