From 99bc1291dd7d1faf8451431496ec286348882de8 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Tue, 13 Dec 2022 17:35:26 +0100 Subject: [PATCH 01/16] WIP no_std --- src/animation.rs | 10 +++++++--- src/buffer.rs | 12 ++++++++---- src/color.rs | 2 +- src/dynimage.rs | 9 ++++++++- src/error.rs | 30 ++++++++++++++++++++++++++---- src/flat.rs | 10 +++++++--- src/image.rs | 14 +++++++++----- src/imageops/affine.rs | 13 ++++++++----- src/imageops/colorops.rs | 4 ++-- src/imageops/mod.rs | 2 +- src/imageops/sample.rs | 5 +++-- src/io/free_functions.rs | 5 ++++- src/io/mod.rs | 5 ++++- src/io/reader.rs | 1 + src/lib.rs | 8 ++++++++ src/math/utils.rs | 2 +- src/traits.rs | 2 +- src/utils/mod.rs | 5 +++-- 18 files changed, 102 insertions(+), 37 deletions(-) diff --git a/src/animation.rs b/src/animation.rs index aad57b4a5d..10ae375d04 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -1,5 +1,9 @@ -use std::iter::Iterator; -use std::time::Duration; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::cmp::Ordering::Equal; +use core::cmp::Ordering::Less; +use core::iter::Iterator; +use core::time::Duration; use num_rational::Ratio; @@ -179,7 +183,7 @@ impl Delay { /// Note that `denom_bound` bounds nominator and denominator of all intermediate /// approximations and the end result. fn closest_bounded_fraction(denom_bound: u32, nom: u32, denom: u32) -> (u32, u32) { - use std::cmp::Ordering::{self, *}; + use core::cmp::Ordering::{self, *}; assert!(0 < denom); assert!(0 < denom_bound); assert!(nom < denom); diff --git a/src/buffer.rs b/src/buffer.rs index 7f79119e4f..e1e2ebf4f1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,12 +1,14 @@ //! Contains the generic `ImageBuffer` struct. +use alloc::vec::Vec; +use core::fmt; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut, Index, IndexMut, Range}; +use core::slice::{ChunksExact, ChunksExactMut}; use num_traits::Zero; -use std::fmt; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut, Index, IndexMut, Range}; use std::path::Path; -use std::slice::{ChunksExact, ChunksExactMut}; use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; +#[cfg(feature = "std")] use crate::dynimage::{save_buffer, save_buffer_with_format, write_buffer_with_format}; use crate::error::ImageResult; use crate::flat::{FlatSamples, SampleLayout}; @@ -1031,6 +1033,7 @@ where } } +#[cfg(feature = "std")] impl ImageBuffer where P: Pixel, @@ -1407,6 +1410,7 @@ mod test { use crate::math::Rect; use crate::GenericImage as _; use crate::{color, Rgb}; + use alloc::vec::Vec; #[test] /// Tests if image buffers from slices work diff --git a/src/color.rs b/src/color.rs index 57a8511f2c..b248c2c47d 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,4 +1,4 @@ -use std::ops::{Index, IndexMut}; +use core::ops::{Index, IndexMut}; use num_traits::{NumCast, ToPrimitive, Zero}; diff --git a/src/dynimage.rs b/src/dynimage.rs index 3e0aeec6b9..42c6dc0f59 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1,7 +1,9 @@ +use alloc::vec::Vec; + +use core::u32; use std::io; use std::io::{Seek, Write}; use std::path::Path; -use std::u32; #[cfg(feature = "gif")] use crate::codecs::gif; @@ -1133,6 +1135,7 @@ where /// /// This will lead to corrupted files if the buffer contains malformed data. Currently only /// jpeg, png, ico, pnm, bmp, exr and tiff files are supported. +#[cfg(feature = "std")] pub fn save_buffer

( path: P, buf: &[u8], @@ -1155,6 +1158,7 @@ where /// This will lead to corrupted files if the buffer contains /// malformed data. Currently only jpeg, png, ico, bmp, exr and /// tiff files are supported. +#[cfg(feature = "std")] pub fn save_buffer_with_format

( path: P, buf: &[u8], @@ -1182,6 +1186,7 @@ where /// /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. +#[cfg(feature = "std")] pub fn write_buffer_with_format( buffered_writer: &mut W, buf: &[u8], @@ -1242,6 +1247,8 @@ mod bench { #[cfg(test)] mod test { + use alloc::vec::Vec; + #[test] fn test_empty_file() { assert!(super::load_from_memory(b"").is_err()); diff --git a/src/error.rs b/src/error.rs index 07ee275f21..275c591b05 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,8 +13,13 @@ //! //! [`ImageError`]: enum.ImageError.html +use alloc::boxed::Box; +use alloc::string::String; +use core::fmt; +#[cfg(feature = "std")] use std::error::Error; -use std::{fmt, io}; +#[cfg(feature = "std")] +use std::io; use crate::color::ExtendedColorType; use crate::image::ImageFormat; @@ -61,6 +66,7 @@ pub enum ImageError { Unsupported(UnsupportedError), /// An error occurred while interacting with the environment. + #[cfg(feature = "std")] IoError(io::Error), } @@ -88,6 +94,11 @@ pub enum UnsupportedErrorKind { GenericFeature(String), } +#[cfg(feature = "std")] +type Underlying = Option>; +#[cfg(not(feature = "std"))] +type Underlying = Option>; + /// An error was encountered while encoding an image. /// /// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its @@ -97,7 +108,7 @@ pub enum UnsupportedErrorKind { #[derive(Debug)] pub struct EncodingError { format: ImageFormatHint, - underlying: Option>, + underlying: Underlying, } /// An error was encountered in inputs arguments. @@ -109,7 +120,7 @@ pub struct EncodingError { #[derive(Debug)] pub struct ParameterError { kind: ParameterErrorKind, - underlying: Option>, + underlying: Underlying, } /// Details how a parameter is malformed. @@ -136,7 +147,7 @@ pub enum ParameterErrorKind { #[derive(Debug)] pub struct DecodingError { format: ImageFormatHint, - underlying: Option>, + underlying: Underlying, } /// Completing the operation would have required more resources than allowed. @@ -183,6 +194,7 @@ pub enum ImageFormatHint { Name(String), /// A common path extension for the format is known. + #[cfg(feature = "std")] PathExtension(std::path::PathBuf), /// The format is not known or could not be determined. @@ -211,6 +223,7 @@ impl UnsupportedError { impl DecodingError { /// Create a `DecodingError` that stems from an arbitrary error of an underlying decoder. + #[cfg(feature = "std")] pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { DecodingError { format, @@ -236,6 +249,7 @@ impl DecodingError { impl EncodingError { /// Create an `EncodingError` that stems from an arbitrary error of an underlying encoder. + #[cfg(feature = "std")] pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { EncodingError { format, @@ -286,6 +300,7 @@ impl LimitError { } } +#[cfg(feature = "std")] impl From for ImageError { fn from(err: io::Error) -> ImageError { ImageError::IoError(err) @@ -298,6 +313,7 @@ impl From for ImageFormatHint { } } +#[cfg(feature = "std")] impl From<&'_ std::path::Path> for ImageFormatHint { fn from(path: &'_ std::path::Path) -> Self { match path.extension() { @@ -332,6 +348,7 @@ impl fmt::Display for ImageError { } } +#[cfg(feature = "std")] impl Error for ImageError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { @@ -380,6 +397,7 @@ impl fmt::Display for UnsupportedError { } } +#[cfg(feature = "std")] impl Error for UnsupportedError {} impl fmt::Display for ParameterError { @@ -408,6 +426,7 @@ impl fmt::Display for ParameterError { } } +#[cfg(feature = "std")] impl Error for ParameterError { fn source(&self) -> Option<&(dyn Error + 'static)> { match &self.underlying { @@ -430,6 +449,7 @@ impl fmt::Display for EncodingError { } } +#[cfg(feature = "std")] impl Error for EncodingError { fn source(&self) -> Option<&(dyn Error + 'static)> { match &self.underlying { @@ -453,6 +473,7 @@ impl fmt::Display for DecodingError { } } +#[cfg(feature = "std")] impl Error for DecodingError { fn source(&self) -> Option<&(dyn Error + 'static)> { match &self.underlying { @@ -475,6 +496,7 @@ impl fmt::Display for LimitError { } } +#[cfg(feature = "std")] impl Error for LimitError {} impl fmt::Display for ImageFormatHint { diff --git a/src/flat.rs b/src/flat.rs index 96644398a2..9362f2eb6b 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -41,9 +41,11 @@ //! } //! ``` //! -use std::marker::PhantomData; -use std::ops::{Deref, Index, IndexMut}; -use std::{cmp, error, fmt}; +//! +use alloc::vec::Vec; +use core::marker::PhantomData; +use core::ops::{Deref, Index, IndexMut}; +use core::{cmp, fmt}; use num_traits::Zero; @@ -1483,6 +1485,7 @@ impl From for ImageError { write!(f, "Required sample buffer in normal form {:?}", self.0) } } + #[cfg(feature = "std")] impl error::Error for NormalFormRequiredError {} match error { @@ -1527,6 +1530,7 @@ impl fmt::Display for Error { } } +#[cfg(feature = "std")] impl error::Error for Error {} impl PartialOrd for NormalForm { diff --git a/src/image.rs b/src/image.rs index ae4cc65e6e..49b3fba928 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,11 +1,13 @@ #![allow(clippy::too_many_arguments)] -use std::convert::TryFrom; -use std::ffi::OsStr; +use alloc::string::String; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::ffi::OsStr; +use core::ops::{Deref, DerefMut}; +use core::usize; use std::io; use std::io::Read; -use std::ops::{Deref, DerefMut}; use std::path::Path; -use std::usize; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ @@ -604,7 +606,7 @@ where ))); } - let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / std::mem::size_of::()]; + let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / core::mem::size_of::()]; decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?; Ok(buf) } @@ -1318,6 +1320,8 @@ where #[cfg(test)] mod tests { + use alloc::boxed::Box; + use alloc::vec::Vec; use std::io; use std::path::Path; diff --git a/src/imageops/affine.rs b/src/imageops/affine.rs index 548381c0bb..059d975b31 100644 --- a/src/imageops/affine.rs +++ b/src/imageops/affine.rs @@ -1,5 +1,7 @@ //! Functions for performing affine transformations. +use alloc::vec::Vec; + use crate::error::{ImageError, ParameterError, ParameterErrorKind}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::Pixel; @@ -52,7 +54,7 @@ pub fn rotate90_in( where I: GenericImageView, I::Pixel: 'static, - Container: std::ops::DerefMut::Subpixel]>, + Container: core::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { @@ -78,7 +80,7 @@ pub fn rotate180_in( where I: GenericImageView, I::Pixel: 'static, - Container: std::ops::DerefMut::Subpixel]>, + Container: core::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { @@ -104,7 +106,7 @@ pub fn rotate270_in( where I: GenericImageView, I::Pixel: 'static, - Container: std::ops::DerefMut::Subpixel]>, + Container: core::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { @@ -156,7 +158,7 @@ pub fn flip_horizontal_in( where I: GenericImageView, I::Pixel: 'static, - Container: std::ops::DerefMut::Subpixel]>, + Container: core::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { @@ -182,7 +184,7 @@ pub fn flip_vertical_in( where I: GenericImageView, I::Pixel: 'static, - Container: std::ops::DerefMut::Subpixel]>, + Container: core::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { @@ -270,6 +272,7 @@ mod test { use crate::image::GenericImage; use crate::traits::Pixel; use crate::{GrayImage, ImageBuffer}; + use alloc::vec::Vec; macro_rules! assert_pixels_eq { ($actual:expr, $expected:expr) => {{ diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index 085e5f44b5..ca0b6e94b4 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -1,7 +1,7 @@ //! Functions for altering and converting the color of pixelbufs - +use alloc::vec::Vec; +use core::f64::consts::PI; use num_traits::NumCast; -use std::f64::consts::PI; use crate::color::{FromColor, IntoColor, Luma, LumaA, Rgba}; use crate::image::{GenericImage, GenericImageView}; diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index 57761c4878..b222ac4af8 100644 --- a/src/imageops/mod.rs +++ b/src/imageops/mod.rs @@ -1,5 +1,5 @@ //! Image Processing Functions -use std::cmp; +use core::cmp; use crate::image::{GenericImage, GenericImageView, SubImage}; use crate::traits::{Lerp, Pixel, Primitive}; diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 1aed9bb327..a5c0d9f169 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -2,8 +2,9 @@ // See http://cs.brown.edu/courses/cs123/lectures/08_Image_Processing_IV.pdf // for some of the theory behind image scaling and convolution - -use std::f32; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::f32; use num_traits::{NumCast, ToPrimitive, Zero}; diff --git a/src/io/free_functions.rs b/src/io/free_functions.rs index d6047d7fea..839bc45017 100644 --- a/src/io/free_functions.rs +++ b/src/io/free_functions.rs @@ -1,7 +1,7 @@ +use core::u32; use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Seek}; use std::path::Path; -use std::u32; use crate::codecs::*; @@ -137,6 +137,7 @@ pub(crate) fn image_dimensions_with_format_impl( load_decoder(buffered_read, format, super::Limits::default(), DimVisitor) } +#[cfg(feature = "std")] #[allow(unused_variables)] // Most variables when no features are supported pub(crate) fn save_buffer_impl( @@ -150,6 +151,7 @@ pub(crate) fn save_buffer_impl( save_buffer_with_format_impl(path, buf, width, height, color, format) } +#[cfg(feature = "std")] #[allow(unused_variables)] // Most variables when no features are supported pub(crate) fn save_buffer_with_format_impl( @@ -190,6 +192,7 @@ pub(crate) fn save_buffer_with_format_impl( } #[allow(unused_variables)] +#[cfg(feature = "std")] // Most variables when no features are supported pub(crate) fn write_buffer_impl( buffered_write: &mut W, diff --git a/src/io/mod.rs b/src/io/mod.rs index 160bd5580d..c17e34009a 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,12 +1,15 @@ //! Input and output of images. -use std::convert::TryFrom; +use core::convert::TryFrom; use crate::{error, ImageError, ImageResult}; +#[cfg(feature = "std")] pub(crate) mod free_functions; +#[cfg(feature = "std")] mod reader; +#[cfg(feature = "std")] pub use self::reader::Reader; /// Set of supported strict limits for a decoder. diff --git a/src/io/reader.rs b/src/io/reader.rs index 7721a79311..0bfe9edca3 100644 --- a/src/io/reader.rs +++ b/src/io/reader.rs @@ -7,6 +7,7 @@ use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::image::ImageFormat; use crate::{ImageError, ImageResult}; +#[cfg(feature = "std")] use super::free_functions; /// A multi-format image reader. diff --git a/src/lib.rs b/src/lib.rs index 60a641f7bc..08f25d9152 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,8 @@ //! [`ImageDecoderRect`]: trait.ImageDecoderRect.html //! [`ImageDecoder`]: trait.ImageDecoder.html //! [`ImageEncoder`]: trait.ImageEncoder.html +// #![no_std] +#![cfg_attr(all(not(feature = "std"), not(test)), no_std)] #![warn(missing_docs)] #![warn(unused_qualifications)] #![deny(unreachable_pub)] @@ -94,6 +96,9 @@ // it's a backwards compatibility break #![allow(clippy::wrong_self_convention, clippy::enum_variant_names)] +#[macro_use] +extern crate alloc; + #[cfg(all(test, feature = "benchmarks"))] extern crate test; @@ -139,12 +144,14 @@ pub use crate::flat::FlatSamples; pub use crate::traits::{EncodableLayout, Pixel, PixelWithColorType, Primitive}; // Opening and loading images +#[cfg(feature = "std")] pub use crate::dynimage::{ image_dimensions, load_from_memory, load_from_memory_with_format, open, save_buffer, save_buffer_with_format, write_buffer_with_format, }; pub use crate::io::free_functions::{guess_format, load}; +#[cfg(feature = "std")] pub use crate::dynimage::DynamicImage; pub use crate::animation::{Delay, Frame, Frames}; @@ -254,6 +261,7 @@ mod animation; #[path = "buffer.rs"] mod buffer_; mod color; +#[cfg(feature = "std")] mod dynimage; mod image; mod traits; diff --git a/src/math/utils.rs b/src/math/utils.rs index 3a1f121d45..69a9633745 100644 --- a/src/math/utils.rs +++ b/src/math/utils.rs @@ -1,6 +1,6 @@ //! Shared mathematical utility functions. -use std::cmp::max; +use core::cmp::max; /// Calculates the width and height an image should be resized to. /// This preserves aspect ratio, and based on the `fill` parameter diff --git a/src/traits.rs b/src/traits.rs index 56daaa0ddf..430716d158 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,8 +2,8 @@ // Note copied from the stdlib under MIT license +use core::ops::AddAssign; use num_traits::{Bounded, Num, NumCast}; -use std::ops::AddAssign; use crate::color::{ColorType, Luma, LumaA, Rgb, Rgba}; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 92bb185148..eed23c2796 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,7 @@ //! Utilities +use alloc::vec::Vec; -use std::iter::repeat; +use core::iter::repeat; #[inline(always)] pub(crate) fn expand_packed(buf: &mut [u8], channels: usize, bit_depth: u8, mut func: F) @@ -66,7 +67,7 @@ pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec { #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn check_dimension_overflow(width: u32, height: u32, bytes_per_pixel: u8) -> bool { - width as u64 * height as u64 > std::u64::MAX / bytes_per_pixel as u64 + width as u64 * height as u64 > core::u64::MAX / bytes_per_pixel as u64 } #[allow(dead_code)] From d9c8b544c8192ffea5032c73a07a757abb39dc09 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Mon, 13 Feb 2023 18:35:23 +0100 Subject: [PATCH 02/16] WIP 2 --- Cargo.toml | 4 +- src/codecs/png.rs | 29 ++-- src/codecs/tga/encoder.rs | 1 + src/codecs/tiff.rs | 74 +++++---- src/codecs/webp/extended.rs | 2 + src/codecs/webp/lossless.rs | 5 +- src/codecs/webp/vp8.rs | 37 +++-- src/dynimage.rs | 16 +- src/error.rs | 293 ++++++------------------------------ src/flat.rs | 49 ++---- src/image.rs | 41 ++--- src/imageops/colorops.rs | 2 +- src/lib.rs | 7 +- src/utils/mod.rs | 3 + 14 files changed, 168 insertions(+), 395 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bdd35bc862..a093a0a954 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ bytemuck = { version = "1.7.0", features = ["extern_crate_alloc"] } # includes c byteorder = "1.3.2" num-rational = { version = "0.4", default-features = false } num-traits = "0.2.0" +snafu = { version = "0.7", default-features = false } gif = { version = "0.12", optional = true } jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, optional = true } png = { version = "0.17.6", optional = true } @@ -61,8 +62,9 @@ jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, [features] # TODO: Add "avif" to this list while preparing for 0.24.0 -default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"] +default = ["std", "gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"] +std = ["snafu/std"] ico = ["bmp", "png"] pnm = [] tga = [] diff --git a/src/codecs/png.rs b/src/codecs/png.rs index ae3032bf78..7e086ac479 100644 --- a/src/codecs/png.rs +++ b/src/codecs/png.rs @@ -220,10 +220,10 @@ impl PngDecoder { } fn unsupported_color(ect: ExtendedColorType) -> ImageError { - ImageError::Unsupported(UnsupportedError::from_format_and_kind( - ImageFormat::Png.into(), - UnsupportedErrorKind::Color(ect), - )) + ImageError::Unsupported { + format: ImageFormat::Png.into(), + kind: UnsupportedErrorKind::Color(ect), + } } impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { @@ -668,21 +668,22 @@ impl ImageError { fn from_png(err: png::DecodingError) -> ImageError { use png::DecodingError::*; match err { + #[cfg(features = "std")] IoError(err) => ImageError::IoError(err), // The input image was not a valid PNG. - err @ Format(_) => { - ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err)) - } + err @ Format(_) => ImageError::Decoding { + format: ImageFormat::Png.into(), + }, // Other is used when: // - The decoder is polled for more animation frames despite being done (or not being animated // in the first place). // - The output buffer does not have the required size. - err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::Generic(err.to_string()), - )), - LimitsExceeded => { - ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) - } + err @ Parameter(_) => ImageError::Parameter { + kind: ParameterErrorKind::Generic(err.to_string()), + }, + LimitsExceeded => ImageError::Limits { + kind: LimitErrorKind::InsufficientMemory, + }, } } } @@ -749,8 +750,6 @@ mod tests { #[test] fn underlying_error() { - use std::error::Error; - let mut not_png = std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png") .unwrap(); diff --git a/src/codecs/tga/encoder.rs b/src/codecs/tga/encoder.rs index cf3498421f..6544f0131e 100644 --- a/src/codecs/tga/encoder.rs +++ b/src/codecs/tga/encoder.rs @@ -101,6 +101,7 @@ impl ImageEncoder for TgaEncoder { mod tests { use super::{EncoderError, TgaEncoder}; use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError}; + use alloc::vec::Vec; use std::{error::Error, io::Cursor}; fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec { diff --git a/src/codecs/tiff.rs b/src/codecs/tiff.rs index f3cc027a01..ba78a6250d 100644 --- a/src/codecs/tiff.rs +++ b/src/codecs/tiff.rs @@ -84,26 +84,24 @@ where fn check_sample_format(sample_format: u16) -> Result<(), ImageError> { match tiff::tags::SampleFormat::from_u16(sample_format) { Some(tiff::tags::SampleFormat::Uint) => Ok(()), - Some(other) => Err(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormat::Tiff.into(), - UnsupportedErrorKind::GenericFeature(format!( - "Unhandled TIFF sample format {:?}", - other - )), - ), - )), - None => Err(ImageError::Decoding(DecodingError::from_format_hint( - ImageFormat::Tiff.into(), - ))), + Some(other) => Err(ImageError::Unsupported { + format: ImageFormat::Tiff.into(), + kind: UnsupportedErrorKind::GenericFeature(format!( + "Unhandled TIFF sample format {:?}", + other + )), + }), + None => Err(ImageError::Decoding { + format: ImageFormat::Tiff.into(), + }), } } fn err_unknown_color_type(value: u8) -> ImageError { - ImageError::Unsupported(UnsupportedError::from_format_and_kind( - ImageFormat::Tiff.into(), - UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)), - )) + ImageError::Unsupported { + format: ImageFormat::Tiff.into(), + kind: UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)), + } } impl ImageError { @@ -112,18 +110,16 @@ impl ImageError { tiff::TiffError::IoError(err) => ImageError::IoError(err), err @ tiff::TiffError::FormatError(_) | err @ tiff::TiffError::IntSizeError - | err @ tiff::TiffError::UsageError(_) => { - ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err)) - } - tiff::TiffError::UnsupportedError(desc) => { - ImageError::Unsupported(UnsupportedError::from_format_and_kind( - ImageFormat::Tiff.into(), - UnsupportedErrorKind::GenericFeature(desc.to_string()), - )) - } - tiff::TiffError::LimitsExceeded => { - ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) - } + | err @ tiff::TiffError::UsageError(_) => ImageError::Decoding { + format: ImageFormat::Tiff.into(), + }, + tiff::TiffError::UnsupportedError(desc) => ImageError::Unsupported { + format: ImageFormat::Tiff.into(), + kind: UnsupportedErrorKind::GenericFeature(desc.to_string()), + }, + tiff::TiffError::LimitsExceeded => ImageError::Limits { + kind: LimitErrorKind::InsufficientMemory, + }, } } @@ -132,18 +128,16 @@ impl ImageError { tiff::TiffError::IoError(err) => ImageError::IoError(err), err @ tiff::TiffError::FormatError(_) | err @ tiff::TiffError::IntSizeError - | err @ tiff::TiffError::UsageError(_) => { - ImageError::Encoding(EncodingError::new(ImageFormat::Tiff.into(), err)) - } - tiff::TiffError::UnsupportedError(desc) => { - ImageError::Unsupported(UnsupportedError::from_format_and_kind( - ImageFormat::Tiff.into(), - UnsupportedErrorKind::GenericFeature(desc.to_string()), - )) - } - tiff::TiffError::LimitsExceeded => { - ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) - } + | err @ tiff::TiffError::UsageError(_) => ImageError::Encoding { + format: ImageFormat::Tiff.into(), + }, + tiff::TiffError::UnsupportedError(desc) => ImageError::Unsupported { + format: ImageFormat::Tiff.into(), + kind: UnsupportedErrorKind::GenericFeature(desc.to_string()), + }, + tiff::TiffError::LimitsExceeded => ImageError::Limits { + kind: LimitErrorKind::InsufficientMemory, + }, } } } diff --git a/src/codecs/webp/extended.rs b/src/codecs/webp/extended.rs index e7aacf9e09..6684baa218 100644 --- a/src/codecs/webp/extended.rs +++ b/src/codecs/webp/extended.rs @@ -1,3 +1,5 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; use std::convert::TryInto; use std::io::{self, Cursor, Error, Read}; use std::{error, fmt}; diff --git a/src/codecs/webp/lossless.rs b/src/codecs/webp/lossless.rs index 7271eda902..df1780b818 100644 --- a/src/codecs/webp/lossless.rs +++ b/src/codecs/webp/lossless.rs @@ -3,6 +3,7 @@ //! [Lossless spec](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) //! +use alloc::vec::Vec; use std::{ convert::TryFrom, convert::TryInto, @@ -104,7 +105,9 @@ impl fmt::Display for DecoderError { impl From for ImageError { fn from(e: DecoderError) -> ImageError { - ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) + ImageError::Decoding { + format: ImageFormat::WebP.into(), + } } } diff --git a/src/codecs/webp/vp8.rs b/src/codecs/webp/vp8.rs index 334293b99f..a7519a83c1 100644 --- a/src/codecs/webp/vp8.rs +++ b/src/codecs/webp/vp8.rs @@ -12,6 +12,8 @@ //! of the VP8 format //! +use alloc::borrow::ToOwned; +use alloc::vec::Vec; use byteorder::{LittleEndian, ReadBytesExt}; use std::convert::TryInto; use std::default::Default; @@ -716,7 +718,9 @@ impl fmt::Display for DecoderError { impl From for ImageError { fn from(e: DecoderError) -> ImageError { - ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) + ImageError::Decoding { + format: ImageFormat::WebP.into(), + } } } @@ -1315,12 +1319,10 @@ impl Vp8Decoder { if !self.frame.keyframe { // 9.7 refresh golden frame and altref frame // FIXME: support this? - return Err(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormat::WebP.into(), - UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), - ), - )); + return Err(ImageError::Unsupported { + format: ImageFormat::WebP.into(), + kind: UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), + }); } else { // Refresh entropy probs ????? let _ = self.b.read_literal(1); @@ -1340,12 +1342,10 @@ impl Vp8Decoder { self.prob_intra = 0; // FIXME: support this? - return Err(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormat::WebP.into(), - UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), - ), - )); + return Err(ImageError::Unsupported { + format: ImageFormat::WebP.into(), + kind: UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), + }); } else { // Reset motion vectors } @@ -1376,12 +1376,10 @@ impl Vp8Decoder { }; if inter_predicted { - return Err(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormat::WebP.into(), - UnsupportedErrorKind::GenericFeature("VP8 inter-prediction".to_owned()), - ), - )); + return Err(ImageError::Unsupported { + format: ImageFormat::WebP.into(), + kind: UnsupportedErrorKind::GenericFeature("VP8 inter-prediction".to_owned()), + }); } if self.frame.keyframe { @@ -2579,6 +2577,7 @@ fn predict_bhupred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { #[cfg(test)] mod test { + use alloc::vec::Vec; #[cfg(feature = "benchmarks")] extern crate test; diff --git a/src/dynimage.rs b/src/dynimage.rs index 42c6dc0f59..99cabb1148 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1081,20 +1081,18 @@ fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult { - return Err(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormatHint::Unknown, - UnsupportedErrorKind::Color(color_type.into()), - ), - )) + return Err(ImageError::Unsupported { + format: ImageFormatHint::Unknown, + kind: UnsupportedErrorKind::Color(color_type.into()), + }) } }; match image { Some(image) => Ok(image), - None => Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))), + None => Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }), } } diff --git a/src/error.rs b/src/error.rs index 275c591b05..51d5ff870e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,8 +16,7 @@ use alloc::boxed::Box; use alloc::string::String; use core::fmt; -#[cfg(feature = "std")] -use std::error::Error; +use snafu::{prelude::*, Error}; #[cfg(feature = "std")] use std::io; @@ -28,14 +27,21 @@ use crate::image::ImageFormat; /// /// This high level enum allows, by variant matching, a rough separation of concerns between /// underlying IO, the caller, format specifications, and the `image` implementation. -#[derive(Debug)] +#[derive(Snafu, Debug)] pub enum ImageError { /// An error was encountered while decoding. /// /// This means that the input data did not conform to the specification of some image format, /// or that no format could be determined, or that it did not match format specific /// requirements set by the caller. - Decoding(DecodingError), + /// + /// An error was encountered while decoding an image. + /// + /// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its + /// documentation for more information. + /// + /// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding + Decoding { format: ImageFormatHint }, /// An error was encountered while encoding. /// @@ -43,19 +49,40 @@ pub enum ImageError { /// specification has no representation for its color space or because a necessary conversion /// is ambiguous. In some cases it might also happen that the dimensions can not be used with /// the format. - Encoding(EncodingError), + /// + /// An error was encountered while encoding an image. + /// + /// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its + /// documentation for more information. + /// + /// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding + Encoding { format: ImageFormatHint }, /// An error was encountered in input arguments. /// /// This is a catch-all case for strictly internal operations such as scaling, conversions, /// etc. that involve no external format specifications. - Parameter(ParameterError), + /// + /// An error was encountered in inputs arguments. + /// + /// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its + /// documentation for more information. + /// + /// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter + Parameter { kind: ParameterErrorKind }, /// Completing the operation would have required more resources than allowed. /// /// Errors of this type are limits set by the user or environment, *not* inherent in a specific /// format or operation that was executed. - Limits(LimitError), + /// + /// Completing the operation would have required more resources than allowed. + /// + /// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its + /// documentation for more information. + /// + /// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits + Limits { kind: LimitErrorKind }, /// An operation can not be completed by the chosen abstraction. /// @@ -63,22 +90,20 @@ pub enum ImageError { /// * it requires a disabled feature, /// * the implementation does not yet exist, or /// * no abstraction for a lower level could be found. - Unsupported(UnsupportedError), + /// + /// The implementation for an operation was not provided. + /// + /// See the variant [`Unsupported`] for more documentation. + /// + /// [`Unsupported`]: enum.ImageError.html#variant.Unsupported + Unsupported { + format: ImageFormatHint, + kind: UnsupportedErrorKind, + }, /// An error occurred while interacting with the environment. #[cfg(feature = "std")] - IoError(io::Error), -} - -/// The implementation for an operation was not provided. -/// -/// See the variant [`Unsupported`] for more documentation. -/// -/// [`Unsupported`]: enum.ImageError.html#variant.Unsupported -#[derive(Debug)] -pub struct UnsupportedError { - format: ImageFormatHint, - kind: UnsupportedErrorKind, + IoError { err: io::Error }, } /// Details what feature is not supported. @@ -94,35 +119,6 @@ pub enum UnsupportedErrorKind { GenericFeature(String), } -#[cfg(feature = "std")] -type Underlying = Option>; -#[cfg(not(feature = "std"))] -type Underlying = Option>; - -/// An error was encountered while encoding an image. -/// -/// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its -/// documentation for more information. -/// -/// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding -#[derive(Debug)] -pub struct EncodingError { - format: ImageFormatHint, - underlying: Underlying, -} - -/// An error was encountered in inputs arguments. -/// -/// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its -/// documentation for more information. -/// -/// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter -#[derive(Debug)] -pub struct ParameterError { - kind: ParameterErrorKind, - underlying: Underlying, -} - /// Details how a parameter is malformed. #[derive(Clone, Debug, Hash, PartialEq)] #[non_exhaustive] @@ -138,30 +134,6 @@ pub enum ParameterErrorKind { NoMoreData, } -/// An error was encountered while decoding an image. -/// -/// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its -/// documentation for more information. -/// -/// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding -#[derive(Debug)] -pub struct DecodingError { - format: ImageFormatHint, - underlying: Underlying, -} - -/// Completing the operation would have required more resources than allowed. -/// -/// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its -/// documentation for more information. -/// -/// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits -#[derive(Debug)] -pub struct LimitError { - kind: LimitErrorKind, - // do we need an underlying error? -} - /// Indicates the limit that prevented an operation from completing. /// /// Note that this enumeration is not exhaustive and may in the future be extended to provide more @@ -335,181 +307,6 @@ impl From for UnsupportedError { /// Result of an image decoding/encoding process pub type ImageResult = Result; -impl fmt::Display for ImageError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - ImageError::IoError(err) => err.fmt(fmt), - ImageError::Decoding(err) => err.fmt(fmt), - ImageError::Encoding(err) => err.fmt(fmt), - ImageError::Parameter(err) => err.fmt(fmt), - ImageError::Limits(err) => err.fmt(fmt), - ImageError::Unsupported(err) => err.fmt(fmt), - } - } -} - -#[cfg(feature = "std")] -impl Error for ImageError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - ImageError::IoError(err) => err.source(), - ImageError::Decoding(err) => err.source(), - ImageError::Encoding(err) => err.source(), - ImageError::Parameter(err) => err.source(), - ImageError::Limits(err) => err.source(), - ImageError::Unsupported(err) => err.source(), - } - } -} - -impl fmt::Display for UnsupportedError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match &self.kind { - UnsupportedErrorKind::Format(ImageFormatHint::Unknown) => { - write!(fmt, "The image format could not be determined",) - } - UnsupportedErrorKind::Format(format @ ImageFormatHint::PathExtension(_)) => write!( - fmt, - "The file extension {} was not recognized as an image format", - format, - ), - UnsupportedErrorKind::Format(format) => { - write!(fmt, "The image format {} is not supported", format,) - } - UnsupportedErrorKind::Color(color) => write!( - fmt, - "The decoder for {} does not support the color type `{:?}`", - self.format, color, - ), - UnsupportedErrorKind::GenericFeature(message) => match &self.format { - ImageFormatHint::Unknown => write!( - fmt, - "The decoder does not support the format feature {}", - message, - ), - other => write!( - fmt, - "The decoder for {} does not support the format features {}", - other, message, - ), - }, - } - } -} - -#[cfg(feature = "std")] -impl Error for UnsupportedError {} - -impl fmt::Display for ParameterError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match &self.kind { - ParameterErrorKind::DimensionMismatch => write!( - fmt, - "The Image's dimensions are either too \ - small or too large" - ), - ParameterErrorKind::FailedAlready => write!( - fmt, - "The end the image stream has been reached due to a previous error" - ), - ParameterErrorKind::Generic(message) => { - write!(fmt, "The parameter is malformed: {}", message,) - } - ParameterErrorKind::NoMoreData => write!(fmt, "The end of the image has been reached",), - }?; - - if let Some(underlying) = &self.underlying { - write!(fmt, "\n{}", underlying)?; - } - - Ok(()) - } -} - -#[cfg(feature = "std")] -impl Error for ParameterError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.underlying { - None => None, - Some(source) => Some(&**source), - } - } -} - -impl fmt::Display for EncodingError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match &self.underlying { - Some(underlying) => write!( - fmt, - "Format error encoding {}:\n{}", - self.format, underlying, - ), - None => write!(fmt, "Format error encoding {}", self.format,), - } - } -} - -#[cfg(feature = "std")] -impl Error for EncodingError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.underlying { - None => None, - Some(source) => Some(&**source), - } - } -} - -impl fmt::Display for DecodingError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match &self.underlying { - None => match self.format { - ImageFormatHint::Unknown => write!(fmt, "Format error"), - _ => write!(fmt, "Format error decoding {}", self.format), - }, - Some(underlying) => { - write!(fmt, "Format error decoding {}: {}", self.format, underlying) - } - } - } -} - -#[cfg(feature = "std")] -impl Error for DecodingError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.underlying { - None => None, - Some(source) => Some(&**source), - } - } -} - -impl fmt::Display for LimitError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self.kind { - LimitErrorKind::InsufficientMemory => write!(fmt, "Insufficient memory"), - LimitErrorKind::DimensionError => write!(fmt, "Image is too large"), - LimitErrorKind::Unsupported { .. } => { - write!(fmt, "The following strict limits are specified but not supported by the opertation: ")?; - Ok(()) - } - } - } -} - -#[cfg(feature = "std")] -impl Error for LimitError {} - -impl fmt::Display for ImageFormatHint { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - ImageFormatHint::Exact(format) => write!(fmt, "{:?}", format), - ImageFormatHint::Name(name) => write!(fmt, "`{}`", name), - ImageFormatHint::PathExtension(ext) => write!(fmt, "`.{:?}`", ext), - ImageFormatHint::Unknown => write!(fmt, "`Unknown`"), - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/flat.rs b/src/flat.rs index 9362f2eb6b..710fca2c8b 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -46,8 +46,8 @@ use alloc::vec::Vec; use core::marker::PhantomData; use core::ops::{Deref, Index, IndexMut}; use core::{cmp, fmt}; - use num_traits::Zero; +use snafu::prelude::*; use crate::color::ColorType; use crate::error::{ @@ -599,7 +599,9 @@ impl FlatSamples { Buffer: AsRef<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor(P::COLOR_TYPE)); + return Err(Error::WrongColor { + underlying: P::COLOR_TYPE, + }); } let as_ref = self.samples.as_ref(); @@ -1007,7 +1009,7 @@ where /// samples in a row major matrix representation. But this error type may be /// resused for other import functions. A more versatile user may also try to /// correct the underlying representation depending on the error variant. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Snafu, Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Error { /// The represented image was too large. /// @@ -1017,7 +1019,7 @@ pub enum Error { /// The represented image can not use this representation. /// /// Has an additional value of the normalized form that would be accepted. - NormalFormRequired(NormalForm), + NormalFormRequired { underlying: NormalForm }, /// The color format did not match the channel count. /// @@ -1028,7 +1030,7 @@ pub enum Error { /// directly memory unsafe although that will likely alias pixels. One scenario is when you /// want to construct an `Rgba` image but have only 3 bytes per pixel and for some reason don't /// care about the value of the alpha channel even though you need `Rgba`. - WrongColor(ColorType), + WrongColor { underlying: ColorType }, } /// Different normal forms of buffers. @@ -1478,15 +1480,9 @@ where impl From for ImageError { fn from(error: Error) -> ImageError { - #[derive(Debug)] + #[derive(Snafu, Debug)] + #[snafu(display("Required sample buffer in normal form {self.0}"))] struct NormalFormRequiredError(NormalForm); - impl fmt::Display for NormalFormRequiredError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Required sample buffer in normal form {:?}", self.0) - } - } - #[cfg(feature = "std")] - impl error::Error for NormalFormRequiredError {} match error { Error::TooLarge => ImageError::Parameter(ParameterError::from_kind( @@ -1506,33 +1502,6 @@ impl From for ImageError { } } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::TooLarge => write!(f, "The layout is too large"), - Error::NormalFormRequired(form) => write!( - f, - "The layout needs to {}", - match form { - NormalForm::ColumnMajorPacked => "be packed and in column major form", - NormalForm::ImagePacked => "be fully packed", - NormalForm::PixelPacked => "have packed pixels", - NormalForm::RowMajorPacked => "be packed and in row major form", - NormalForm::Unaliased => "not have any aliasing channels", - } - ), - Error::WrongColor(color) => write!( - f, - "The chosen color type does not match the hint {:?}", - color - ), - } - } -} - -#[cfg(feature = "std")] -impl error::Error for Error {} - impl PartialOrd for NormalForm { /// Compares the logical preconditions. /// diff --git a/src/image.rs b/src/image.rs index 49b3fba928..c5a09e005d 100644 --- a/src/image.rs +++ b/src/image.rs @@ -2,7 +2,6 @@ use alloc::string::String; use alloc::vec::Vec; use core::convert::TryFrom; -use core::ffi::OsStr; use core::ops::{Deref, DerefMut}; use core::usize; use std::io; @@ -85,10 +84,11 @@ impl ImageFormat { /// let format = ImageFormat::from_extension("jpg"); /// assert_eq!(format, Some(ImageFormat::Jpeg)); /// ``` + #[cfg(features = "std")] #[inline] pub fn from_extension(ext: S) -> Option where - S: AsRef, + S: AsRef, { // thin wrapper function to strip generics fn inner(ext: &OsStr) -> Option { @@ -129,6 +129,7 @@ impl ImageFormat { /// /// # Ok::<(), image::error::ImageError>(()) /// ``` + #[cfg(features = "std")] #[inline] pub fn from_path

(path: P) -> ImageResult where @@ -560,14 +561,14 @@ where || width == 0 || height == 0 { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); + return Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }); } if scanline_bytes > usize::max_value() as u64 { - return Err(ImageError::Limits(LimitError::from_kind( - LimitErrorKind::InsufficientMemory, - ))); + return Err(ImageError::Limits { + kind: LimitErrorKind::InsufficientMemory, + }); } progress_callback(Progress { @@ -601,9 +602,9 @@ where { let total_bytes = usize::try_from(decoder.total_bytes()); if total_bytes.is_err() || total_bytes.unwrap() > isize::max_value() as usize { - return Err(ImageError::Limits(LimitError::from_kind( - LimitErrorKind::InsufficientMemory, - ))); + return Err(ImageError::Limits { + kind: LimitErrorKind::InsufficientMemory, + }); } let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / core::mem::size_of::()]; @@ -1027,9 +1028,9 @@ pub trait GenericImage: GenericImageView { // Do bounds checking here so we can use the non-bounds-checking // functions to copy pixels. if self.width() < other.width() + x || self.height() < other.height() + y { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); + return Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }); } for k in 0..other.height() { @@ -1320,11 +1321,6 @@ where #[cfg(test)] mod tests { - use alloc::boxed::Box; - use alloc::vec::Vec; - use std::io; - use std::path::Path; - use super::{ load_rect, ColorType, GenericImage, GenericImageView, ImageDecoder, ImageFormat, ImageResult, @@ -1332,6 +1328,11 @@ mod tests { use crate::color::Rgba; use crate::math::Rect; use crate::{GrayImage, ImageBuffer}; + use alloc::borrow::ToOwned; + use alloc::boxed::Box; + use alloc::vec::Vec; + use std::io; + use std::path::Path; #[test] #[allow(deprecated)] @@ -1634,6 +1635,7 @@ mod tests { assert_eq!(output[0..9], [6, 7, 11, 12, 16, 17, 21, 22, 0]); } + #[cfg(features = "std")] #[test] fn test_image_format_from_path() { fn from_path(s: &str) -> ImageResult { @@ -1825,6 +1827,7 @@ mod tests { assert_eq!(&image.into_raw(), &expected); } + #[cfg(features = "std")] #[test] fn image_formats_are_recognized() { use ImageFormat::*; diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index ca0b6e94b4..3055587de7 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -543,9 +543,9 @@ where #[cfg(test)] mod test { - use super::*; use crate::{GrayImage, ImageBuffer}; + use alloc::string::ToString; macro_rules! assert_pixels_eq { ($actual:expr, $expected:expr) => {{ diff --git a/src/lib.rs b/src/lib.rs index 08f25d9152..c373e9e9de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,8 +83,7 @@ //! [`ImageDecoderRect`]: trait.ImageDecoderRect.html //! [`ImageDecoder`]: trait.ImageDecoder.html //! [`ImageEncoder`]: trait.ImageEncoder.html -// #![no_std] -#![cfg_attr(all(not(feature = "std"), not(test)), no_std)] +#![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] #![warn(unused_qualifications)] #![deny(unreachable_pub)] @@ -99,6 +98,10 @@ #[macro_use] extern crate alloc; +#[cfg(test)] +#[macro_use] +extern crate std; + #[cfg(all(test, feature = "benchmarks"))] extern crate test; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index eed23c2796..bb844b0f90 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,4 +1,5 @@ //! Utilities +use alloc::borrow::ToOwned; use alloc::vec::Vec; use core::iter::repeat; @@ -95,6 +96,8 @@ where #[cfg(test)] mod test { + use alloc::borrow::ToOwned; + #[test] fn gray_to_luma8_skip() { let check = |bit_depth, w, from, to| { From dd789f2e00308deb1df7458a2273db1efabde20d Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 24 Feb 2023 14:36:54 +0100 Subject: [PATCH 03/16] WIP 3 [no ci] --- .github/workflows/rust.yml | 6 +- src/buffer.rs | 7 ++- src/error.rs | 110 +------------------------------------ src/flat.rs | 65 +++++++++++++--------- src/image.rs | 36 +++++++----- src/imageops/affine.rs | 33 +++++------ src/io/mod.rs | 24 ++++---- src/lib.rs | 1 + tests/regression.rs | 4 +- 9 files changed, 101 insertions(+), 185 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d0a5d10064..996bafe2d3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust CI on: push: - branches: [ master, next ] - pull_request: - branches: [ master, next ] + # branches: [ master, next ] + # pull_request: + # branches: [ master, next ] jobs: build: diff --git a/src/buffer.rs b/src/buffer.rs index e1e2ebf4f1..4727f5cbb8 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -5,7 +5,6 @@ use core::marker::PhantomData; use core::ops::{Deref, DerefMut, Index, IndexMut, Range}; use core::slice::{ChunksExact, ChunksExactMut}; use num_traits::Zero; -use std::path::Path; use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; #[cfg(feature = "std")] @@ -981,6 +980,7 @@ where } } +#[cfg(feature = "std")] impl ImageBuffer where P: Pixel, @@ -992,7 +992,7 @@ where /// The image format is derived from the file extension. pub fn save(&self, path: Q) -> ImageResult<()> where - Q: AsRef, + Q: AsRef, P: PixelWithColorType, { save_buffer( @@ -1005,6 +1005,7 @@ where } } +#[cfg(feature = "std")] impl ImageBuffer where P: Pixel, @@ -1018,7 +1019,7 @@ where /// supported types. pub fn save_with_format(&self, path: Q, format: ImageFormat) -> ImageResult<()> where - Q: AsRef, + Q: AsRef, P: PixelWithColorType, { // This is valid as the subpixel is u8. diff --git a/src/error.rs b/src/error.rs index 51d5ff870e..0a9e07b846 100644 --- a/src/error.rs +++ b/src/error.rs @@ -173,109 +173,10 @@ pub enum ImageFormatHint { Unknown, } -impl UnsupportedError { - /// Create an `UnsupportedError` for an image with details on the unsupported feature. - /// - /// If the operation was not connected to a particular image format then the hint may be - /// `Unknown`. - pub fn from_format_and_kind(format: ImageFormatHint, kind: UnsupportedErrorKind) -> Self { - UnsupportedError { format, kind } - } - - /// Returns the corresponding `UnsupportedErrorKind` of the error. - pub fn kind(&self) -> UnsupportedErrorKind { - self.kind.clone() - } - - /// Returns the image format associated with this error. - pub fn format_hint(&self) -> ImageFormatHint { - self.format.clone() - } -} - -impl DecodingError { - /// Create a `DecodingError` that stems from an arbitrary error of an underlying decoder. - #[cfg(feature = "std")] - pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { - DecodingError { - format, - underlying: Some(err.into()), - } - } - - /// Create a `DecodingError` for an image format. - /// - /// The error will not contain any further information but is very easy to create. - pub fn from_format_hint(format: ImageFormatHint) -> Self { - DecodingError { - format, - underlying: None, - } - } - - /// Returns the image format associated with this error. - pub fn format_hint(&self) -> ImageFormatHint { - self.format.clone() - } -} - -impl EncodingError { - /// Create an `EncodingError` that stems from an arbitrary error of an underlying encoder. - #[cfg(feature = "std")] - pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { - EncodingError { - format, - underlying: Some(err.into()), - } - } - - /// Create an `EncodingError` for an image format. - /// - /// The error will not contain any further information but is very easy to create. - pub fn from_format_hint(format: ImageFormatHint) -> Self { - EncodingError { - format, - underlying: None, - } - } - - /// Return the image format associated with this error. - pub fn format_hint(&self) -> ImageFormatHint { - self.format.clone() - } -} - -impl ParameterError { - /// Construct a `ParameterError` directly from a corresponding kind. - pub fn from_kind(kind: ParameterErrorKind) -> Self { - ParameterError { - kind, - underlying: None, - } - } - - /// Returns the corresponding `ParameterErrorKind` of the error. - pub fn kind(&self) -> ParameterErrorKind { - self.kind.clone() - } -} - -impl LimitError { - /// Construct a generic `LimitError` directly from a corresponding kind. - pub fn from_kind(kind: LimitErrorKind) -> Self { - LimitError { kind } - } - - /// Returns the corresponding `LimitErrorKind` of the error. - pub fn kind(&self) -> LimitErrorKind { - self.kind.clone() - } -} - #[cfg(feature = "std")] impl From for ImageError { fn from(err: io::Error) -> ImageError { - ImageError::IoError(err) + ImageError::IoError { err } } } @@ -295,15 +196,6 @@ impl From<&'_ std::path::Path> for ImageFormatHint { } } -impl From for UnsupportedError { - fn from(hint: ImageFormatHint) -> Self { - UnsupportedError { - format: hint.clone(), - kind: UnsupportedErrorKind::Format(hint), - } - } -} - /// Result of an image decoding/encoding process pub type ImageResult = Result; diff --git a/src/flat.rs b/src/flat.rs index 710fca2c8b..eb966df917 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -50,10 +50,7 @@ use num_traits::Zero; use snafu::prelude::*; use crate::color::ColorType; -use crate::error::{ - DecodingError, ImageError, ImageFormatHint, ParameterError, ParameterErrorKind, - UnsupportedError, UnsupportedErrorKind, -}; +use crate::error::{ImageError, ImageFormatHint, ParameterErrorKind, UnsupportedErrorKind}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::{Pixel, PixelWithColorType}; use crate::ImageBuffer; @@ -640,7 +637,9 @@ impl FlatSamples { Buffer: AsMut<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor(P::COLOR_TYPE)); + return Err(Error::WrongColor { + underlying: P::COLOR_TYPE, + }); } let as_mut = self.samples.as_mut(); @@ -675,11 +674,15 @@ impl FlatSamples { Buffer: AsMut<[P::Subpixel]>, { if !self.layout.is_normal(NormalForm::PixelPacked) { - return Err(Error::NormalFormRequired(NormalForm::PixelPacked)); + return Err(Error::NormalFormRequired { + underlying: NormalForm::PixelPacked, + }); } if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor(P::COLOR_TYPE)); + return Err(Error::WrongColor { + underlying: P::COLOR_TYPE, + }); } let as_mut = self.samples.as_mut(); @@ -771,11 +774,21 @@ impl FlatSamples { Buffer: Deref, { if !self.is_normal(NormalForm::RowMajorPacked) { - return Err((Error::NormalFormRequired(NormalForm::RowMajorPacked), self)); + return Err(( + Error::NormalFormRequired { + underlying: NormalForm::RowMajorPacked, + }, + self, + )); } if self.layout.channels != P::CHANNEL_COUNT { - return Err((Error::WrongColor(P::COLOR_TYPE), self)); + return Err(( + Error::WrongColor { + underlying: P::COLOR_TYPE, + }, + self, + )); } if !self.fits(self.samples.deref().len()) { @@ -1196,7 +1209,12 @@ where Buffer: AsMut<[P::Subpixel]>, { if !self.inner.is_normal(NormalForm::PixelPacked) { - return Err((Error::NormalFormRequired(NormalForm::PixelPacked), self)); + return Err(( + Error::NormalFormRequired { + underlying: NormalForm::PixelPacked, + }, + self, + )); } // No length check or channel count check required, all the same. @@ -1480,24 +1498,17 @@ where impl From for ImageError { fn from(error: Error) -> ImageError { - #[derive(Snafu, Debug)] - #[snafu(display("Required sample buffer in normal form {self.0}"))] - struct NormalFormRequiredError(NormalForm); - match error { - Error::TooLarge => ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - )), - Error::NormalFormRequired(form) => ImageError::Decoding(DecodingError::new( - ImageFormatHint::Unknown, - NormalFormRequiredError(form), - )), - Error::WrongColor(color) => { - ImageError::Unsupported(UnsupportedError::from_format_and_kind( - ImageFormatHint::Unknown, - UnsupportedErrorKind::Color(color.into()), - )) - } + Error::TooLarge => ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }, + Error::NormalFormRequired { underlying } => ImageError::Decoding { + format: ImageFormatHint::Unknown, + }, + Error::WrongColor { underlying } => ImageError::Unsupported { + format: ImageFormatHint::Unknown, + kind: UnsupportedErrorKind::Color(underlying.into()), + }, } } } diff --git a/src/image.rs b/src/image.rs index c5a09e005d..b520e2ca26 100644 --- a/src/image.rs +++ b/src/image.rs @@ -4,15 +4,11 @@ use alloc::vec::Vec; use core::convert::TryFrom; use core::ops::{Deref, DerefMut}; use core::usize; -use std::io; -use std::io::Read; -use std::path::Path; +// use std::io; +// use std::io::Read; use crate::color::{ColorType, ExtendedColorType}; -use crate::error::{ - ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, ParameterError, - ParameterErrorKind, -}; +use crate::error::{ImageError, ImageFormatHint, ImageResult, LimitErrorKind, ParameterErrorKind}; use crate::math::Rect; use crate::traits::Pixel; use crate::ImageBuffer; @@ -396,11 +392,12 @@ impl ImageReadBuffer { } } + #[cfg(feature = "std")] #[allow(dead_code)] // When no image formats that use it are enabled - pub(crate) fn read(&mut self, buf: &mut [u8], mut read_scanline: F) -> io::Result + pub(crate) fn read(&mut self, buf: &mut [u8], mut read_scanline: F) -> std::io::Result where - F: FnMut(&mut [u8]) -> io::Result, + F: FnMut(&mut [u8]) -> std::io::Result, { if self.buffer.len() == self.consumed { if self.offset == self.total_bytes { @@ -443,6 +440,7 @@ impl ImageReadBuffer { /// Decodes a specific region of the image, represented by the rectangle /// starting from ```x``` and ```y``` and having ```length``` and ```width``` +#[cfg(feature = "std")] #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn load_rect<'a, D, F, F1, F2, E>( @@ -459,7 +457,7 @@ pub(crate) fn load_rect<'a, D, F, F1, F2, E>( where D: ImageDecoder<'a>, F: Fn(Progress), - F1: FnMut(&mut D, u64) -> io::Result<()>, + F1: FnMut(&mut D, u64) -> std::io::Result<()>, F2: FnMut(&mut D, &mut [u8]) -> Result<(), E>, ImageError: From, { @@ -596,6 +594,7 @@ where /// of the output buffer is guaranteed. /// /// Panics if there isn't enough memory to decode the image. +#[cfg(feature = "std")] pub(crate) fn decoder_to_vec<'a, T>(decoder: impl ImageDecoder<'a>) -> ImageResult> where T: crate::traits::Primitive + bytemuck::Pod, @@ -650,7 +649,10 @@ impl Progress { /// The trait that all decoders implement pub trait ImageDecoder<'a>: Sized { /// The type of reader produced by `into_reader`. - type Reader: Read + 'a; + #[cfg(feature = "std")] + type Reader: std::io::Read + 'a; + #[cfg(not(feature = "std"))] + type Reader: 'a; /// Returns a tuple containing the width and height of the image fn dimensions(&self) -> (u32, u32); @@ -666,6 +668,7 @@ pub trait ImageDecoder<'a>: Sized { /// Returns a reader that can be used to obtain the bytes of the image. For the best /// performance, always try to read at least `scanline_bytes` from the reader at a time. Reading /// fewer bytes will cause the reader to perform internal buffering. + #[cfg(feature = "std")] fn into_reader(self) -> ImageResult; /// Returns the total number of bytes in the decoded image. @@ -708,12 +711,14 @@ pub trait ImageDecoder<'a>: Sized { /// buf /// } /// ``` + #[cfg(feature = "std")] fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { self.read_image_with_progress(buf, |_| {}) } /// Same as `read_image` but periodically calls the provided callback to give updates on loading /// progress. + #[cfg(feature = "std")] fn read_image_with_progress( self, buf: &mut [u8], @@ -1322,8 +1327,7 @@ where #[cfg(test)] mod tests { use super::{ - load_rect, ColorType, GenericImage, GenericImageView, ImageDecoder, ImageFormat, - ImageResult, + ColorType, GenericImage, GenericImageView, ImageDecoder, ImageFormat, ImageResult, }; use crate::color::Rgba; use crate::math::Rect; @@ -1334,6 +1338,9 @@ mod tests { use std::io; use std::path::Path; + #[cfg(feature = "std")] + use super::load_rect; + #[test] #[allow(deprecated)] /// Test that alpha blending works as expected @@ -1461,6 +1468,7 @@ mod tests { view.to_image(); } + #[cfg(feature = "std")] #[test] fn test_load_rect() { struct MockDecoder { @@ -1581,6 +1589,7 @@ mod tests { } } + #[cfg(feature = "std")] #[test] fn test_load_rect_single_scanline() { const DATA: [u8; 25] = [ @@ -1846,6 +1855,7 @@ mod tests { } } + #[cfg(feature = "std")] #[test] fn total_bytes_overflow() { struct D; diff --git a/src/imageops/affine.rs b/src/imageops/affine.rs index 059d975b31..2aabf68b8b 100644 --- a/src/imageops/affine.rs +++ b/src/imageops/affine.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; -use crate::error::{ImageError, ParameterError, ParameterErrorKind}; +use crate::error::{ImageError, ParameterErrorKind}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::Pixel; use crate::ImageBuffer; @@ -58,9 +58,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); + return Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }); } for y in 0..h0 { @@ -84,9 +84,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); + return Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }); } for y in 0..h0 { @@ -110,9 +110,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); + return Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }); } for y in 0..h0 { @@ -162,9 +162,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); + return Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }); } for y in 0..h0 { @@ -188,9 +188,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); + return Err(ImageError::Parameter { + kind: ParameterErrorKind::DimensionMismatch, + }); } for y in 0..h0 { @@ -272,6 +272,7 @@ mod test { use crate::image::GenericImage; use crate::traits::Pixel; use crate::{GrayImage, ImageBuffer}; + use alloc::string::ToString; use alloc::vec::Vec; macro_rules! assert_pixels_eq { diff --git a/src/io/mod.rs b/src/io/mod.rs index c17e34009a..5eea1e103e 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -94,17 +94,17 @@ impl Limits { 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, - ))); + return Err(ImageError::Limits { + 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, - ))); + return Err(ImageError::Limits { + kind: error::LimitErrorKind::DimensionError, + }); } } @@ -116,9 +116,9 @@ impl Limits { 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, - ))); + return Err(ImageError::Limits { + kind: error::LimitErrorKind::InsufficientMemory, + }); } *max_alloc -= amount; @@ -132,9 +132,9 @@ impl Limits { match u64::try_from(amount) { Ok(n) => self.reserve(n), Err(_) if self.max_alloc.is_some() => { - return Err(ImageError::Limits(error::LimitError::from_kind( - error::LimitErrorKind::InsufficientMemory, - ))); + return Err(ImageError::Limits { + kind: error::LimitErrorKind::InsufficientMemory, + }); } Err(_) => { // Out of bounds, but we weren't asked to consider any limit. diff --git a/src/lib.rs b/src/lib.rs index c373e9e9de..9960a9bb76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,6 +152,7 @@ pub use crate::dynimage::{ image_dimensions, load_from_memory, load_from_memory_with_format, open, save_buffer, save_buffer_with_format, write_buffer_with_format, }; +#[cfg(feature = "std")] pub use crate::io::free_functions::{guess_format, load}; #[cfg(feature = "std")] diff --git a/tests/regression.rs b/tests/regression.rs index 927447d7a2..52611a897a 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -75,7 +75,7 @@ fn bad_gif_oom() { let error = image::load_from_memory(&data).unwrap_err(); assert!( - matches!(error, image::ImageError::Limits(_)) - | matches!(error, image::ImageError::Unsupported(_)) + matches!(error, image::ImageError::Limits { kind }) + | matches!(error, image::ImageError::Unsupported { format, kind }) ); } From fe365edd6ffe59e8aa66a505907fa688acd80f05 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 24 Feb 2023 15:35:46 +0100 Subject: [PATCH 04/16] WIP build no_std OK Still some doctests(and tests?) FAIL `cargo build --no-default-features` [no ci] --- Cargo.toml | 36 ++++++++++++++++++++++++++++++++++++ src/dynimage.rs | 38 ++++++++++++++++++++++++++++++++++---- tests/reference_images.rs | 6 ++++-- tests/regression.rs | 3 +++ tests/truncate_images.rs | 11 +++++++++++ 5 files changed, 88 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a093a0a954..8f60991ede 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,3 +109,39 @@ harness = false [[bench]] name = "copy_from" harness = false + +[[example]] +name = "tile" +required-features = ["std"] + +[[example]] +name = "fractal" +required-features = ["std"] + +[[example]] +name = "scaleup" +required-features = ["std"] + +[[example]] +name = "concat" +required-features = ["std"] + +[[example]] +name = "gradient" +required-features = ["std"] + +[[example]] +name = "opening" +required-features = ["std"] + +[[example]] +name = "decode" +required-features = ["std"] + +[[example]] +name = "scaledown" +required-features = ["std"] + +[[example]] +name = "convert" +required-features = ["std"] \ No newline at end of file diff --git a/src/dynimage.rs b/src/dynimage.rs index 99cabb1148..940f0b75f2 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1,8 +1,12 @@ +use alloc::borrow::ToOwned; use alloc::vec::Vec; - use core::u32; + +#[cfg(feature = "std")] use std::io; +#[cfg(feature = "std")] use std::io::{Seek, Write}; +#[cfg(feature = "std")] use std::path::Path; #[cfg(feature = "gif")] @@ -17,20 +21,22 @@ use crate::buffer_::{ Rgb16Image, RgbImage, Rgba16Image, RgbaImage, }; use crate::color::{self, IntoColor}; -use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; +use crate::error::{ImageError, ImageResult, ParameterErrorKind}; // FIXME: These imports exist because we don't support all of our own color types. -use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; +use crate::error::{ImageFormatHint, UnsupportedErrorKind}; use crate::flat::FlatSamples; use crate::image::{ GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat, ImageOutputFormat, }; use crate::imageops; -use crate::io::free_functions; use crate::math::resize_dimensions; use crate::traits::Pixel; use crate::{image, Luma, LumaA}; use crate::{Rgb32FImage, Rgba32FImage}; +#[cfg(feature = "std")] +use crate::io::free_functions; + /// A Dynamic Image /// /// This represents a _matrix_ of _pixels_ which are _convertible_ from and to an _RGBA_ @@ -54,6 +60,7 @@ use crate::{Rgb32FImage, Rgba32FImage}; /// would hardly be feasible as a simple enum, due to the sheer number of combinations of channel /// kinds, channel order, and bit depth. Rather, this type provides an opinionated selection with /// normalized channel order which can store common pixel values without loss. +#[cfg(feature = "std")] #[derive(Clone, Debug, PartialEq)] #[non_exhaustive] pub enum DynamicImage { @@ -121,50 +128,60 @@ macro_rules! dynamic_map( ); ); +#[cfg(feature = "std")] impl DynamicImage { /// Creates a dynamic image backed by a buffer of gray pixels. + #[cfg(feature = "std")] pub fn new_luma8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLuma8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray /// pixels with transparency. + #[cfg(feature = "std")] pub fn new_luma_a8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLumaA8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. + #[cfg(feature = "std")] pub fn new_rgb8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGBA pixels. + #[cfg(feature = "std")] pub fn new_rgba8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgba8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray pixels. + #[cfg(feature = "std")] pub fn new_luma16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLuma16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray /// pixels with transparency. + #[cfg(feature = "std")] pub fn new_luma_a16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLumaA16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. + #[cfg(feature = "std")] pub fn new_rgb16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGBA pixels. + #[cfg(feature = "std")] pub fn new_rgba16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgba16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. + #[cfg(feature = "std")] pub fn new_rgb32f(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb32F(ImageBuffer::new(w, h)) } @@ -175,16 +192,19 @@ impl DynamicImage { } /// Decodes an encoded image into a dynamic image. + #[cfg(feature = "std")] pub fn from_decoder<'a>(decoder: impl ImageDecoder<'a>) -> ImageResult { decoder_to_image(decoder) } /// Returns a copy of this image as an RGB image. + #[cfg(feature = "std")] pub fn to_rgb8(&self) -> RgbImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as an RGB image. + #[cfg(feature = "std")] pub fn to_rgb16(&self) -> Rgb16Image { dynamic_map!(*self, |ref p| p.convert()) } @@ -813,6 +833,7 @@ impl DynamicImage { /// /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. + #[cfg(feature = "std")] pub fn write_to>( &self, w: &mut W, @@ -861,6 +882,7 @@ impl DynamicImage { /// Saves the buffer to a file at the path specified. /// /// The image format is derived from the file extension. + #[cfg(feature = "std")] pub fn save(&self, path: Q) -> ImageResult<()> where Q: AsRef, @@ -873,6 +895,7 @@ impl DynamicImage { /// /// See [`save_buffer_with_format`](fn.save_buffer_with_format.html) for /// supported types. + #[cfg(feature = "std")] pub fn save_with_format(&self, path: Q, format: ImageFormat) -> ImageResult<()> where Q: AsRef, @@ -1023,6 +1046,7 @@ impl Default for DynamicImage { } /// Decodes an image and stores it into a dynamic image +#[cfg(feature = "std")] fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult { let (w, h) = decoder.dimensions(); let color_type = decoder.color_type(); @@ -1103,6 +1127,7 @@ fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult(path: P) -> ImageResult where P: AsRef, @@ -1118,6 +1143,7 @@ where /// content before its path or manually supplying the format. /// /// [`io::Reader`]: io/struct.Reader.html +#[cfg(feature = "std")] pub fn image_dimensions

(path: P) -> ImageResult<(u32, u32)> where P: AsRef, @@ -1209,6 +1235,7 @@ where /// Try [`io::Reader`] for more advanced uses. /// /// [`io::Reader`]: io/struct.Reader.html +#[cfg(feature = "std")] pub fn load_from_memory(buffer: &[u8]) -> ImageResult { let format = free_functions::guess_format(buffer)?; load_from_memory_with_format(buffer, format) @@ -1223,6 +1250,7 @@ pub fn load_from_memory(buffer: &[u8]) -> ImageResult { /// /// [`load`]: fn.load.html /// [`io::Reader`]: io/struct.Reader.html +#[cfg(feature = "std")] #[inline(always)] pub fn load_from_memory_with_format(buf: &[u8], format: ImageFormat) -> ImageResult { let b = io::Cursor::new(buf); @@ -1247,6 +1275,7 @@ mod bench { mod test { use alloc::vec::Vec; + #[cfg(feature = "std")] #[test] fn test_empty_file() { assert!(super::load_from_memory(b"").is_err()); @@ -1352,6 +1381,7 @@ mod test { let _ = super::DynamicImage::new_luma16(1, 1).into_bytes(); } + #[cfg(feature = "std")] #[test] fn issue_1705_can_turn_16bit_image_into_bytes() { let pixels = vec![65535u16; 64 * 64]; diff --git a/tests/reference_images.rs b/tests/reference_images.rs index 9106b63a8c..9d48ae830d 100644 --- a/tests/reference_images.rs +++ b/tests/reference_images.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use std::u32; use crc32fast::Hasher as Crc32; +#[cfg(feature = "std")] use image::DynamicImage; const BASE_PATH: [&str; 2] = [".", "tests"]; @@ -149,6 +150,7 @@ fn parse_crc(src: &str) -> Option { u32::from_str_radix(src, 16).ok() } +#[cfg(feature = "std")] #[test] fn check_references() { process_images(REFERENCE_DIR, Some("png"), |base, path, decoder| { @@ -159,7 +161,7 @@ fn check_references() { // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images // or because a specific decoder included via a feature. - Err(image::ImageError::Unsupported(_)) => return, + Err(image::ImageError::Unsupported { format, kind }) => return, Err(err) => panic!("{}", err), }; @@ -258,7 +260,7 @@ fn check_references() { // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images // or because a specific decoder included via a feature. - Err(image::ImageError::Unsupported(_)) => return, + Err(image::ImageError::Unsupported { format, kind }) => return, Err(err) => panic!("decoding of {:?} failed with: {}", img_path, err), }; } diff --git a/tests/regression.rs b/tests/regression.rs index 52611a897a..aa0516a927 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -31,6 +31,7 @@ where } } +#[cfg(feature = "std")] #[test] fn check_regressions() { process_images(REGRESSION_DIR, None, |path| { @@ -41,6 +42,7 @@ fn check_regressions() { /// Check that BMP files with large values could cause OOM issues are rejected. /// /// The images are postfixed with `bad_bmp` to not be loaded by the other test. +#[cfg(feature = "std")] #[test] fn bad_bmps() { let path: PathBuf = BASE_PATH @@ -60,6 +62,7 @@ fn bad_bmps() { } } +#[cfg(feature = "std")] #[test] fn bad_gif_oom() { let data = [ diff --git a/tests/truncate_images.rs b/tests/truncate_images.rs index dafe5ac557..bcf242cfbb 100644 --- a/tests/truncate_images.rs +++ b/tests/truncate_images.rs @@ -37,6 +37,7 @@ where } } +#[cfg(feature = "std")] fn truncate_images(decoder: &str) { process_images(IMAGE_DIR, Some(decoder), |path| { println!("{:?}", path); @@ -50,51 +51,61 @@ fn truncate_images(decoder: &str) { }) } +#[cfg(feature = "std")] #[test] fn truncate_tga() { truncate_images("tga") } +#[cfg(feature = "std")] #[test] fn truncate_tiff() { truncate_images("tiff") } +#[cfg(feature = "std")] #[test] fn truncate_png() { truncate_images("png") } +#[cfg(feature = "std")] #[test] fn truncate_gif() { truncate_images("gif") } +#[cfg(feature = "std")] #[test] fn truncate_bmp() { truncate_images("bmp") } +#[cfg(feature = "std")] #[test] fn truncate_ico() { truncate_images("ico") } +#[cfg(feature = "std")] #[test] fn truncate_jpg() { truncate_images("jpg") } +#[cfg(feature = "std")] #[test] fn truncate_hdr() { truncate_images("hdr"); } +#[cfg(feature = "std")] #[test] fn truncate_farbfeld() { truncate_images("farbfeld"); } +#[cfg(feature = "std")] #[test] fn truncate_exr() { truncate_images("exr"); From a97e4483cc5abceeaafbe13760f7e93020b37999 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 24 Feb 2023 15:47:27 +0100 Subject: [PATCH 05/16] WIP all tests pass `cargo test --no-default-features` BUT a lot of `#[cfg(feature = "std")]` etc [no ci] --- README.md | 37 +++++++++++++++++++++---------------- src/buffer.rs | 6 ++++++ src/flat.rs | 1 + src/imageops/mod.rs | 4 ++++ src/lib.rs | 14 +++++++++----- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e1a921e9d3..921cf7659b 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,21 @@ All image processing functions provided operate on types that implement the `Gen `image` provides implementations of common image format encoders and decoders. -| Format | Decoding | Encoding | -| ------ | -------- | -------- | -| PNG | All supported color types | Same as decoding | -| JPEG | Baseline and progressive | Baseline JPEG | -| GIF | Yes | Yes | -| BMP | Yes | Rgb8, Rgba8, Gray8, GrayA8 | -| ICO | Yes | Yes | -| TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 | -| WebP | Yes | Rgb8, Rgba8 \* | -| AVIF | Only 8-bit \*\* | Lossy | -| PNM | PBM, PGM, PPM, standard PAM | Yes | -| DDS | DXT1, DXT3, DXT5 | No | -| TGA | Yes | Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 | -| OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) | -| farbfeld | Yes | Yes | +| Format | Decoding | Encoding | +| -------- | ----------------------------------------- | --------------------------------------- | +| PNG | All supported color types | Same as decoding | +| JPEG | Baseline and progressive | Baseline JPEG | +| GIF | Yes | Yes | +| BMP | Yes | Rgb8, Rgba8, Gray8, GrayA8 | +| ICO | Yes | Yes | +| TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 | +| WebP | Yes | Rgb8, Rgba8 \* | +| AVIF | Only 8-bit \*\* | Lossy | +| PNM | PBM, PGM, PPM, standard PAM | Yes | +| DDS | DXT1, DXT3, DXT5 | No | +| TGA | Yes | Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 | +| OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) | +| farbfeld | Yes | Yes | - \* Requires the `webp-encoder` feature, uses the libwebp C library. - \*\* Requires the `avif-decoder` feature, uses the libdav1d C library. @@ -60,7 +60,7 @@ The most important methods for decoders are... All pixels are parameterised by their component type. ## Images -Individual pixels within images are indexed with (0,0) at the top left corner. +Individual pixels within images are indexed with (0,0) at the top left corner. ### The [`GenericImageView`](https://docs.rs/image/*/image/trait.GenericImageView.html) and [`GenericImage`](https://docs.rs/image/*/image/trait.GenericImage.html) Traits Traits that provide methods for inspecting (`GenericImageView`) and manipulating (`GenericImage`) images, parameterised over the image's pixel type. @@ -166,6 +166,7 @@ reader which offer some more control. ```rust,no_run use image::GenericImageView; +#[cfg(feature = "std")] fn main() { // Use the open function to load an image from a Path. // `open` returns a `DynamicImage` on success. @@ -180,6 +181,8 @@ fn main() { // Write the contents of this image to the Writer in PNG format. img.save("test.png").unwrap(); } +#[cfg(not(feature = "std"))] +fn main() {} ``` ### Generating Fractals @@ -225,6 +228,7 @@ fn main() { } // Save the image as “fractal.png”, the format is deduced from the path + #[cfg(feature = "std")] imgbuf.save("fractal.png").unwrap(); } ``` @@ -242,6 +246,7 @@ fn main() { let buffer: &[u8] = unimplemented!(); // Generate the image data // Save the buffer as "image.png" + #[cfg(feature = "std")] image::save_buffer("image.png", buffer, 800, 600, image::ColorType::Rgb8).unwrap() } ``` diff --git a/src/buffer.rs b/src/buffer.rs index 4727f5cbb8..9e93138726 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -630,6 +630,7 @@ where /// Overlays an image on top of a larger background raster. /// /// ```no_run +/// # #[cfg(feature = "std")] { /// use image::{GenericImage, GenericImageView, ImageBuffer, open}; /// /// let on_top = open("path/to/some.png").unwrap().into_rgb8(); @@ -642,15 +643,18 @@ where /// }); /// /// image::imageops::overlay(&mut img, &on_top, 128, 128); +/// } /// ``` /// /// Convert an RgbaImage to a GrayImage. /// /// ```no_run +/// # #[cfg(feature = "std")] { /// use image::{open, DynamicImage}; /// /// let rgba = open("path/to/some.png").unwrap().into_rgba8(); /// let gray = DynamicImage::ImageRgba8(rgba).into_luma8(); +/// } /// ``` #[derive(Debug, Hash, PartialEq, Eq)] pub struct ImageBuffer { @@ -1364,11 +1368,13 @@ where /// use image::GrayImage; /// /// let image_path = "examples/fractal.png"; + /// # #[cfg(feature = "std")] { /// let image = image::open(&image_path) /// .expect("Open file failed") /// .to_rgba8(); /// /// let gray_image: GrayImage = image.convert(); + /// } /// ``` fn convert(&self) -> ImageBuffer> { let mut buffer: ImageBuffer> = diff --git a/src/flat.rs b/src/flat.rs index eb966df917..93998b4e90 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -13,6 +13,7 @@ //! use image::flat::{FlatSamples, SampleLayout}; //! use image::imageops::thumbnail; //! +//! #[cfg(feature = "std")] //! #[no_mangle] //! pub extern "C" fn store_rgb8_compressed( //! data: *const u8, len: usize, diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index b222ac4af8..2e20fda0f2 100644 --- a/src/imageops/mod.rs +++ b/src/imageops/mod.rs @@ -242,10 +242,12 @@ where /// use image::{RgbaImage}; /// /// let mut img = RgbaImage::new(1920, 1080); +/// # #[cfg(feature = "std")] { /// let tile = image::open("tile.png").unwrap(); /// /// image::imageops::tile(&mut img, &tile); /// img.save("tiled_wallpaper.png").unwrap(); +/// } /// ``` pub fn tile(bottom: &mut I, top: &J) where @@ -272,6 +274,7 @@ where /// let end = Rgba::from_slice(&[255, 255, 255, 255]); /// /// image::imageops::vertical_gradient(&mut img, start, end); +/// #[cfg(feature = "std")] /// img.save("vertical_gradient.png").unwrap(); pub fn vertical_gradient(img: &mut I, start: &P, stop: &P) where @@ -305,6 +308,7 @@ where /// let end = Rgba::from_slice(&[255, 255, 255, 255]); /// /// image::imageops::horizontal_gradient(&mut img, start, end); +/// #[cfg(feature = "std")] /// img.save("horizontal_gradient.png").unwrap(); pub fn horizontal_gradient(img: &mut I, start: &P, stop: &P) where diff --git a/src/lib.rs b/src/lib.rs index 9960a9bb76..1fc17e29f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,14 +17,17 @@ //! //! ```rust,no_run //! use std::io::Cursor; -//! use image::io::Reader as ImageReader; +//! #[cfg(feature = "std")] //! # fn main() -> Result<(), image::ImageError> { //! # let bytes = vec![0u8]; //! -//! let img = ImageReader::open("myimage.png")?.decode()?; -//! let img2 = ImageReader::new(Cursor::new(bytes)).with_guessed_format()?.decode()?; +//! let img = image::io::Reader::ImageReader::open("myimage.png")?.decode()?; +//! let img2 = image::io::Reader::ImageReader::new(Cursor::new(bytes)).with_guessed_format()?.decode()?; //! # Ok(()) //! # } +//! +//! #[cfg(not(feature = "std"))] +//! fn main() {} //! ``` //! //! And save them using [`save`] or [`write_to`] methods: @@ -32,8 +35,9 @@ //! ```rust,no_run //! # use std::io::{Write, Cursor}; //! # use image::ImageOutputFormat; +//! #[cfg(all(feature = "png", feature = "std"))] //! # use image::DynamicImage; -//! # #[cfg(feature = "png")] +//! # #[cfg(all(feature = "png", feature = "std"))] //! # fn main() -> Result<(), image::ImageError> { //! # let img: DynamicImage = unimplemented!(); //! # let img2: DynamicImage = unimplemented!(); @@ -43,7 +47,7 @@ //! img2.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png)?; //! # Ok(()) //! # } -//! # #[cfg(not(feature = "png"))] fn main() {} +//! # #[cfg(not(all(feature = "png", feature = "std")))] fn main() {} //! ``` //! //! With default features, the crate includes support for [many common image formats](codecs/index.html#supported-formats). From 58e7cef7616c0e407f4080be99c5751f3c26b250 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 24 Feb 2023 16:15:36 +0100 Subject: [PATCH 06/16] WIP no-std-check - `cargo test --no-default-features --features=alloc` -> OK - `cargo +nightly-2022-10-22 no-std-check --no-default-features` -> FAIL --- Cargo.toml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f60991ede..b3bdd8c214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,11 +31,11 @@ path = "./src/lib.rs" [dependencies] bytemuck = { version = "1.7.0", features = ["extern_crate_alloc"] } # includes cast_vec -byteorder = "1.3.2" +byteorder = { version = "1.3.2", default-features = false } num-rational = { version = "0.4", default-features = false } -num-traits = "0.2.0" +num-traits = { version = "0.2", default-features = false } snafu = { version = "0.7", default-features = false } -gif = { version = "0.12", optional = true } +gif = { version = "0.12", optional = true, default-features = false } jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, optional = true } png = { version = "0.17.6", optional = true } scoped_threadpool = { version = "0.1", optional = true } @@ -45,7 +45,9 @@ rgb = { version = "0.8.25", optional = true } mp4parse = { version = "0.12.0", optional = true } dav1d = { version = "0.6.0", optional = true } dcv-color-primitives = { version = "0.4.0", optional = true } -color_quant = "1.1" +# TODO(interstellar) no_std not supported on crate.io; need a fork/patch +# NOTE: also an optional dependency of "gif" +color_quant = { version = "1.1", git = "https://github.com/Interstellar-Network/color_quant.git", branch = "sgx-nostd-compat", default-features = false, optional = true } exr = { version = "1.5.0", optional = true } qoi = { version = "0.4", optional = true } libwebp = { package = "webp", version = "0.2.2", default-features = false, optional = true } @@ -64,7 +66,7 @@ jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, # TODO: Add "avif" to this list while preparing for 0.24.0 default = ["std", "gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"] -std = ["snafu/std"] +std = ["snafu/std", "num-traits/std", "byteorder/std", "gif/default", "color_quant/std"] ico = ["bmp", "png"] pnm = [] tga = [] @@ -96,6 +98,8 @@ avif-decoder = ["mp4parse", "dcv-color-primitives", "dav1d"] # Requires rustc nightly for feature test. benchmarks = [] +alloc = ["num-traits/libm", "color_quant/alloc"] + [[bench]] path = "benches/decode.rs" name = "decode" From b381dc97c5d70351484bb03011cc54b4e5222de0 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 24 Feb 2023 16:20:59 +0100 Subject: [PATCH 07/16] WIP no-std-check OK - `cargo +nightly-2022-10-22 no-std-check --no-default-features --features=alloc` OK - `cargo test --no-default-features --features=alloc` OK --- src/color.rs | 3 +++ src/imageops/colorops.rs | 3 +++ src/imageops/sample.rs | 3 +++ src/math/utils.rs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/color.rs b/src/color.rs index b248c2c47d..1bde9e3869 100644 --- a/src/color.rs +++ b/src/color.rs @@ -4,6 +4,9 @@ use num_traits::{NumCast, ToPrimitive, Zero}; use crate::traits::{Enlargeable, Pixel, Primitive}; +#[cfg(not(feature = "std"))] +use num_traits::float::FloatCore; + /// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[non_exhaustive] diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index 3055587de7..5af3f03d7d 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -3,6 +3,9 @@ use alloc::vec::Vec; use core::f64::consts::PI; use num_traits::NumCast; +#[cfg(not(feature = "std"))] +use num_traits::Float; + use crate::color::{FromColor, IntoColor, Luma, LumaA, Rgba}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::{Pixel, Primitive}; diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index a5c0d9f169..d1f9ed2b71 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -8,6 +8,9 @@ use core::f32; use num_traits::{NumCast, ToPrimitive, Zero}; +#[cfg(not(feature = "std"))] +use num_traits::Float; + use crate::image::{GenericImage, GenericImageView}; use crate::traits::{Enlargeable, Pixel, Primitive}; use crate::utils::clamp; diff --git a/src/math/utils.rs b/src/math/utils.rs index 69a9633745..5744697807 100644 --- a/src/math/utils.rs +++ b/src/math/utils.rs @@ -2,6 +2,9 @@ use core::cmp::max; +#[cfg(not(feature = "std"))] +use num_traits::Float; + /// Calculates the width and height an image should be resized to. /// This preserves aspect ratio, and based on the `fill` parameter /// will either fill the dimensions to fit inside the smaller constraint From 49e877fea02ef3a375f33a00549f654aad33669a Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Thu, 2 Mar 2023 12:45:04 +0100 Subject: [PATCH 08/16] small fixes - bump `color_quant`: feature `alloc` renamed `num-traits` cf https://github.com/image-rs/color_quant/pull/21 - `ImageDecoder`: remove `Reader` when no_std b/c cleaner --- .github/workflows/rust.yml | 1 + Cargo.toml | 2 +- src/image.rs | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 996bafe2d3..734a2e31ea 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,6 +2,7 @@ name: Rust CI on: push: + # TODO(interstellar) revert # branches: [ master, next ] # pull_request: # branches: [ master, next ] diff --git a/Cargo.toml b/Cargo.toml index b3bdd8c214..76042e5860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,7 @@ avif-decoder = ["mp4parse", "dcv-color-primitives", "dav1d"] # Requires rustc nightly for feature test. benchmarks = [] -alloc = ["num-traits/libm", "color_quant/alloc"] +alloc = ["num-traits/libm", "color_quant/num-traits"] [[bench]] path = "benches/decode.rs" diff --git a/src/image.rs b/src/image.rs index b520e2ca26..664e76605a 100644 --- a/src/image.rs +++ b/src/image.rs @@ -651,8 +651,6 @@ pub trait ImageDecoder<'a>: Sized { /// The type of reader produced by `into_reader`. #[cfg(feature = "std")] type Reader: std::io::Read + 'a; - #[cfg(not(feature = "std"))] - type Reader: 'a; /// Returns a tuple containing the width and height of the image fn dimensions(&self) -> (u32, u32); From 124ce5afcde9d9f88ebfbd98933712937609d031 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Thu, 2 Mar 2023 13:37:38 +0100 Subject: [PATCH 09/16] DynamicImage: remove most `#[cfg(feature = "std")]` --- src/dynimage.rs | 13 ------------- src/lib.rs | 2 -- 2 files changed, 15 deletions(-) diff --git a/src/dynimage.rs b/src/dynimage.rs index 940f0b75f2..b9cfdf2d65 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -60,7 +60,6 @@ use crate::io::free_functions; /// would hardly be feasible as a simple enum, due to the sheer number of combinations of channel /// kinds, channel order, and bit depth. Rather, this type provides an opinionated selection with /// normalized channel order which can store common pixel values without loss. -#[cfg(feature = "std")] #[derive(Clone, Debug, PartialEq)] #[non_exhaustive] pub enum DynamicImage { @@ -128,60 +127,50 @@ macro_rules! dynamic_map( ); ); -#[cfg(feature = "std")] impl DynamicImage { /// Creates a dynamic image backed by a buffer of gray pixels. - #[cfg(feature = "std")] pub fn new_luma8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLuma8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray /// pixels with transparency. - #[cfg(feature = "std")] pub fn new_luma_a8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLumaA8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. - #[cfg(feature = "std")] pub fn new_rgb8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGBA pixels. - #[cfg(feature = "std")] pub fn new_rgba8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgba8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray pixels. - #[cfg(feature = "std")] pub fn new_luma16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLuma16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray /// pixels with transparency. - #[cfg(feature = "std")] pub fn new_luma_a16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLumaA16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. - #[cfg(feature = "std")] pub fn new_rgb16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGBA pixels. - #[cfg(feature = "std")] pub fn new_rgba16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgba16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. - #[cfg(feature = "std")] pub fn new_rgb32f(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb32F(ImageBuffer::new(w, h)) } @@ -198,13 +187,11 @@ impl DynamicImage { } /// Returns a copy of this image as an RGB image. - #[cfg(feature = "std")] pub fn to_rgb8(&self) -> RgbImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as an RGB image. - #[cfg(feature = "std")] pub fn to_rgb16(&self) -> Rgb16Image { dynamic_map!(*self, |ref p| p.convert()) } diff --git a/src/lib.rs b/src/lib.rs index 1fc17e29f0..ef2bc4a358 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,7 +159,6 @@ pub use crate::dynimage::{ #[cfg(feature = "std")] pub use crate::io::free_functions::{guess_format, load}; -#[cfg(feature = "std")] pub use crate::dynimage::DynamicImage; pub use crate::animation::{Delay, Frame, Frames}; @@ -269,7 +268,6 @@ mod animation; #[path = "buffer.rs"] mod buffer_; mod color; -#[cfg(feature = "std")] mod dynimage; mod image; mod traits; From 1cb86644a4352cb1a1b162bd6144e70404346561 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 3 Mar 2023 11:56:33 +0100 Subject: [PATCH 10/16] std-gate `ImageDecoder` and `ImageDecoderRect` --- src/dynimage.rs | 6 +++--- src/image.rs | 9 ++++----- src/lib.rs | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/dynimage.rs b/src/dynimage.rs index b9cfdf2d65..82c3e44cb6 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -25,15 +25,15 @@ use crate::error::{ImageError, ImageResult, ParameterErrorKind}; // FIXME: These imports exist because we don't support all of our own color types. use crate::error::{ImageFormatHint, UnsupportedErrorKind}; use crate::flat::FlatSamples; -use crate::image::{ - GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat, ImageOutputFormat, -}; +use crate::image::{GenericImage, GenericImageView, ImageEncoder, ImageFormat, ImageOutputFormat}; use crate::imageops; use crate::math::resize_dimensions; use crate::traits::Pixel; use crate::{image, Luma, LumaA}; use crate::{Rgb32FImage, Rgba32FImage}; +#[cfg(feature = "std")] +use crate::image::ImageDecoder; #[cfg(feature = "std")] use crate::io::free_functions; diff --git a/src/image.rs b/src/image.rs index 664e76605a..6ed64d5385 100644 --- a/src/image.rs +++ b/src/image.rs @@ -647,9 +647,9 @@ impl Progress { } /// The trait that all decoders implement +#[cfg(feature = "std")] pub trait ImageDecoder<'a>: Sized { /// The type of reader produced by `into_reader`. - #[cfg(feature = "std")] type Reader: std::io::Read + 'a; /// Returns a tuple containing the width and height of the image @@ -771,6 +771,7 @@ pub trait ImageDecoder<'a>: Sized { } /// Specialized image decoding not be supported by all formats +#[cfg(feature = "std")] pub trait ImageDecoderRect<'a>: ImageDecoder<'a> + Sized { /// Decode a rectangular section of the image; see [`read_rect_with_progress()`](#fn.read_rect_with_progress). fn read_rect( @@ -1324,9 +1325,7 @@ where #[cfg(test)] mod tests { - use super::{ - ColorType, GenericImage, GenericImageView, ImageDecoder, ImageFormat, ImageResult, - }; + use super::{ColorType, GenericImage, GenericImageView, ImageFormat, ImageResult}; use crate::color::Rgba; use crate::math::Rect; use crate::{GrayImage, ImageBuffer}; @@ -1337,7 +1336,7 @@ mod tests { use std::path::Path; #[cfg(feature = "std")] - use super::load_rect; + use super::{load_rect, ImageDecoder}; #[test] #[allow(deprecated)] diff --git a/src/lib.rs b/src/lib.rs index ef2bc4a358..77490302cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,8 +123,6 @@ pub use crate::image::{ AnimationDecoder, GenericImage, GenericImageView, - ImageDecoder, - ImageDecoderRect, ImageEncoder, ImageFormat, ImageOutputFormat, @@ -157,6 +155,8 @@ pub use crate::dynimage::{ save_buffer_with_format, write_buffer_with_format, }; #[cfg(feature = "std")] +pub use crate::image::{ImageDecoder, ImageDecoderRect}; +#[cfg(feature = "std")] pub use crate::io::free_functions::{guess_format, load}; pub use crate::dynimage::DynamicImage; From f4c58d815e4161a4fcb4837d32c70d769ee619f9 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 3 Mar 2023 12:43:00 +0100 Subject: [PATCH 11/16] error.rs: reverse almost all changes - remove Snafu - require feature `error_in_core` for `no_std` [ci skip] --- Cargo.toml | 3 +- src/codecs/png.rs | 29 +-- src/codecs/tiff.rs | 74 ++++--- src/codecs/webp/lossless.rs | 4 +- src/codecs/webp/vp8.rs | 34 +-- src/dynimage.rs | 20 +- src/error.rs | 398 +++++++++++++++++++++++++++++++----- src/flat.rs | 116 ++++++----- src/image.rs | 36 ++-- src/imageops/affine.rs | 34 ++- src/io/mod.rs | 26 ++- src/lib.rs | 1 + 12 files changed, 558 insertions(+), 217 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76042e5860..f70cc50817 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ bytemuck = { version = "1.7.0", features = ["extern_crate_alloc"] } # includes c byteorder = { version = "1.3.2", default-features = false } num-rational = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false } -snafu = { version = "0.7", default-features = false } gif = { version = "0.12", optional = true, default-features = false } jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, optional = true } png = { version = "0.17.6", optional = true } @@ -66,7 +65,7 @@ jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, # TODO: Add "avif" to this list while preparing for 0.24.0 default = ["std", "gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"] -std = ["snafu/std", "num-traits/std", "byteorder/std", "gif/default", "color_quant/std"] +std = ["num-traits/std", "byteorder/std", "gif/default", "color_quant/std"] ico = ["bmp", "png"] pnm = [] tga = [] diff --git a/src/codecs/png.rs b/src/codecs/png.rs index 7e086ac479..ae3032bf78 100644 --- a/src/codecs/png.rs +++ b/src/codecs/png.rs @@ -220,10 +220,10 @@ impl PngDecoder { } fn unsupported_color(ect: ExtendedColorType) -> ImageError { - ImageError::Unsupported { - format: ImageFormat::Png.into(), - kind: UnsupportedErrorKind::Color(ect), - } + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormat::Png.into(), + UnsupportedErrorKind::Color(ect), + )) } impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { @@ -668,22 +668,21 @@ impl ImageError { fn from_png(err: png::DecodingError) -> ImageError { use png::DecodingError::*; match err { - #[cfg(features = "std")] IoError(err) => ImageError::IoError(err), // The input image was not a valid PNG. - err @ Format(_) => ImageError::Decoding { - format: ImageFormat::Png.into(), - }, + err @ Format(_) => { + ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err)) + } // Other is used when: // - The decoder is polled for more animation frames despite being done (or not being animated // in the first place). // - The output buffer does not have the required size. - err @ Parameter(_) => ImageError::Parameter { - kind: ParameterErrorKind::Generic(err.to_string()), - }, - LimitsExceeded => ImageError::Limits { - kind: LimitErrorKind::InsufficientMemory, - }, + err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic(err.to_string()), + )), + LimitsExceeded => { + ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) + } } } } @@ -750,6 +749,8 @@ mod tests { #[test] fn underlying_error() { + use std::error::Error; + let mut not_png = std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png") .unwrap(); diff --git a/src/codecs/tiff.rs b/src/codecs/tiff.rs index ba78a6250d..f3cc027a01 100644 --- a/src/codecs/tiff.rs +++ b/src/codecs/tiff.rs @@ -84,24 +84,26 @@ where fn check_sample_format(sample_format: u16) -> Result<(), ImageError> { match tiff::tags::SampleFormat::from_u16(sample_format) { Some(tiff::tags::SampleFormat::Uint) => Ok(()), - Some(other) => Err(ImageError::Unsupported { - format: ImageFormat::Tiff.into(), - kind: UnsupportedErrorKind::GenericFeature(format!( - "Unhandled TIFF sample format {:?}", - other - )), - }), - None => Err(ImageError::Decoding { - format: ImageFormat::Tiff.into(), - }), + Some(other) => Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Tiff.into(), + UnsupportedErrorKind::GenericFeature(format!( + "Unhandled TIFF sample format {:?}", + other + )), + ), + )), + None => Err(ImageError::Decoding(DecodingError::from_format_hint( + ImageFormat::Tiff.into(), + ))), } } fn err_unknown_color_type(value: u8) -> ImageError { - ImageError::Unsupported { - format: ImageFormat::Tiff.into(), - kind: UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)), - } + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormat::Tiff.into(), + UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)), + )) } impl ImageError { @@ -110,16 +112,18 @@ impl ImageError { tiff::TiffError::IoError(err) => ImageError::IoError(err), err @ tiff::TiffError::FormatError(_) | err @ tiff::TiffError::IntSizeError - | err @ tiff::TiffError::UsageError(_) => ImageError::Decoding { - format: ImageFormat::Tiff.into(), - }, - tiff::TiffError::UnsupportedError(desc) => ImageError::Unsupported { - format: ImageFormat::Tiff.into(), - kind: UnsupportedErrorKind::GenericFeature(desc.to_string()), - }, - tiff::TiffError::LimitsExceeded => ImageError::Limits { - kind: LimitErrorKind::InsufficientMemory, - }, + | err @ tiff::TiffError::UsageError(_) => { + ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err)) + } + tiff::TiffError::UnsupportedError(desc) => { + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormat::Tiff.into(), + UnsupportedErrorKind::GenericFeature(desc.to_string()), + )) + } + tiff::TiffError::LimitsExceeded => { + ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) + } } } @@ -128,16 +132,18 @@ impl ImageError { tiff::TiffError::IoError(err) => ImageError::IoError(err), err @ tiff::TiffError::FormatError(_) | err @ tiff::TiffError::IntSizeError - | err @ tiff::TiffError::UsageError(_) => ImageError::Encoding { - format: ImageFormat::Tiff.into(), - }, - tiff::TiffError::UnsupportedError(desc) => ImageError::Unsupported { - format: ImageFormat::Tiff.into(), - kind: UnsupportedErrorKind::GenericFeature(desc.to_string()), - }, - tiff::TiffError::LimitsExceeded => ImageError::Limits { - kind: LimitErrorKind::InsufficientMemory, - }, + | err @ tiff::TiffError::UsageError(_) => { + ImageError::Encoding(EncodingError::new(ImageFormat::Tiff.into(), err)) + } + tiff::TiffError::UnsupportedError(desc) => { + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormat::Tiff.into(), + UnsupportedErrorKind::GenericFeature(desc.to_string()), + )) + } + tiff::TiffError::LimitsExceeded => { + ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) + } } } } diff --git a/src/codecs/webp/lossless.rs b/src/codecs/webp/lossless.rs index df1780b818..a14e63a864 100644 --- a/src/codecs/webp/lossless.rs +++ b/src/codecs/webp/lossless.rs @@ -105,9 +105,7 @@ impl fmt::Display for DecoderError { impl From for ImageError { fn from(e: DecoderError) -> ImageError { - ImageError::Decoding { - format: ImageFormat::WebP.into(), - } + ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) } } diff --git a/src/codecs/webp/vp8.rs b/src/codecs/webp/vp8.rs index a7519a83c1..a161465a6d 100644 --- a/src/codecs/webp/vp8.rs +++ b/src/codecs/webp/vp8.rs @@ -718,9 +718,7 @@ impl fmt::Display for DecoderError { impl From for ImageError { fn from(e: DecoderError) -> ImageError { - ImageError::Decoding { - format: ImageFormat::WebP.into(), - } + ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) } } @@ -1319,10 +1317,12 @@ impl Vp8Decoder { if !self.frame.keyframe { // 9.7 refresh golden frame and altref frame // FIXME: support this? - return Err(ImageError::Unsupported { - format: ImageFormat::WebP.into(), - kind: UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), - }); + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::WebP.into(), + UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), + ), + )); } else { // Refresh entropy probs ????? let _ = self.b.read_literal(1); @@ -1342,10 +1342,12 @@ impl Vp8Decoder { self.prob_intra = 0; // FIXME: support this? - return Err(ImageError::Unsupported { - format: ImageFormat::WebP.into(), - kind: UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), - }); + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::WebP.into(), + UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), + ), + )); } else { // Reset motion vectors } @@ -1376,10 +1378,12 @@ impl Vp8Decoder { }; if inter_predicted { - return Err(ImageError::Unsupported { - format: ImageFormat::WebP.into(), - kind: UnsupportedErrorKind::GenericFeature("VP8 inter-prediction".to_owned()), - }); + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::WebP.into(), + UnsupportedErrorKind::GenericFeature("VP8 inter-prediction".to_owned()), + ), + )); } if self.frame.keyframe { diff --git a/src/dynimage.rs b/src/dynimage.rs index 82c3e44cb6..1ab93b400b 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -21,9 +21,9 @@ use crate::buffer_::{ Rgb16Image, RgbImage, Rgba16Image, RgbaImage, }; use crate::color::{self, IntoColor}; -use crate::error::{ImageError, ImageResult, ParameterErrorKind}; +use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; // FIXME: These imports exist because we don't support all of our own color types. -use crate::error::{ImageFormatHint, UnsupportedErrorKind}; +use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::flat::FlatSamples; use crate::image::{GenericImage, GenericImageView, ImageEncoder, ImageFormat, ImageOutputFormat}; use crate::imageops; @@ -1092,18 +1092,20 @@ fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult { - return Err(ImageError::Unsupported { - format: ImageFormatHint::Unknown, - kind: UnsupportedErrorKind::Color(color_type.into()), - }) + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormatHint::Unknown, + UnsupportedErrorKind::Color(color_type.into()), + ), + )) } }; match image { Some(image) => Ok(image), - None => Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }), + None => Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))), } } diff --git a/src/error.rs b/src/error.rs index 0a9e07b846..fdd08b8dc6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,12 @@ use alloc::boxed::Box; use alloc::string::String; use core::fmt; -use snafu::{prelude::*, Error}; + +// TODO(interstellar) error_in_core; and re-add feature-gate +#[cfg(feature = "alloc")] +use core::error::Error; +#[cfg(feature = "std")] +use std::error::Error; #[cfg(feature = "std")] use std::io; @@ -27,21 +32,14 @@ use crate::image::ImageFormat; /// /// This high level enum allows, by variant matching, a rough separation of concerns between /// underlying IO, the caller, format specifications, and the `image` implementation. -#[derive(Snafu, Debug)] +#[derive(Debug)] pub enum ImageError { /// An error was encountered while decoding. /// /// This means that the input data did not conform to the specification of some image format, /// or that no format could be determined, or that it did not match format specific /// requirements set by the caller. - /// - /// An error was encountered while decoding an image. - /// - /// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its - /// documentation for more information. - /// - /// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding - Decoding { format: ImageFormatHint }, + Decoding(DecodingError), /// An error was encountered while encoding. /// @@ -49,40 +47,19 @@ pub enum ImageError { /// specification has no representation for its color space or because a necessary conversion /// is ambiguous. In some cases it might also happen that the dimensions can not be used with /// the format. - /// - /// An error was encountered while encoding an image. - /// - /// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its - /// documentation for more information. - /// - /// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding - Encoding { format: ImageFormatHint }, + Encoding(EncodingError), /// An error was encountered in input arguments. /// /// This is a catch-all case for strictly internal operations such as scaling, conversions, /// etc. that involve no external format specifications. - /// - /// An error was encountered in inputs arguments. - /// - /// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its - /// documentation for more information. - /// - /// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter - Parameter { kind: ParameterErrorKind }, + Parameter(ParameterError), /// Completing the operation would have required more resources than allowed. /// /// Errors of this type are limits set by the user or environment, *not* inherent in a specific /// format or operation that was executed. - /// - /// Completing the operation would have required more resources than allowed. - /// - /// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its - /// documentation for more information. - /// - /// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits - Limits { kind: LimitErrorKind }, + Limits(LimitError), /// An operation can not be completed by the chosen abstraction. /// @@ -90,20 +67,22 @@ pub enum ImageError { /// * it requires a disabled feature, /// * the implementation does not yet exist, or /// * no abstraction for a lower level could be found. - /// - /// The implementation for an operation was not provided. - /// - /// See the variant [`Unsupported`] for more documentation. - /// - /// [`Unsupported`]: enum.ImageError.html#variant.Unsupported - Unsupported { - format: ImageFormatHint, - kind: UnsupportedErrorKind, - }, + Unsupported(UnsupportedError), /// An error occurred while interacting with the environment. #[cfg(feature = "std")] - IoError { err: io::Error }, + IoError(io::Error), +} + +/// The implementation for an operation was not provided. +/// +/// See the variant [`Unsupported`] for more documentation. +/// +/// [`Unsupported`]: enum.ImageError.html#variant.Unsupported +#[derive(Debug)] +pub struct UnsupportedError { + format: ImageFormatHint, + kind: UnsupportedErrorKind, } /// Details what feature is not supported. @@ -119,6 +98,30 @@ pub enum UnsupportedErrorKind { GenericFeature(String), } +/// An error was encountered while encoding an image. +/// +/// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding +#[derive(Debug)] +pub struct EncodingError { + format: ImageFormatHint, + underlying: Option>, +} + +/// An error was encountered in inputs arguments. +/// +/// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter +#[derive(Debug)] +pub struct ParameterError { + kind: ParameterErrorKind, + underlying: Option>, +} + /// Details how a parameter is malformed. #[derive(Clone, Debug, Hash, PartialEq)] #[non_exhaustive] @@ -134,6 +137,30 @@ pub enum ParameterErrorKind { NoMoreData, } +/// An error was encountered while decoding an image. +/// +/// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding +#[derive(Debug)] +pub struct DecodingError { + format: ImageFormatHint, + underlying: Option>, +} + +/// Completing the operation would have required more resources than allowed. +/// +/// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits +#[derive(Debug)] +pub struct LimitError { + kind: LimitErrorKind, + // do we need an underlying error? +} + /// Indicates the limit that prevented an operation from completing. /// /// Note that this enumeration is not exhaustive and may in the future be extended to provide more @@ -173,10 +200,107 @@ pub enum ImageFormatHint { Unknown, } +impl UnsupportedError { + /// Create an `UnsupportedError` for an image with details on the unsupported feature. + /// + /// If the operation was not connected to a particular image format then the hint may be + /// `Unknown`. + pub fn from_format_and_kind(format: ImageFormatHint, kind: UnsupportedErrorKind) -> Self { + UnsupportedError { format, kind } + } + + /// Returns the corresponding `UnsupportedErrorKind` of the error. + pub fn kind(&self) -> UnsupportedErrorKind { + self.kind.clone() + } + + /// Returns the image format associated with this error. + pub fn format_hint(&self) -> ImageFormatHint { + self.format.clone() + } +} + +impl DecodingError { + /// Create a `DecodingError` that stems from an arbitrary error of an underlying decoder. + pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { + DecodingError { + format, + underlying: Some(err.into()), + } + } + + /// Create a `DecodingError` for an image format. + /// + /// The error will not contain any further information but is very easy to create. + pub fn from_format_hint(format: ImageFormatHint) -> Self { + DecodingError { + format, + underlying: None, + } + } + + /// Returns the image format associated with this error. + pub fn format_hint(&self) -> ImageFormatHint { + self.format.clone() + } +} + +impl EncodingError { + /// Create an `EncodingError` that stems from an arbitrary error of an underlying encoder. + pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { + EncodingError { + format, + underlying: Some(err.into()), + } + } + + /// Create an `EncodingError` for an image format. + /// + /// The error will not contain any further information but is very easy to create. + pub fn from_format_hint(format: ImageFormatHint) -> Self { + EncodingError { + format, + underlying: None, + } + } + + /// Return the image format associated with this error. + pub fn format_hint(&self) -> ImageFormatHint { + self.format.clone() + } +} + +impl ParameterError { + /// Construct a `ParameterError` directly from a corresponding kind. + pub fn from_kind(kind: ParameterErrorKind) -> Self { + ParameterError { + kind, + underlying: None, + } + } + + /// Returns the corresponding `ParameterErrorKind` of the error. + pub fn kind(&self) -> ParameterErrorKind { + self.kind.clone() + } +} + +impl LimitError { + /// Construct a generic `LimitError` directly from a corresponding kind. + pub fn from_kind(kind: LimitErrorKind) -> Self { + LimitError { kind } + } + + /// Returns the corresponding `LimitErrorKind` of the error. + pub fn kind(&self) -> LimitErrorKind { + self.kind.clone() + } +} + #[cfg(feature = "std")] impl From for ImageError { fn from(err: io::Error) -> ImageError { - ImageError::IoError { err } + ImageError::IoError(err) } } @@ -196,9 +320,191 @@ impl From<&'_ std::path::Path> for ImageFormatHint { } } +impl From for UnsupportedError { + fn from(hint: ImageFormatHint) -> Self { + UnsupportedError { + format: hint.clone(), + kind: UnsupportedErrorKind::Format(hint), + } + } +} + /// Result of an image decoding/encoding process pub type ImageResult = Result; +impl fmt::Display for ImageError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + #[cfg(feature = "std")] + ImageError::IoError(err) => err.fmt(fmt), + ImageError::Decoding(err) => err.fmt(fmt), + ImageError::Encoding(err) => err.fmt(fmt), + ImageError::Parameter(err) => err.fmt(fmt), + ImageError::Limits(err) => err.fmt(fmt), + ImageError::Unsupported(err) => err.fmt(fmt), + } + } +} + +impl Error for ImageError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + #[cfg(feature = "std")] + ImageError::IoError(err) => err.source(), + ImageError::Decoding(err) => err.source(), + ImageError::Encoding(err) => err.source(), + ImageError::Parameter(err) => err.source(), + ImageError::Limits(err) => err.source(), + ImageError::Unsupported(err) => err.source(), + } + } +} + +impl fmt::Display for UnsupportedError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.kind { + UnsupportedErrorKind::Format(ImageFormatHint::Unknown) => { + write!(fmt, "The image format could not be determined",) + } + #[cfg(feature = "std")] + UnsupportedErrorKind::Format(format @ ImageFormatHint::PathExtension(_)) => write!( + fmt, + "The file extension {} was not recognized as an image format", + format, + ), + UnsupportedErrorKind::Format(format) => { + write!(fmt, "The image format {} is not supported", format,) + } + UnsupportedErrorKind::Color(color) => write!( + fmt, + "The decoder for {} does not support the color type `{:?}`", + self.format, color, + ), + UnsupportedErrorKind::GenericFeature(message) => match &self.format { + ImageFormatHint::Unknown => write!( + fmt, + "The decoder does not support the format feature {}", + message, + ), + other => write!( + fmt, + "The decoder for {} does not support the format features {}", + other, message, + ), + }, + } + } +} + +impl Error for UnsupportedError {} + +impl fmt::Display for ParameterError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.kind { + ParameterErrorKind::DimensionMismatch => write!( + fmt, + "The Image's dimensions are either too \ + small or too large" + ), + ParameterErrorKind::FailedAlready => write!( + fmt, + "The end the image stream has been reached due to a previous error" + ), + ParameterErrorKind::Generic(message) => { + write!(fmt, "The parameter is malformed: {}", message,) + } + ParameterErrorKind::NoMoreData => write!(fmt, "The end of the image has been reached",), + }?; + + if let Some(underlying) = &self.underlying { + write!(fmt, "\n{}", underlying)?; + } + + Ok(()) + } +} + +impl Error for ParameterError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.underlying { + None => None, + Some(source) => Some(&**source), + } + } +} + +impl fmt::Display for EncodingError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.underlying { + Some(underlying) => write!( + fmt, + "Format error encoding {}:\n{}", + self.format, underlying, + ), + None => write!(fmt, "Format error encoding {}", self.format,), + } + } +} + +impl Error for EncodingError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.underlying { + None => None, + Some(source) => Some(&**source), + } + } +} + +impl fmt::Display for DecodingError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.underlying { + None => match self.format { + ImageFormatHint::Unknown => write!(fmt, "Format error"), + _ => write!(fmt, "Format error decoding {}", self.format), + }, + Some(underlying) => { + write!(fmt, "Format error decoding {}: {}", self.format, underlying) + } + } + } +} + +impl Error for DecodingError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.underlying { + None => None, + Some(source) => Some(&**source), + } + } +} + +impl fmt::Display for LimitError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self.kind { + LimitErrorKind::InsufficientMemory => write!(fmt, "Insufficient memory"), + LimitErrorKind::DimensionError => write!(fmt, "Image is too large"), + LimitErrorKind::Unsupported { .. } => { + write!(fmt, "The following strict limits are specified but not supported by the opertation: ")?; + Ok(()) + } + } + } +} + +impl Error for LimitError {} + +impl fmt::Display for ImageFormatHint { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + ImageFormatHint::Exact(format) => write!(fmt, "{:?}", format), + ImageFormatHint::Name(name) => write!(fmt, "`{}`", name), + #[cfg(feature = "std")] + ImageFormatHint::PathExtension(ext) => write!(fmt, "`.{:?}`", ext), + ImageFormatHint::Unknown => write!(fmt, "`Unknown`"), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/flat.rs b/src/flat.rs index 93998b4e90..1602e54489 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -42,16 +42,23 @@ //! } //! ``` //! -//! use alloc::vec::Vec; +use core::cmp; +use core::fmt; use core::marker::PhantomData; use core::ops::{Deref, Index, IndexMut}; -use core::{cmp, fmt}; use num_traits::Zero; -use snafu::prelude::*; + +#[cfg(feature = "alloc")] +use core::error; +#[cfg(feature = "std")] +use std::error; use crate::color::ColorType; -use crate::error::{ImageError, ImageFormatHint, ParameterErrorKind, UnsupportedErrorKind}; +use crate::error::{ + DecodingError, ImageError, ImageFormatHint, ParameterError, ParameterErrorKind, + UnsupportedError, UnsupportedErrorKind, +}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::{Pixel, PixelWithColorType}; use crate::ImageBuffer; @@ -597,9 +604,7 @@ impl FlatSamples { Buffer: AsRef<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor { - underlying: P::COLOR_TYPE, - }); + return Err(Error::WrongColor(P::COLOR_TYPE)); } let as_ref = self.samples.as_ref(); @@ -638,9 +643,7 @@ impl FlatSamples { Buffer: AsMut<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor { - underlying: P::COLOR_TYPE, - }); + return Err(Error::WrongColor(P::COLOR_TYPE)); } let as_mut = self.samples.as_mut(); @@ -675,15 +678,11 @@ impl FlatSamples { Buffer: AsMut<[P::Subpixel]>, { if !self.layout.is_normal(NormalForm::PixelPacked) { - return Err(Error::NormalFormRequired { - underlying: NormalForm::PixelPacked, - }); + return Err(Error::NormalFormRequired(NormalForm::PixelPacked)); } if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor { - underlying: P::COLOR_TYPE, - }); + return Err(Error::WrongColor(P::COLOR_TYPE)); } let as_mut = self.samples.as_mut(); @@ -775,21 +774,11 @@ impl FlatSamples { Buffer: Deref, { if !self.is_normal(NormalForm::RowMajorPacked) { - return Err(( - Error::NormalFormRequired { - underlying: NormalForm::RowMajorPacked, - }, - self, - )); + return Err((Error::NormalFormRequired(NormalForm::RowMajorPacked), self)); } if self.layout.channels != P::CHANNEL_COUNT { - return Err(( - Error::WrongColor { - underlying: P::COLOR_TYPE, - }, - self, - )); + return Err((Error::WrongColor(P::COLOR_TYPE), self)); } if !self.fits(self.samples.deref().len()) { @@ -1023,7 +1012,7 @@ where /// samples in a row major matrix representation. But this error type may be /// resused for other import functions. A more versatile user may also try to /// correct the underlying representation depending on the error variant. -#[derive(Snafu, Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Error { /// The represented image was too large. /// @@ -1033,7 +1022,7 @@ pub enum Error { /// The represented image can not use this representation. /// /// Has an additional value of the normalized form that would be accepted. - NormalFormRequired { underlying: NormalForm }, + NormalFormRequired(NormalForm), /// The color format did not match the channel count. /// @@ -1044,7 +1033,7 @@ pub enum Error { /// directly memory unsafe although that will likely alias pixels. One scenario is when you /// want to construct an `Rgba` image but have only 3 bytes per pixel and for some reason don't /// care about the value of the alpha channel even though you need `Rgba`. - WrongColor { underlying: ColorType }, + WrongColor(ColorType), } /// Different normal forms of buffers. @@ -1210,12 +1199,7 @@ where Buffer: AsMut<[P::Subpixel]>, { if !self.inner.is_normal(NormalForm::PixelPacked) { - return Err(( - Error::NormalFormRequired { - underlying: NormalForm::PixelPacked, - }, - self, - )); + return Err((Error::NormalFormRequired(NormalForm::PixelPacked), self)); } // No length check or channel count check required, all the same. @@ -1499,21 +1483,59 @@ where impl From for ImageError { fn from(error: Error) -> ImageError { + #[derive(Debug)] + struct NormalFormRequiredError(NormalForm); + impl fmt::Display for NormalFormRequiredError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Required sample buffer in normal form {:?}", self.0) + } + } + impl error::Error for NormalFormRequiredError {} + match error { - Error::TooLarge => ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }, - Error::NormalFormRequired { underlying } => ImageError::Decoding { - format: ImageFormatHint::Unknown, - }, - Error::WrongColor { underlying } => ImageError::Unsupported { - format: ImageFormatHint::Unknown, - kind: UnsupportedErrorKind::Color(underlying.into()), - }, + Error::TooLarge => ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + )), + Error::NormalFormRequired(form) => ImageError::Decoding(DecodingError::new( + ImageFormatHint::Unknown, + NormalFormRequiredError(form), + )), + Error::WrongColor(color) => { + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormatHint::Unknown, + UnsupportedErrorKind::Color(color.into()), + )) + } } } } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::TooLarge => write!(f, "The layout is too large"), + Error::NormalFormRequired(form) => write!( + f, + "The layout needs to {}", + match form { + NormalForm::ColumnMajorPacked => "be packed and in column major form", + NormalForm::ImagePacked => "be fully packed", + NormalForm::PixelPacked => "have packed pixels", + NormalForm::RowMajorPacked => "be packed and in row major form", + NormalForm::Unaliased => "not have any aliasing channels", + } + ), + Error::WrongColor(color) => write!( + f, + "The chosen color type does not match the hint {:?}", + color + ), + } + } +} + +impl error::Error for Error {} + impl PartialOrd for NormalForm { /// Compares the logical preconditions. /// diff --git a/src/image.rs b/src/image.rs index 6ed64d5385..ecafe6a5a2 100644 --- a/src/image.rs +++ b/src/image.rs @@ -4,11 +4,17 @@ use alloc::vec::Vec; use core::convert::TryFrom; use core::ops::{Deref, DerefMut}; use core::usize; -// use std::io; -// use std::io::Read; + +#[cfg(feature = "std")] +use std::io; +#[cfg(feature = "std")] +use std::io::Read; use crate::color::{ColorType, ExtendedColorType}; -use crate::error::{ImageError, ImageFormatHint, ImageResult, LimitErrorKind, ParameterErrorKind}; +use crate::error::{ + ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, ParameterError, + ParameterErrorKind, +}; use crate::math::Rect; use crate::traits::Pixel; use crate::ImageBuffer; @@ -559,14 +565,14 @@ where || width == 0 || height == 0 { - return Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }); + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); } if scanline_bytes > usize::max_value() as u64 { - return Err(ImageError::Limits { - kind: LimitErrorKind::InsufficientMemory, - }); + return Err(ImageError::Limits(LimitError::from_kind( + LimitErrorKind::InsufficientMemory, + ))); } progress_callback(Progress { @@ -601,9 +607,9 @@ where { let total_bytes = usize::try_from(decoder.total_bytes()); if total_bytes.is_err() || total_bytes.unwrap() > isize::max_value() as usize { - return Err(ImageError::Limits { - kind: LimitErrorKind::InsufficientMemory, - }); + return Err(ImageError::Limits(LimitError::from_kind( + LimitErrorKind::InsufficientMemory, + ))); } let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / core::mem::size_of::()]; @@ -1032,9 +1038,9 @@ pub trait GenericImage: GenericImageView { // Do bounds checking here so we can use the non-bounds-checking // functions to copy pixels. if self.width() < other.width() + x || self.height() < other.height() + y { - return Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }); + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); } for k in 0..other.height() { diff --git a/src/imageops/affine.rs b/src/imageops/affine.rs index 2aabf68b8b..5831de3143 100644 --- a/src/imageops/affine.rs +++ b/src/imageops/affine.rs @@ -1,8 +1,6 @@ -//! Functions for performing affine transformations. - use alloc::vec::Vec; -use crate::error::{ImageError, ParameterErrorKind}; +use crate::error::{ImageError, ParameterError, ParameterErrorKind}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::Pixel; use crate::ImageBuffer; @@ -58,9 +56,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { - return Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }); + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); } for y in 0..h0 { @@ -84,9 +82,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { - return Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }); + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); } for y in 0..h0 { @@ -110,9 +108,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { - return Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }); + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); } for y in 0..h0 { @@ -162,9 +160,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { - return Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }); + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); } for y in 0..h0 { @@ -188,9 +186,9 @@ where { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { - return Err(ImageError::Parameter { - kind: ParameterErrorKind::DimensionMismatch, - }); + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); } for y in 0..h0 { diff --git a/src/io/mod.rs b/src/io/mod.rs index 5eea1e103e..97c3a0b868 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -89,22 +89,20 @@ impl Limits { 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 { - kind: error::LimitErrorKind::DimensionError, - }); + 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 { - kind: error::LimitErrorKind::DimensionError, - }); + return Err(ImageError::Limits(error::LimitError::from_kind( + error::LimitErrorKind::DimensionError, + ))); } } @@ -116,9 +114,9 @@ impl Limits { 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 { - kind: error::LimitErrorKind::InsufficientMemory, - }); + return Err(ImageError::Limits(error::LimitError::from_kind( + error::LimitErrorKind::InsufficientMemory, + ))); } *max_alloc -= amount; @@ -132,9 +130,9 @@ impl Limits { match u64::try_from(amount) { Ok(n) => self.reserve(n), Err(_) if self.max_alloc.is_some() => { - return Err(ImageError::Limits { - kind: error::LimitErrorKind::InsufficientMemory, - }); + return Err(ImageError::Limits(error::LimitError::from_kind( + error::LimitErrorKind::InsufficientMemory, + ))); } Err(_) => { // Out of bounds, but we weren't asked to consider any limit. diff --git a/src/lib.rs b/src/lib.rs index 77490302cf..3b1facb1e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,6 +88,7 @@ //! [`ImageDecoder`]: trait.ImageDecoder.html //! [`ImageEncoder`]: trait.ImageEncoder.html #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "alloc", feature(error_in_core))] #![warn(missing_docs)] #![warn(unused_qualifications)] #![deny(unreachable_pub)] From 4684577301ec9b011a6f2d484d3f8c9aa522281e Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 3 Mar 2023 13:24:07 +0100 Subject: [PATCH 12/16] fix `std` build Both `std` and `no_std` build+test OK - `cargo test --no-default-features --features=default` OK - `cargo +nightly-2022-10-22 test --no-default-features --features=alloc` OK - `cargo +nightly-2022-10-22 no-std-check --no-default-features --features=alloc` OK --- src/image.rs | 16 +++++++--------- src/io/free_functions.rs | 1 - src/lib.rs | 5 +++-- tests/reference_images.rs | 4 ++-- tests/regression.rs | 4 ++-- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/image.rs b/src/image.rs index ecafe6a5a2..25a8a8f729 100644 --- a/src/image.rs +++ b/src/image.rs @@ -86,14 +86,13 @@ impl ImageFormat { /// let format = ImageFormat::from_extension("jpg"); /// assert_eq!(format, Some(ImageFormat::Jpeg)); /// ``` - #[cfg(features = "std")] - #[inline] + #[cfg(feature = "std")] pub fn from_extension(ext: S) -> Option where S: AsRef, { // thin wrapper function to strip generics - fn inner(ext: &OsStr) -> Option { + fn inner(ext: &std::ffi::OsStr) -> Option { let ext = ext.to_str()?.to_ascii_lowercase(); Some(match ext.as_str() { @@ -131,14 +130,13 @@ impl ImageFormat { /// /// # Ok::<(), image::error::ImageError>(()) /// ``` - #[cfg(features = "std")] - #[inline] + #[cfg(feature = "std")] pub fn from_path

(path: P) -> ImageResult where - P: AsRef, + P: AsRef, { // thin wrapper function to strip generics - fn inner(path: &Path) -> ImageResult { + fn inner(path: &std::path::Path) -> ImageResult { let exact_ext = path.extension(); exact_ext .and_then(ImageFormat::from_extension) @@ -1647,7 +1645,7 @@ mod tests { assert_eq!(output[0..9], [6, 7, 11, 12, 16, 17, 21, 22, 0]); } - #[cfg(features = "std")] + #[cfg(feature = "std")] #[test] fn test_image_format_from_path() { fn from_path(s: &str) -> ImageResult { @@ -1839,7 +1837,7 @@ mod tests { assert_eq!(&image.into_raw(), &expected); } - #[cfg(features = "std")] + #[cfg(feature = "std")] #[test] fn image_formats_are_recognized() { use ImageFormat::*; diff --git a/src/io/free_functions.rs b/src/io/free_functions.rs index 839bc45017..1a2d1cea14 100644 --- a/src/io/free_functions.rs +++ b/src/io/free_functions.rs @@ -192,7 +192,6 @@ pub(crate) fn save_buffer_with_format_impl( } #[allow(unused_variables)] -#[cfg(feature = "std")] // Most variables when no features are supported pub(crate) fn write_buffer_impl( buffered_write: &mut W, diff --git a/src/lib.rs b/src/lib.rs index 3b1facb1e1..d4197674a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,10 +19,11 @@ //! use std::io::Cursor; //! #[cfg(feature = "std")] //! # fn main() -> Result<(), image::ImageError> { +//! use image::io::Reader as ImageReader; //! # let bytes = vec![0u8]; //! -//! let img = image::io::Reader::ImageReader::open("myimage.png")?.decode()?; -//! let img2 = image::io::Reader::ImageReader::new(Cursor::new(bytes)).with_guessed_format()?.decode()?; +//! let img = ImageReader::open("myimage.png")?.decode()?; +//! let img2 = ImageReader::new(Cursor::new(bytes)).with_guessed_format()?.decode()?; //! # Ok(()) //! # } //! diff --git a/tests/reference_images.rs b/tests/reference_images.rs index 9d48ae830d..c2ea120a81 100644 --- a/tests/reference_images.rs +++ b/tests/reference_images.rs @@ -161,7 +161,7 @@ fn check_references() { // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images // or because a specific decoder included via a feature. - Err(image::ImageError::Unsupported { format, kind }) => return, + Err(image::ImageError::Unsupported(_)) => return, Err(err) => panic!("{}", err), }; @@ -260,7 +260,7 @@ fn check_references() { // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images // or because a specific decoder included via a feature. - Err(image::ImageError::Unsupported { format, kind }) => return, + Err(image::ImageError::Unsupported(_)) => return, Err(err) => panic!("decoding of {:?} failed with: {}", img_path, err), }; } diff --git a/tests/regression.rs b/tests/regression.rs index aa0516a927..52aabddcef 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -78,7 +78,7 @@ fn bad_gif_oom() { let error = image::load_from_memory(&data).unwrap_err(); assert!( - matches!(error, image::ImageError::Limits { kind }) - | matches!(error, image::ImageError::Unsupported { format, kind }) + matches!(error, image::ImageError::Limits(_)) + | matches!(error, image::ImageError::Unsupported(_)) ); } From 27a05ea3abd72df3002876e85dde3d92974801e2 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 3 Mar 2023 13:42:11 +0100 Subject: [PATCH 13/16] cargo: require "std" for all features TODO TEMP? or keep it that way? --- Cargo.toml | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f70cc50817..235cc010ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,39 +65,40 @@ jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, # TODO: Add "avif" to this list while preparing for 0.24.0 default = ["std", "gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"] -std = ["num-traits/std", "byteorder/std", "gif/default", "color_quant/std"] -ico = ["bmp", "png"] -pnm = [] -tga = [] -bmp = [] -hdr = ["scoped_threadpool"] -dxt = [] +std = ["num-traits/std", "byteorder/std", "color_quant/std"] +alloc = ["num-traits/libm", "color_quant/num-traits"] + +gif = ["std", "gif/default"] +ico = ["std", "bmp", "png"] +pnm = ["std"] +tga = ["std"] +bmp = ["std"] +hdr = ["std", "scoped_threadpool"] +dxt = ["std"] dds = ["dxt"] -farbfeld = [] -openexr = ["exr"] -qoi = ["dep:qoi"] +farbfeld = ["std"] +openexr = ["std", "exr"] +qoi = ["std", "dep:qoi"] # Enables WebP decoder support. -webp = [] +webp = ["std"] # Non-default, not included in `webp`. Requires native dependency libwebp. -webp-encoder = ["libwebp"] +webp-encoder = ["std", "libwebp"] # Enables multi-threading. # Requires latest stable Rust. -jpeg_rayon = ["jpeg/rayon"] +jpeg_rayon = ["std", "jpeg/rayon"] # Non-default, enables avif support. # Requires latest stable Rust. -avif = ["avif-encoder"] +avif = ["std", "avif-encoder"] # Requires latest stable Rust and recent nasm (>= 2.14). -avif-encoder = ["ravif", "rgb"] +avif-encoder = ["std", "ravif", "rgb"] # Non-default, even in `avif`. Requires stable Rust and native dependency libdav1d. -avif-decoder = ["mp4parse", "dcv-color-primitives", "dav1d"] +avif-decoder = ["std", "mp4parse", "dcv-color-primitives", "dav1d"] # Build some inline benchmarks. Useful only during development. # Requires rustc nightly for feature test. -benchmarks = [] - -alloc = ["num-traits/libm", "color_quant/num-traits"] +benchmarks = ["std"] [[bench]] path = "benches/decode.rs" From 0c04163a9e5a3951ad9e4727f874e18823e977b6 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 3 Mar 2023 13:58:15 +0100 Subject: [PATCH 14/16] ci: always compile at least with "std" --- .github/workflows/rust.yml | 4 ++-- Cargo.toml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 734a2e31ea..84f9d2fb9c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: rust: ["1.61.0", stable, beta, nightly] - features: [gif, jpeg, png, tiff, ico, pnm, tga, webp, bmp, hdr, dxt, dds, farbfeld, openexr, jpeg_rayon, webp-encoder, ''] + features: [gif, jpeg, png, tiff, ico, pnm, tga, webp, bmp, hdr, dxt, dds, farbfeld, openexr, jpeg_rayon, webp-encoder, std] steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - features: [gif, jpeg, png, tiff, ico, pnm, tga, webp, bmp, hdr, dxt, dds, farbfeld, openexr, jpeg_rayon, webp-encoder, ''] + features: [gif, jpeg, png, tiff, ico, pnm, tga, webp, bmp, hdr, dxt, dds, farbfeld, openexr, jpeg_rayon, webp-encoder, std] # we are using the cross project for cross compilation to mips: # https://github.com/cross-rs/cross diff --git a/Cargo.toml b/Cargo.toml index 235cc010ff..c22d59ac6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,8 @@ jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, # TODO: Add "avif" to this list while preparing for 0.24.0 default = ["std", "gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"] +# MUST always compile with either "std" or "alloc" +# TODO(interstellar) add compile error in case of neither? std = ["num-traits/std", "byteorder/std", "color_quant/std"] alloc = ["num-traits/libm", "color_quant/num-traits"] From f425feea588a25cee3d637dcfa256e64fa5d2034 Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 3 Mar 2023 15:29:28 +0100 Subject: [PATCH 15/16] cargo: require "std" for all features [2] --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index c22d59ac6e..684dc23c3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,9 @@ std = ["num-traits/std", "byteorder/std", "color_quant/std"] alloc = ["num-traits/libm", "color_quant/num-traits"] gif = ["std", "gif/default"] +jpeg = ["std", "dep:jpeg"] +png = ["std", "dep:png"] +tiff = ["std", "dep:tiff"] ico = ["std", "bmp", "png"] pnm = ["std"] tga = ["std"] From acbbdeae179ed18482327f28826c134699b21fea Mon Sep 17 00:00:00 2001 From: Nathan Prat Date: Fri, 3 Mar 2023 15:31:56 +0100 Subject: [PATCH 16/16] ci: add "no-std-check" --- .github/workflows/rust.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 84f9d2fb9c..58f78256fe 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -121,6 +121,19 @@ jobs: env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always + no_std_check: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: build + run: | + cargo install cargo-no-std-check + cargo no-std-check --no-default-features --features=alloc + clippy: runs-on: ubuntu-20.04 steps: