Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
312
third-party/vendor/image/src/io/free_functions.rs
vendored
Normal file
312
third-party/vendor/image/src/io/free_functions.rs
vendored
Normal file
|
|
@ -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")]
|
||||
ImageOutputFormat::WebP => {
|
||||
webp::WebPEncoder::new_lossless(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
|
||||
}
|
||||
180
third-party/vendor/image/src/io/mod.rs
vendored
Normal file
180
third-party/vendor/image/src/io/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
//! Input and output of images.
|
||||
|
||||
use crate::{error, ColorType, 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 acts identically to [`reserve`], but accepts the width, height and color type
|
||||
/// used to create an [`ImageBuffer`] and does all the math for you.
|
||||
pub fn reserve_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
color_type: ColorType,
|
||||
) -> ImageResult<()> {
|
||||
self.check_dimensions(width, height)?;
|
||||
let in_memory_size = (width as u64)
|
||||
.saturating_mul(height as u64)
|
||||
.saturating_mul(color_type.bytes_per_pixel().into());
|
||||
self.reserve(in_memory_size)?;
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
239
third-party/vendor/image/src/io/reader.rs
vendored
Normal file
239
third-party/vendor/image/src/io/reader.rs
vendored
Normal file
|
|
@ -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),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue