diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/io | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/io')
-rw-r--r-- | vendor/image/src/io/free_functions.rs | 312 | ||||
-rw-r--r-- | vendor/image/src/io/mod.rs | 166 | ||||
-rw-r--r-- | vendor/image/src/io/reader.rs | 239 |
3 files changed, 717 insertions, 0 deletions
diff --git a/vendor/image/src/io/free_functions.rs b/vendor/image/src/io/free_functions.rs new file mode 100644 index 0000000..d6047d7 --- /dev/null +++ b/vendor/image/src/io/free_functions.rs @@ -0,0 +1,312 @@ +use std::fs::File; +use std::io::{BufRead, BufReader, BufWriter, Seek}; +use std::path::Path; +use std::u32; + +use crate::codecs::*; + +use crate::dynimage::DynamicImage; +use crate::error::{ImageError, ImageFormatHint, ImageResult}; +use crate::image; +use crate::image::ImageFormat; +#[allow(unused_imports)] // When no features are supported +use crate::image::{ImageDecoder, ImageEncoder}; +use crate::{ + color, + error::{UnsupportedError, UnsupportedErrorKind}, + ImageOutputFormat, +}; + +pub(crate) fn open_impl(path: &Path) -> ImageResult<DynamicImage> { + let buffered_read = BufReader::new(File::open(path).map_err(ImageError::IoError)?); + + load(buffered_read, ImageFormat::from_path(path)?) +} + +/// Create a new image from a Reader. +/// +/// Assumes the reader is already buffered. For optimal performance, +/// consider wrapping the reader with a `BufReader::new()`. +/// +/// Try [`io::Reader`] for more advanced uses. +/// +/// [`io::Reader`]: io/struct.Reader.html +#[allow(unused_variables)] +// r is unused if no features are supported. +pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> { + load_inner(r, super::Limits::default(), format) +} + +pub(crate) trait DecoderVisitor { + type Result; + fn visit_decoder<'a, D: ImageDecoder<'a>>(self, decoder: D) -> ImageResult<Self::Result>; +} + +pub(crate) fn load_decoder<R: BufRead + Seek, V: DecoderVisitor>( + r: R, + format: ImageFormat, + limits: super::Limits, + visitor: V, +) -> ImageResult<V::Result> { + #[allow(unreachable_patterns)] + // Default is unreachable if all features are supported. + match format { + #[cfg(feature = "avif-decoder")] + image::ImageFormat::Avif => visitor.visit_decoder(avif::AvifDecoder::new(r)?), + #[cfg(feature = "png")] + image::ImageFormat::Png => visitor.visit_decoder(png::PngDecoder::with_limits(r, limits)?), + #[cfg(feature = "gif")] + image::ImageFormat::Gif => visitor.visit_decoder(gif::GifDecoder::new(r)?), + #[cfg(feature = "jpeg")] + image::ImageFormat::Jpeg => visitor.visit_decoder(jpeg::JpegDecoder::new(r)?), + #[cfg(feature = "webp")] + image::ImageFormat::WebP => visitor.visit_decoder(webp::WebPDecoder::new(r)?), + #[cfg(feature = "tiff")] + image::ImageFormat::Tiff => visitor.visit_decoder(tiff::TiffDecoder::new(r)?), + #[cfg(feature = "tga")] + image::ImageFormat::Tga => visitor.visit_decoder(tga::TgaDecoder::new(r)?), + #[cfg(feature = "dds")] + image::ImageFormat::Dds => visitor.visit_decoder(dds::DdsDecoder::new(r)?), + #[cfg(feature = "bmp")] + image::ImageFormat::Bmp => visitor.visit_decoder(bmp::BmpDecoder::new(r)?), + #[cfg(feature = "ico")] + image::ImageFormat::Ico => visitor.visit_decoder(ico::IcoDecoder::new(r)?), + #[cfg(feature = "hdr")] + image::ImageFormat::Hdr => visitor.visit_decoder(hdr::HdrAdapter::new(BufReader::new(r))?), + #[cfg(feature = "exr")] + image::ImageFormat::OpenExr => visitor.visit_decoder(openexr::OpenExrDecoder::new(r)?), + #[cfg(feature = "pnm")] + image::ImageFormat::Pnm => visitor.visit_decoder(pnm::PnmDecoder::new(r)?), + #[cfg(feature = "farbfeld")] + image::ImageFormat::Farbfeld => visitor.visit_decoder(farbfeld::FarbfeldDecoder::new(r)?), + #[cfg(feature = "qoi")] + image::ImageFormat::Qoi => visitor.visit_decoder(qoi::QoiDecoder::new(r)?), + _ => Err(ImageError::Unsupported( + ImageFormatHint::Exact(format).into(), + )), + } +} + +pub(crate) fn load_inner<R: BufRead + Seek>( + r: R, + limits: super::Limits, + format: ImageFormat, +) -> ImageResult<DynamicImage> { + struct LoadVisitor(super::Limits); + + impl DecoderVisitor for LoadVisitor { + type Result = DynamicImage; + + fn visit_decoder<'a, D: ImageDecoder<'a>>( + self, + mut decoder: D, + ) -> ImageResult<Self::Result> { + let mut limits = self.0; + // Check that we do not allocate a bigger buffer than we are allowed to + // FIXME: should this rather go in `DynamicImage::from_decoder` somehow? + limits.reserve(decoder.total_bytes())?; + decoder.set_limits(limits)?; + DynamicImage::from_decoder(decoder) + } + } + + load_decoder(r, format, limits.clone(), LoadVisitor(limits)) +} + +pub(crate) fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> { + let format = image::ImageFormat::from_path(path)?; + let reader = BufReader::new(File::open(path)?); + image_dimensions_with_format_impl(reader, format) +} + +#[allow(unused_variables)] +// fin is unused if no features are supported. +pub(crate) fn image_dimensions_with_format_impl<R: BufRead + Seek>( + buffered_read: R, + format: ImageFormat, +) -> ImageResult<(u32, u32)> { + struct DimVisitor; + + impl DecoderVisitor for DimVisitor { + type Result = (u32, u32); + fn visit_decoder<'a, D: ImageDecoder<'a>>(self, decoder: D) -> ImageResult<Self::Result> { + Ok(decoder.dimensions()) + } + } + + load_decoder(buffered_read, format, super::Limits::default(), DimVisitor) +} + +#[allow(unused_variables)] +// Most variables when no features are supported +pub(crate) fn save_buffer_impl( + path: &Path, + buf: &[u8], + width: u32, + height: u32, + color: color::ColorType, +) -> ImageResult<()> { + let format = ImageFormat::from_path(path)?; + save_buffer_with_format_impl(path, buf, width, height, color, format) +} + +#[allow(unused_variables)] +// Most variables when no features are supported +pub(crate) fn save_buffer_with_format_impl( + path: &Path, + buf: &[u8], + width: u32, + height: u32, + color: color::ColorType, + format: ImageFormat, +) -> ImageResult<()> { + let buffered_file_write = &mut BufWriter::new(File::create(path)?); // always seekable + + let format = match format { + #[cfg(feature = "pnm")] + image::ImageFormat::Pnm => { + let ext = path + .extension() + .and_then(|s| s.to_str()) + .map_or("".to_string(), |s| s.to_ascii_lowercase()); + ImageOutputFormat::Pnm(match &*ext { + "pbm" => pnm::PnmSubtype::Bitmap(pnm::SampleEncoding::Binary), + "pgm" => pnm::PnmSubtype::Graymap(pnm::SampleEncoding::Binary), + "ppm" => pnm::PnmSubtype::Pixmap(pnm::SampleEncoding::Binary), + "pam" => pnm::PnmSubtype::ArbitraryMap, + _ => { + return Err(ImageError::Unsupported( + ImageFormatHint::Exact(format).into(), + )) + } // Unsupported Pnm subtype. + }) + } + // #[cfg(feature = "hdr")] + // image::ImageFormat::Hdr => hdr::HdrEncoder::new(fout).encode(&[Rgb<f32>], width, height), // usize + format => format.into(), + }; + + write_buffer_impl(buffered_file_write, buf, width, height, color, format) +} + +#[allow(unused_variables)] +// Most variables when no features are supported +pub(crate) fn write_buffer_impl<W: std::io::Write + Seek>( + buffered_write: &mut W, + buf: &[u8], + width: u32, + height: u32, + color: color::ColorType, + format: ImageOutputFormat, +) -> ImageResult<()> { + match format { + #[cfg(feature = "png")] + ImageOutputFormat::Png => { + png::PngEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "jpeg")] + ImageOutputFormat::Jpeg(quality) => { + jpeg::JpegEncoder::new_with_quality(buffered_write, quality) + .write_image(buf, width, height, color) + } + #[cfg(feature = "pnm")] + ImageOutputFormat::Pnm(subtype) => pnm::PnmEncoder::new(buffered_write) + .with_subtype(subtype) + .write_image(buf, width, height, color), + #[cfg(feature = "gif")] + ImageOutputFormat::Gif => { + gif::GifEncoder::new(buffered_write).encode(buf, width, height, color) + } + #[cfg(feature = "ico")] + ImageOutputFormat::Ico => { + ico::IcoEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "bmp")] + ImageOutputFormat::Bmp => { + bmp::BmpEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "farbfeld")] + ImageOutputFormat::Farbfeld => { + farbfeld::FarbfeldEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "tga")] + ImageOutputFormat::Tga => { + tga::TgaEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "exr")] + ImageOutputFormat::OpenExr => { + openexr::OpenExrEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "tiff")] + ImageOutputFormat::Tiff => { + tiff::TiffEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "avif-encoder")] + ImageOutputFormat::Avif => { + avif::AvifEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "qoi")] + ImageOutputFormat::Qoi => { + qoi::QoiEncoder::new(buffered_write).write_image(buf, width, height, color) + } + #[cfg(feature = "webp-encoder")] + ImageOutputFormat::WebP => { + webp::WebPEncoder::new(buffered_write).write_image(buf, width, height, color) + } + + image::ImageOutputFormat::Unsupported(msg) => Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormatHint::Unknown, + UnsupportedErrorKind::Format(ImageFormatHint::Name(msg)), + ), + )), + } +} + +static MAGIC_BYTES: [(&[u8], ImageFormat); 23] = [ + (b"\x89PNG\r\n\x1a\n", ImageFormat::Png), + (&[0xff, 0xd8, 0xff], ImageFormat::Jpeg), + (b"GIF89a", ImageFormat::Gif), + (b"GIF87a", ImageFormat::Gif), + (b"RIFF", ImageFormat::WebP), // TODO: better magic byte detection, see https://github.com/image-rs/image/issues/660 + (b"MM\x00*", ImageFormat::Tiff), + (b"II*\x00", ImageFormat::Tiff), + (b"DDS ", ImageFormat::Dds), + (b"BM", ImageFormat::Bmp), + (&[0, 0, 1, 0], ImageFormat::Ico), + (b"#?RADIANCE", ImageFormat::Hdr), + (b"P1", ImageFormat::Pnm), + (b"P2", ImageFormat::Pnm), + (b"P3", ImageFormat::Pnm), + (b"P4", ImageFormat::Pnm), + (b"P5", ImageFormat::Pnm), + (b"P6", ImageFormat::Pnm), + (b"P7", ImageFormat::Pnm), + (b"farbfeld", ImageFormat::Farbfeld), + (b"\0\0\0 ftypavif", ImageFormat::Avif), + (b"\0\0\0\x1cftypavif", ImageFormat::Avif), + (&[0x76, 0x2f, 0x31, 0x01], ImageFormat::OpenExr), // = &exr::meta::magic_number::BYTES + (b"qoif", ImageFormat::Qoi), +]; + +/// Guess image format from memory block +/// +/// Makes an educated guess about the image format based on the Magic Bytes at the beginning. +/// TGA is not supported by this function. +/// This is not to be trusted on the validity of the whole memory block +pub fn guess_format(buffer: &[u8]) -> ImageResult<ImageFormat> { + match guess_format_impl(buffer) { + Some(format) => Ok(format), + None => Err(ImageError::Unsupported(ImageFormatHint::Unknown.into())), + } +} + +pub(crate) fn guess_format_impl(buffer: &[u8]) -> Option<ImageFormat> { + for &(signature, format) in &MAGIC_BYTES { + if buffer.starts_with(signature) { + return Some(format); + } + } + + None +} diff --git a/vendor/image/src/io/mod.rs b/vendor/image/src/io/mod.rs new file mode 100644 index 0000000..8fbc6e2 --- /dev/null +++ b/vendor/image/src/io/mod.rs @@ -0,0 +1,166 @@ +//! Input and output of images. + +use std::convert::TryFrom; + +use crate::{error, ImageError, ImageResult}; + +pub(crate) mod free_functions; +mod reader; + +pub use self::reader::Reader; + +/// Set of supported strict limits for a decoder. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[allow(missing_copy_implementations)] +#[allow(clippy::manual_non_exhaustive)] +pub struct LimitSupport { + _non_exhaustive: (), +} + +#[allow(clippy::derivable_impls)] +impl Default for LimitSupport { + fn default() -> LimitSupport { + LimitSupport { + _non_exhaustive: (), + } + } +} + +/// Resource limits for decoding. +/// +/// Limits can be either *strict* or *non-strict*. Non-strict limits are best-effort +/// limits where the library does not guarantee that limit will not be exceeded. Do note +/// that it is still considered a bug if a non-strict limit is exceeded, however as +/// some of the underlying decoders do not support not support such limits one cannot +/// rely on these limits being supported. For stric limits the library makes a stronger +/// guarantee that the limit will not be exceeded. Exceeding a strict limit is considered +/// a critical bug. If a decoder cannot guarantee that it will uphold a strict limit it +/// *must* fail with `image::error::LimitErrorKind::Unsupported`. +/// +/// Currently the only strict limits supported are the `max_image_width` and `max_image_height` +/// limits, however more will be added in the future. [`LimitSupport`] will default to support +/// being false and decoders should enable support for the limits they support in +/// [`ImageDecoder::set_limits`]. +/// +/// The limit check should only ever fail if a limit will be exceeded or an unsupported strict +/// limit is used. +/// +/// [`LimitSupport`]: ./struct.LimitSupport.html +/// [`ImageDecoder::set_limits`]: ../trait.ImageDecoder.html#method.set_limits +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[allow(missing_copy_implementations)] +#[allow(clippy::manual_non_exhaustive)] +pub struct Limits { + /// The maximum allowed image width. This limit is strict. The default is no limit. + pub max_image_width: Option<u32>, + /// The maximum allowed image height. This limit is strict. The default is no limit. + pub max_image_height: Option<u32>, + /// The maximum allowed sum of allocations allocated by the decoder at any one time excluding + /// allocator overhead. This limit is non-strict by default and some decoders may ignore it. + /// The default is 512MiB. + pub max_alloc: Option<u64>, + _non_exhaustive: (), +} + +impl Default for Limits { + fn default() -> Limits { + Limits { + max_image_width: None, + max_image_height: None, + max_alloc: Some(512 * 1024 * 1024), + _non_exhaustive: (), + } + } +} + +impl Limits { + /// Disable all limits. + pub fn no_limits() -> Limits { + Limits { + max_image_width: None, + max_image_height: None, + max_alloc: None, + _non_exhaustive: (), + } + } + + /// This function checks that all currently set strict limits are supported. + pub fn check_support(&self, _supported: &LimitSupport) -> ImageResult<()> { + Ok(()) + } + + /// This function checks the `max_image_width` and `max_image_height` limits given + /// the image width and height. + pub fn check_dimensions(&self, width: u32, height: u32) -> ImageResult<()> { + if let Some(max_width) = self.max_image_width { + if width > max_width { + return Err(ImageError::Limits(error::LimitError::from_kind( + error::LimitErrorKind::DimensionError, + ))); + } + } + + if let Some(max_height) = self.max_image_height { + if height > max_height { + return Err(ImageError::Limits(error::LimitError::from_kind( + error::LimitErrorKind::DimensionError, + ))); + } + } + + Ok(()) + } + + /// This function checks that the current limit allows for reserving the set amount + /// of bytes, it then reduces the limit accordingly. + pub fn reserve(&mut self, amount: u64) -> ImageResult<()> { + if let Some(max_alloc) = self.max_alloc.as_mut() { + if *max_alloc < amount { + return Err(ImageError::Limits(error::LimitError::from_kind( + error::LimitErrorKind::InsufficientMemory, + ))); + } + + *max_alloc -= amount; + } + + Ok(()) + } + + /// This function acts identically to [`reserve`], but takes a `usize` for convenience. + pub fn reserve_usize(&mut self, amount: usize) -> ImageResult<()> { + match u64::try_from(amount) { + Ok(n) => self.reserve(n), + Err(_) if self.max_alloc.is_some() => Err(ImageError::Limits( + error::LimitError::from_kind(error::LimitErrorKind::InsufficientMemory), + )), + Err(_) => { + // Out of bounds, but we weren't asked to consider any limit. + Ok(()) + } + } + } + + /// This function increases the `max_alloc` limit with amount. Should only be used + /// together with [`reserve`]. + /// + /// [`reserve`]: #method.reserve + pub fn free(&mut self, amount: u64) { + if let Some(max_alloc) = self.max_alloc.as_mut() { + *max_alloc = max_alloc.saturating_add(amount); + } + } + + /// This function acts identically to [`free`], but takes a `usize` for convenience. + pub fn free_usize(&mut self, amount: usize) { + match u64::try_from(amount) { + Ok(n) => self.free(n), + Err(_) if self.max_alloc.is_some() => { + panic!("max_alloc is set, we should have exited earlier when the reserve failed"); + } + Err(_) => { + // Out of bounds, but we weren't asked to consider any limit. + } + } + } +} diff --git a/vendor/image/src/io/reader.rs b/vendor/image/src/io/reader.rs new file mode 100644 index 0000000..660780f --- /dev/null +++ b/vendor/image/src/io/reader.rs @@ -0,0 +1,239 @@ +use std::fs::File; +use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; +use std::path::Path; + +use crate::dynimage::DynamicImage; +use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; +use crate::image::ImageFormat; +use crate::{ImageError, ImageResult}; + +use super::free_functions; + +/// A multi-format image reader. +/// +/// Wraps an input reader to facilitate automatic detection of an image's format, appropriate +/// decoding method, and dispatches into the set of supported [`ImageDecoder`] implementations. +/// +/// ## Usage +/// +/// Opening a file, deducing the format based on the file path automatically, and trying to decode +/// the image contained can be performed by constructing the reader and immediately consuming it. +/// +/// ```no_run +/// # use image::ImageError; +/// # use image::io::Reader; +/// # fn main() -> Result<(), ImageError> { +/// let image = Reader::open("path/to/image.png")? +/// .decode()?; +/// # Ok(()) } +/// ``` +/// +/// It is also possible to make a guess based on the content. This is especially handy if the +/// source is some blob in memory and you have constructed the reader in another way. Here is an +/// example with a `pnm` black-and-white subformat that encodes its pixel matrix with ascii values. +/// +/// ``` +/// # use image::ImageError; +/// # use image::io::Reader; +/// # fn main() -> Result<(), ImageError> { +/// use std::io::Cursor; +/// use image::ImageFormat; +/// +/// let raw_data = b"P1 2 2\n\ +/// 0 1\n\ +/// 1 0\n"; +/// +/// let mut reader = Reader::new(Cursor::new(raw_data)) +/// .with_guessed_format() +/// .expect("Cursor io never fails"); +/// assert_eq!(reader.format(), Some(ImageFormat::Pnm)); +/// +/// # #[cfg(feature = "pnm")] +/// let image = reader.decode()?; +/// # Ok(()) } +/// ``` +/// +/// As a final fallback or if only a specific format must be used, the reader always allows manual +/// specification of the supposed image format with [`set_format`]. +/// +/// [`set_format`]: #method.set_format +/// [`ImageDecoder`]: ../trait.ImageDecoder.html +pub struct Reader<R: Read> { + /// The reader. Should be buffered. + inner: R, + /// The format, if one has been set or deduced. + format: Option<ImageFormat>, + /// Decoding limits + limits: super::Limits, +} + +impl<R: Read> Reader<R> { + /// Create a new image reader without a preset format. + /// + /// Assumes the reader is already buffered. For optimal performance, + /// consider wrapping the reader with a `BufReader::new()`. + /// + /// It is possible to guess the format based on the content of the read object with + /// [`with_guessed_format`], or to set the format directly with [`set_format`]. + /// + /// [`with_guessed_format`]: #method.with_guessed_format + /// [`set_format`]: method.set_format + pub fn new(buffered_reader: R) -> Self { + Reader { + inner: buffered_reader, + format: None, + limits: super::Limits::default(), + } + } + + /// Construct a reader with specified format. + /// + /// Assumes the reader is already buffered. For optimal performance, + /// consider wrapping the reader with a `BufReader::new()`. + pub fn with_format(buffered_reader: R, format: ImageFormat) -> Self { + Reader { + inner: buffered_reader, + format: Some(format), + limits: super::Limits::default(), + } + } + + /// Get the currently determined format. + pub fn format(&self) -> Option<ImageFormat> { + self.format + } + + /// Supply the format as which to interpret the read image. + pub fn set_format(&mut self, format: ImageFormat) { + self.format = Some(format); + } + + /// Remove the current information on the image format. + /// + /// Note that many operations require format information to be present and will return e.g. an + /// `ImageError::Unsupported` when the image format has not been set. + pub fn clear_format(&mut self) { + self.format = None; + } + + /// Disable all decoding limits. + pub fn no_limits(&mut self) { + self.limits = super::Limits::no_limits(); + } + + /// Set a custom set of decoding limits. + pub fn limits(&mut self, limits: super::Limits) { + self.limits = limits; + } + + /// Unwrap the reader. + pub fn into_inner(self) -> R { + self.inner + } +} + +impl Reader<BufReader<File>> { + /// Open a file to read, format will be guessed from path. + /// + /// This will not attempt any io operation on the opened file. + /// + /// If you want to inspect the content for a better guess on the format, which does not depend + /// on file extensions, follow this call with a call to [`with_guessed_format`]. + /// + /// [`with_guessed_format`]: #method.with_guessed_format + pub fn open<P>(path: P) -> io::Result<Self> + where + P: AsRef<Path>, + { + Self::open_impl(path.as_ref()) + } + + fn open_impl(path: &Path) -> io::Result<Self> { + Ok(Reader { + inner: BufReader::new(File::open(path)?), + format: ImageFormat::from_path(path).ok(), + limits: super::Limits::default(), + }) + } +} + +impl<R: BufRead + Seek> Reader<R> { + /// Make a format guess based on the content, replacing it on success. + /// + /// Returns `Ok` with the guess if no io error occurs. Additionally, replaces the current + /// format if the guess was successful. If the guess was unable to determine a format then + /// the current format of the reader is unchanged. + /// + /// Returns an error if the underlying reader fails. The format is unchanged. The error is a + /// `std::io::Error` and not `ImageError` since the only error case is an error when the + /// underlying reader seeks. + /// + /// When an error occurs, the reader may not have been properly reset and it is potentially + /// hazardous to continue with more io. + /// + /// ## Usage + /// + /// This supplements the path based type deduction from [`open`](Reader::open) with content based deduction. + /// This is more common in Linux and UNIX operating systems and also helpful if the path can + /// not be directly controlled. + /// + /// ```no_run + /// # use image::ImageError; + /// # use image::io::Reader; + /// # fn main() -> Result<(), ImageError> { + /// let image = Reader::open("image.unknown")? + /// .with_guessed_format()? + /// .decode()?; + /// # Ok(()) } + /// ``` + pub fn with_guessed_format(mut self) -> io::Result<Self> { + let format = self.guess_format()?; + // Replace format if found, keep current state if not. + self.format = format.or(self.format); + Ok(self) + } + + fn guess_format(&mut self) -> io::Result<Option<ImageFormat>> { + let mut start = [0; 16]; + + // Save current offset, read start, restore offset. + let cur = self.inner.stream_position()?; + let len = io::copy( + // Accept shorter files but read at most 16 bytes. + &mut self.inner.by_ref().take(16), + &mut Cursor::new(&mut start[..]), + )?; + self.inner.seek(SeekFrom::Start(cur))?; + + Ok(free_functions::guess_format_impl(&start[..len as usize])) + } + + /// Read the image dimensions. + /// + /// Uses the current format to construct the correct reader for the format. + /// + /// If no format was determined, returns an `ImageError::Unsupported`. + pub fn into_dimensions(mut self) -> ImageResult<(u32, u32)> { + let format = self.require_format()?; + free_functions::image_dimensions_with_format_impl(self.inner, format) + } + + /// Read the image (replaces `load`). + /// + /// Uses the current format to construct the correct reader for the format. + /// + /// If no format was determined, returns an `ImageError::Unsupported`. + pub fn decode(mut self) -> ImageResult<DynamicImage> { + let format = self.require_format()?; + free_functions::load_inner(self.inner, self.limits, format) + } + + fn require_format(&mut self) -> ImageResult<ImageFormat> { + self.format.ok_or_else(|| { + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormatHint::Unknown, + UnsupportedErrorKind::Format(ImageFormatHint::Unknown), + )) + }) + } +} |