diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/image/src/io | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/image/src/io')
-rw-r--r-- | third_party/rust/image/src/io/free_functions.rs | 289 | ||||
-rw-r--r-- | third_party/rust/image/src/io/mod.rs | 5 | ||||
-rw-r--r-- | third_party/rust/image/src/io/reader.rs | 210 |
3 files changed, 504 insertions, 0 deletions
diff --git a/third_party/rust/image/src/io/free_functions.rs b/third_party/rust/image/src/io/free_functions.rs new file mode 100644 index 0000000000..227864d77e --- /dev/null +++ b/third_party/rust/image/src/io/free_functions.rs @@ -0,0 +1,289 @@ +use std::ffi::OsString; +use std::fs::File; +use std::io::{BufRead, BufReader, BufWriter, Seek}; +use std::path::Path; +use std::u32; + +#[cfg(feature = "bmp")] +use crate::bmp; +#[cfg(feature = "gif")] +use crate::gif; +#[cfg(feature = "hdr")] +use crate::hdr; +#[cfg(feature = "ico")] +use crate::ico; +#[cfg(feature = "jpeg")] +use crate::jpeg; +#[cfg(feature = "png")] +use crate::png; +#[cfg(feature = "pnm")] +use crate::pnm; +#[cfg(feature = "tga")] +use crate::tga; +#[cfg(feature = "dds")] +use crate::dds; +#[cfg(feature = "tiff")] +use crate::tiff; +#[cfg(feature = "webp")] +use crate::webp; + +use crate::color; +use crate::image; +use crate::dynimage::DynamicImage; +use crate::error::{ImageError, ImageFormatHint, ImageResult}; +use crate::image::{ImageDecoder, ImageEncoder, ImageFormat}; + +/// Internal error type for guessing format from path. +pub(crate) enum PathError { + /// The extension did not fit a supported format. + UnknownExtension(OsString), + /// Extension could not be converted to `str`. + NoExtension, +} + +pub(crate) fn open_impl(path: &Path) -> ImageResult<DynamicImage> { + let fin = match File::open(path) { + Ok(f) => f, + Err(err) => return Err(ImageError::IoError(err)), + }; + let fin = BufReader::new(fin); + + load(fin, ImageFormat::from_path(path)?) +} + +/// Create a new image from a Reader +/// +/// Try [`io::Reader`] for more advanced uses. +/// +/// [`io::Reader`]: io/struct.Reader.html +pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> { + #[allow(deprecated, unreachable_patterns)] + // Default is unreachable if all features are supported. + match format { + #[cfg(feature = "png")] + image::ImageFormat::Png => DynamicImage::from_decoder(png::PngDecoder::new(r)?), + #[cfg(feature = "gif")] + image::ImageFormat::Gif => DynamicImage::from_decoder(gif::GifDecoder::new(r)?), + #[cfg(feature = "jpeg")] + image::ImageFormat::Jpeg => DynamicImage::from_decoder(jpeg::JpegDecoder::new(r)?), + #[cfg(feature = "webp")] + image::ImageFormat::WebP => DynamicImage::from_decoder(webp::WebPDecoder::new(r)?), + #[cfg(feature = "tiff")] + image::ImageFormat::Tiff => DynamicImage::from_decoder(tiff::TiffDecoder::new(r)?), + #[cfg(feature = "tga")] + image::ImageFormat::Tga => DynamicImage::from_decoder(tga::TgaDecoder::new(r)?), + #[cfg(feature = "dds")] + image::ImageFormat::Dds => DynamicImage::from_decoder(dds::DdsDecoder::new(r)?), + #[cfg(feature = "bmp")] + image::ImageFormat::Bmp => DynamicImage::from_decoder(bmp::BmpDecoder::new(r)?), + #[cfg(feature = "ico")] + image::ImageFormat::Ico => DynamicImage::from_decoder(ico::IcoDecoder::new(r)?), + #[cfg(feature = "hdr")] + image::ImageFormat::Hdr => DynamicImage::from_decoder(hdr::HDRAdapter::new(BufReader::new(r))?), + #[cfg(feature = "pnm")] + image::ImageFormat::Pnm => DynamicImage::from_decoder(pnm::PnmDecoder::new(BufReader::new(r))?), + _ => Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())), + } +} + +pub(crate) fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> { + let format = image::ImageFormat::from_path(path)?; + + let fin = File::open(path)?; + let fin = BufReader::new(fin); + + image_dimensions_with_format_impl(fin, format) +} + +pub(crate) fn image_dimensions_with_format_impl<R: BufRead + Seek>(fin: R, format: ImageFormat) + -> ImageResult<(u32, u32)> +{ + #[allow(unreachable_patterns)] + // Default is unreachable if all features are supported. + Ok(match format { + #[cfg(feature = "jpeg")] + image::ImageFormat::Jpeg => jpeg::JpegDecoder::new(fin)?.dimensions(), + #[cfg(feature = "png")] + image::ImageFormat::Png => png::PngDecoder::new(fin)?.dimensions(), + #[cfg(feature = "gif")] + image::ImageFormat::Gif => gif::GifDecoder::new(fin)?.dimensions(), + #[cfg(feature = "webp")] + image::ImageFormat::WebP => webp::WebPDecoder::new(fin)?.dimensions(), + #[cfg(feature = "tiff")] + image::ImageFormat::Tiff => tiff::TiffDecoder::new(fin)?.dimensions(), + #[cfg(feature = "tga")] + image::ImageFormat::Tga => tga::TgaDecoder::new(fin)?.dimensions(), + #[cfg(feature = "dds")] + image::ImageFormat::Dds => dds::DdsDecoder::new(fin)?.dimensions(), + #[cfg(feature = "bmp")] + image::ImageFormat::Bmp => bmp::BmpDecoder::new(fin)?.dimensions(), + #[cfg(feature = "ico")] + image::ImageFormat::Ico => ico::IcoDecoder::new(fin)?.dimensions(), + #[cfg(feature = "hdr")] + image::ImageFormat::Hdr => hdr::HDRAdapter::new(fin)?.dimensions(), + #[cfg(feature = "pnm")] + image::ImageFormat::Pnm => { + pnm::PnmDecoder::new(fin)?.dimensions() + } + format => return Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())), + }) +} + +pub(crate) fn save_buffer_impl( + path: &Path, + buf: &[u8], + width: u32, + height: u32, + color: color::ColorType, +) -> ImageResult<()> { + let fout = &mut BufWriter::new(File::create(path)?); + let ext = path.extension() + .and_then(|s| s.to_str()) + .map_or("".to_string(), |s| s.to_ascii_lowercase()); + + match &*ext { + #[cfg(feature = "gif")] + "gif" => gif::Encoder::new(fout).encode(buf, width, height, color), + #[cfg(feature = "ico")] + "ico" => ico::ICOEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "jpeg")] + "jpg" | "jpeg" => jpeg::JPEGEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "png")] + "png" => png::PNGEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "pnm")] + "pbm" => pnm::PNMEncoder::new(fout) + .with_subtype(pnm::PNMSubtype::Bitmap(pnm::SampleEncoding::Binary)) + .write_image(buf, width, height, color), + #[cfg(feature = "pnm")] + "pgm" => pnm::PNMEncoder::new(fout) + .with_subtype(pnm::PNMSubtype::Graymap(pnm::SampleEncoding::Binary)) + .write_image(buf, width, height, color), + #[cfg(feature = "pnm")] + "ppm" => pnm::PNMEncoder::new(fout) + .with_subtype(pnm::PNMSubtype::Pixmap(pnm::SampleEncoding::Binary)) + .write_image(buf, width, height, color), + #[cfg(feature = "pnm")] + "pam" => pnm::PNMEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "bmp")] + "bmp" => bmp::BMPEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "tiff")] + "tif" | "tiff" => tiff::TiffEncoder::new(fout) + .write_image(buf, width, height, color), + _ => Err(ImageError::Unsupported(ImageFormatHint::from(path).into())), + } +} + +pub(crate) fn save_buffer_with_format_impl( + path: &Path, + buf: &[u8], + width: u32, + height: u32, + color: color::ColorType, + format: ImageFormat, +) -> ImageResult<()> { + let fout = &mut BufWriter::new(File::create(path)?); + + match format { + #[cfg(feature = "gif")] + image::ImageFormat::Gif => gif::Encoder::new(fout).encode(buf, width, height, color), + #[cfg(feature = "ico")] + image::ImageFormat::Ico => ico::ICOEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "jpeg")] + image::ImageFormat::Jpeg => jpeg::JPEGEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "png")] + image::ImageFormat::Png => png::PNGEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "bmp")] + image::ImageFormat::Bmp => bmp::BMPEncoder::new(fout).write_image(buf, width, height, color), + #[cfg(feature = "tiff")] + image::ImageFormat::Tiff => tiff::TiffEncoder::new(fout) + .write_image(buf, width, height, color), + format => return Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())), + } +} + +/// Guess format from a path. +/// +/// Returns `PathError::NoExtension` if the path has no extension or returns a +/// `PathError::UnknownExtension` containing the extension if it can not be convert to a `str`. +pub(crate) fn guess_format_from_path_impl(path: &Path) -> Result<ImageFormat, PathError> { + let exact_ext = path.extension(); + + let ext = exact_ext + .and_then(|s| s.to_str()) + .map(str::to_ascii_lowercase); + + let ext = ext.as_ref() + .map(String::as_str); + + Ok(match ext { + Some("jpg") | Some("jpeg") => image::ImageFormat::Jpeg, + Some("png") => image::ImageFormat::Png, + Some("gif") => image::ImageFormat::Gif, + Some("webp") => image::ImageFormat::WebP, + Some("tif") | Some("tiff") => image::ImageFormat::Tiff, + Some("tga") => image::ImageFormat::Tga, + Some("dds") => image::ImageFormat::Dds, + Some("bmp") => image::ImageFormat::Bmp, + Some("ico") => image::ImageFormat::Ico, + Some("hdr") => image::ImageFormat::Hdr, + Some("pbm") | Some("pam") | Some("ppm") | Some("pgm") => image::ImageFormat::Pnm, + // The original extension is used, instead of _format + _ => return match exact_ext { + None => Err(PathError::NoExtension), + Some(os) => Err(PathError::UnknownExtension(os.to_owned())), + }, + }) +} + +static MAGIC_BYTES: [(&'static [u8], ImageFormat); 18] = [ + (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), +]; + +/// 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 +} + +impl From<PathError> for ImageError { + fn from(path: PathError) -> Self { + let format_hint = match path { + PathError::NoExtension => ImageFormatHint::Unknown, + PathError::UnknownExtension(ext) => ImageFormatHint::PathExtension(ext.into()), + }; + ImageError::Unsupported(format_hint.into()) + } +} diff --git a/third_party/rust/image/src/io/mod.rs b/third_party/rust/image/src/io/mod.rs new file mode 100644 index 0000000000..e7964c7259 --- /dev/null +++ b/third_party/rust/image/src/io/mod.rs @@ -0,0 +1,5 @@ +//! Input and output of images. +mod reader; +pub(crate) mod free_functions; + +pub use self::reader::Reader; diff --git a/third_party/rust/image/src/io/reader.rs b/third_party/rust/image/src/io/reader.rs new file mode 100644 index 0000000000..4da41f458b --- /dev/null +++ b/third_party/rust/image/src/io/reader.rs @@ -0,0 +1,210 @@ +use std::fs::File; +use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; +use std::path::Path; + +use crate::dynimage::DynamicImage; +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. +/// +#[cfg_attr(feature = "pnm", doc = "```")] +#[cfg_attr(not(feature = "pnm"), doc = "```no_run")] +/// # 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)); +/// +/// 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. + inner: R, + /// The format, if one has been set or deduced. + format: Option<ImageFormat>, +} + +impl<R: Read> Reader<R> { + /// Create a new image reader without a preset format. + /// + /// It is possible to guess the format based on the content of the read object with + /// [`guess_format`], or to set the format directly with [`set_format`]. + /// + /// [`guess_format`]: #method.guess_format + /// [`set_format`]: method.set_format + pub fn new(reader: R) -> Self { + Reader { + inner: reader, + format: None, + } + } + + /// Construct a reader with specified format. + pub fn with_format(reader: R, format: ImageFormat) -> Self { + Reader { + inner: reader, + format: Some(format), + } + } + + /// 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::UnsupportedError` when the image format has not been set. + pub fn clear_format(&mut self) { + self.format = None; + } + + /// 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 [`guess_format`]. + /// + /// [`guess_format`]: #method.guess_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> { + let file = File::open(path)?; + Ok(Reader { + inner: BufReader::new(file), + format: ImageFormat::from_path(path).ok(), + }) + } +} + +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 not 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`] 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.seek(SeekFrom::Current(0))?; + 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::UnsupportedError`. + 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::UnsupportedError`. + pub fn decode(mut self) -> ImageResult<DynamicImage> { + let format = self.require_format()?; + free_functions::load(self.inner, format) + } + + fn require_format(&mut self) -> ImageResult<ImageFormat> { + self.format.ok_or_else(|| + ImageError::UnsupportedError("Unable to determine image format".into())) + } +} |