From a688fee7d1473ab7318f93e5bc7f8e1f4d4bd926 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 13:37:28 +1000 Subject: [PATCH 01/42] Increase MSRV to 1.81 Brings support for `core::error::Error` --- .github/workflows/rust.yml | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dcc67f776f..66b5eeb07f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -38,14 +38,14 @@ jobs: strategy: fail-fast: false matrix: - rust: ["1.70.0", nightly, beta] + rust: ["1.81.0", nightly, beta] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - if: ${{ matrix.rust == '1.70.0' }} + if: ${{ matrix.rust == '1.81.0' }} - name: Generate Cargo.lock with minimal-version dependencies - if: ${{ matrix.rust == '1.70.0' }} + if: ${{ matrix.rust == '1.81.0' }} run: cargo -Zminimal-versions generate-lockfile - uses: dtolnay/rust-toolchain@v1 @@ -58,7 +58,7 @@ jobs: - name: build run: cargo build -v - name: test - if: ${{ matrix.rust != '1.70.0' }} + if: ${{ matrix.rust != '1.81.0' }} run: cargo test -v && cargo doc -v test_other_archs: diff --git a/Cargo.toml b/Cargo.toml index 74eb6094db..2b1f4be531 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" resolver = "2" # note: when changed, also update test runner in `.github/workflows/rust.yml` -rust-version = "1.70.0" +rust-version = "1.81.0" license = "MIT OR Apache-2.0" description = "Imaging library. Provides basic image processing and encoders/decoders for common image formats." From 8638a7b33a16acbfb2645774faae591e9e012ddf Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 13:39:37 +1000 Subject: [PATCH 02/42] Isolate a specific `std` feature Included in all relevant existing features --- Cargo.toml | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b1f4be531..82e22487d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,8 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bytemuck = { version = "1.8.0", features = ["extern_crate_alloc"] } # includes cast_vec -byteorder-lite = "0.1.0" -num-traits = { version = "0.2.0" } +byteorder-lite = { version = "0.1.0", default-features = false } +num-traits = { version = "0.2.0", default-features = false } # Optional dependencies color_quant = { version = "1.1", optional = true } @@ -54,7 +54,7 @@ rgb = { version = "0.8.48", default-features = false, optional = true } tiff = { version = "0.9.0", optional = true } zune-core = { version = "0.4.12", default-features = false, optional = true } zune-jpeg = { version = "0.4.13", optional = true } -serde = { version = "1.0.214", optional = true, features = ["derive"] } +serde = { version = "1.0.214", default-features = false, optional = true, features = ["derive"] } [dev-dependencies] crc32fast = "1.2.0" @@ -64,33 +64,34 @@ quickcheck = "1" criterion = "0.5.0" [features] -default = ["rayon", "default-formats"] +default = ["std", "rayon", "default-formats"] # Format features default-formats = ["avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"] -avif = ["dep:ravif", "dep:rgb"] -bmp = [] -dds = [] -exr = ["dep:exr"] -ff = [] # Farbfeld image format -gif = ["dep:gif", "dep:color_quant"] -hdr = [] -ico = ["bmp", "png"] -jpeg = ["dep:zune-core", "dep:zune-jpeg"] -png = ["dep:png"] -pnm = [] -qoi = ["dep:qoi"] -tga = [] -tiff = ["dep:tiff"] -webp = ["dep:image-webp"] +avif = ["dep:ravif", "dep:rgb", "std"] +bmp = ["std"] +dds = ["std"] +exr = ["dep:exr", "std"] +ff = ["std"] # Farbfeld image format +gif = ["dep:gif", "dep:color_quant", "std"] +hdr = ["std"] +ico = ["bmp", "png", "std"] +jpeg = ["dep:zune-core", "dep:zune-jpeg", "std"] +png = ["dep:png", "std"] +pnm = ["std"] +qoi = ["dep:qoi", "std"] +tga = ["std"] +tiff = ["dep:tiff", "std"] +webp = ["dep:image-webp", "std"] # Other features -rayon = ["dep:rayon", "ravif?/threading"] # Enables multi-threading -nasm = ["ravif?/asm"] # Enables use of nasm by rav1e (requires nasm to be installed) -color_quant = ["dep:color_quant"] # Enables color quantization -avif-native = ["dep:mp4parse", "dep:dav1d"] # Enable native dependency libdav1d -benchmarks = [] # Build some inline benchmarks. Useful only during development (requires nightly Rust) +rayon = ["dep:rayon", "ravif?/threading", "std"] # Enables multi-threading +nasm = ["ravif?/asm", "std"] # Enables use of nasm by rav1e (requires nasm to be installed) +color_quant = ["dep:color_quant", "std"] # Enables color quantization +avif-native = ["dep:mp4parse", "dep:dav1d", "std"] # Enable native dependency libdav1d +benchmarks = ["std"] # Build some inline benchmarks. Useful only during development (requires nightly Rust) serde = ["dep:serde"] +std = ["byteorder-lite/std", "num-traits/std"] [[bench]] path = "benches/decode.rs" From 18c547434aebf568eb1b30c36008df47b712b83d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 13:40:05 +1000 Subject: [PATCH 03/42] Added `libm` feature Simplifies access to floating point operations in `no_std` contexts. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 82e22487d7..e38182db46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ avif-native = ["dep:mp4parse", "dep:dav1d", "std"] # Enable native dependency li benchmarks = ["std"] # Build some inline benchmarks. Useful only during development (requires nightly Rust) serde = ["dep:serde"] std = ["byteorder-lite/std", "num-traits/std"] +libm = ["num-traits/libm"] [[bench]] path = "benches/decode.rs" From c84e9051e1feb0cf445b4077b25711a6c2545c2b Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:18:26 +1000 Subject: [PATCH 04/42] Implement `no_std` support --- src/animation.rs | 10 +++++--- src/buffer.rs | 25 +++++++++++++------ src/buffer_par.rs | 11 ++++---- src/codecs/avif/decoder.rs | 24 +++++++++--------- src/codecs/avif/encoder.rs | 8 +++--- src/codecs/avif/yuv.rs | 10 ++++---- src/codecs/bmp/decoder.rs | 12 ++++++--- src/codecs/bmp/encoder.rs | 2 ++ src/codecs/dds.rs | 5 +++- src/codecs/dxt.rs | 2 ++ src/codecs/farbfeld.rs | 2 ++ src/codecs/gif.rs | 8 ++++-- src/codecs/hdr/decoder.rs | 13 +++++++--- src/codecs/hdr/encoder.rs | 7 ++++-- src/codecs/ico/decoder.rs | 4 ++- src/codecs/ico/encoder.rs | 4 ++- src/codecs/jpeg/decoder.rs | 5 +++- src/codecs/jpeg/encoder.rs | 4 ++- src/codecs/openexr.rs | 3 +++ src/codecs/pcx.rs | 6 ++--- src/codecs/png.rs | 12 ++++++--- src/codecs/pnm/autobreak.rs | 1 + src/codecs/pnm/decoder.rs | 15 +++++++---- src/codecs/pnm/encoder.rs | 9 ++++--- src/codecs/pnm/header.rs | 5 +++- src/codecs/qoi.rs | 5 +++- src/codecs/tga/decoder.rs | 3 +++ src/codecs/tga/encoder.rs | 7 ++++-- src/codecs/tiff.rs | 8 ++++-- src/codecs/webp/decoder.rs | 2 ++ src/codecs/webp/encoder.rs | 1 + src/color.rs | 5 +++- src/dynimage.rs | 36 ++++++++++++++++++++++----- src/error.rs | 20 ++++++++++++--- src/flat.rs | 15 +++++------ src/image.rs | 21 +++++++++++----- src/image_reader/free_functions.rs | 27 ++++++++++++++------ src/image_reader/image_reader_type.rs | 22 ++++++++++++---- src/image_reader/mod.rs | 1 + src/imageops/affine.rs | 12 +++++---- src/imageops/colorops.rs | 9 +++++++ src/imageops/fast_blur.rs | 1 + src/imageops/mod.rs | 16 +++++++++--- src/imageops/sample.rs | 23 ++++++++++++++++- src/lib.rs | 17 +++++++++++-- src/math/utils.rs | 5 +++- src/metadata.rs | 6 ++++- src/traits.rs | 2 +- src/utils/mod.rs | 4 ++- 49 files changed, 348 insertions(+), 127 deletions(-) diff --git a/src/animation.rs b/src/animation.rs index f6dac64247..816310af7b 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -1,5 +1,7 @@ -use std::cmp::Ordering; -use std::time::Duration; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::time::Duration; use crate::error::ImageResult; use crate::RgbaImage; @@ -152,7 +154,7 @@ impl Delay { /// # Examples /// /// ``` - /// use std::time::Duration; + /// use core::time::Duration; /// use image::Delay; /// /// let duration = Duration::from_millis(20); @@ -206,7 +208,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::*; + use core::cmp::Ordering::*; assert!(0 < denom); assert!(0 < denom_bound); assert!(nom < denom); diff --git a/src/buffer.rs b/src/buffer.rs index 8ad693009b..cb380f05cd 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,21 +1,27 @@ //! Contains the generic `ImageBuffer` struct. +use alloc::vec; +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}; -use crate::dynimage::{save_buffer, save_buffer_with_format, write_buffer_with_format}; use crate::error::ImageResult; use crate::flat::{FlatSamples, SampleLayout}; -use crate::image::{GenericImage, GenericImageView, ImageEncoder, ImageFormat}; +use crate::image::ImageFormat; +use crate::image::{GenericImage, GenericImageView, ImageEncoder}; use crate::math::Rect; use crate::traits::{EncodableLayout, Pixel, PixelWithColorType}; use crate::utils::expand_packed; use crate::DynamicImage; +#[cfg(feature = "std")] +use crate::dynimage::{save_buffer, save_buffer_with_format, write_buffer_with_format}; +#[cfg(feature = "std")] +use std::path::Path; + /// Iterate over pixel refs. pub struct Pixels<'a, P: Pixel + 'a> where @@ -993,6 +999,7 @@ where /// 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, @@ -1019,6 +1026,7 @@ where /// /// 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, @@ -1046,6 +1054,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_to(&self, writer: &mut W, format: ImageFormat) -> ImageResult<()> where W: std::io::Write + std::io::Seek, @@ -1715,7 +1724,7 @@ mod test { #[test] fn exact_size_iter_size_hint() { - // The docs for `std::iter::ExactSizeIterator` requires that the implementation of + // The docs for `core::iter::ExactSizeIterator` requires that the implementation of // `size_hint` on the iterator returns the same value as the `len` implementation. // This test should work for any size image. diff --git a/src/buffer_par.rs b/src/buffer_par.rs index 3e77b10b94..8e1f7c0476 100644 --- a/src/buffer_par.rs +++ b/src/buffer_par.rs @@ -1,8 +1,9 @@ +use alloc::vec::Vec; +use core::fmt; +use core::ops::{Deref, DerefMut}; use rayon::iter::plumbing::*; use rayon::iter::{IndexedParallelIterator, ParallelIterator}; use rayon::slice::{ChunksExact, ChunksExactMut, ParallelSlice, ParallelSliceMut}; -use std::fmt; -use std::ops::{Deref, DerefMut}; use crate::traits::Pixel; use crate::ImageBuffer; @@ -430,7 +431,7 @@ mod test { #[test] fn iter_parity() { let mut image1 = RgbImage::from_fn(17, 29, |x, y| { - Rgb(std::array::from_fn(|i| { + Rgb(core::array::from_fn(|i| { ((x + y * 98 + i as u32 * 27) % 255) as u8 })) }); @@ -487,9 +488,9 @@ mod benchmarks { } fn pixel_func() -> Rgb { + use core::hash::{BuildHasher, Hasher}; use std::collections::hash_map::RandomState; - use std::hash::{BuildHasher, Hasher}; - Rgb(std::array::from_fn(|_| { + Rgb(core::array::from_fn(|_| { RandomState::new().build_hasher().finish() as u8 })) } diff --git a/src/codecs/avif/decoder.rs b/src/codecs/avif/decoder.rs index 04e43f666a..acbd3e6223 100644 --- a/src/codecs/avif/decoder.rs +++ b/src/codecs/avif/decoder.rs @@ -8,10 +8,10 @@ use crate::{ColorType, ImageDecoder, ImageError, ImageFormat, ImageResult}; /// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec. /// /// [AVIF]: https://aomediacodec.github.io/av1-avif/ -use std::error::Error; -use std::fmt::{Display, Formatter}; +use core::error::Error; +use core::fmt::{Display, Formatter}; +use core::marker::PhantomData; use std::io::Read; -use std::marker::PhantomData; use crate::codecs::avif::yuv::*; use dav1d::{PixelLayout, PlanarImageComponent}; @@ -38,7 +38,7 @@ enum AvifDecoderError { } impl Display for AvifDecoderError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { AvifDecoderError::AlphaPlaneFormat(pixel_layout) => match pixel_layout { PixelLayout::I400 => unreachable!("This option must be handled correctly"), @@ -127,14 +127,14 @@ fn reshape_plane(source: &[u8], stride: usize, width: usize, height: usize) -> V } struct Plane16View<'a> { - data: std::borrow::Cow<'a, [u16]>, + data: alloc::borrow::Cow<'a, [u16]>, stride: usize, } impl Default for Plane16View<'_> { fn default() -> Self { Plane16View { - data: std::borrow::Cow::Owned(vec![]), + data: alloc::borrow::Cow::Owned(vec![]), stride: 0, } } @@ -160,13 +160,13 @@ fn transmute_y_plane16( if stride & 1 == 0 { match bytemuck::try_cast_slice(plane_ref) { Ok(slice) => Plane16View { - data: std::borrow::Cow::Borrowed(slice), + data: alloc::borrow::Cow::Borrowed(slice), stride: y_plane_stride, }, Err(_) => { shape_y_plane(); Plane16View { - data: std::borrow::Cow::Owned(bind_y), + data: alloc::borrow::Cow::Owned(bind_y), stride: y_plane_stride, } } @@ -174,7 +174,7 @@ fn transmute_y_plane16( } else { shape_y_plane(); Plane16View { - data: std::borrow::Cow::Owned(bind_y), + data: alloc::borrow::Cow::Owned(bind_y), stride: y_plane_stride, } } @@ -209,13 +209,13 @@ fn transmute_chroma_plane16( if stride & 1 == 0 { match bytemuck::try_cast_slice(plane_ref) { Ok(slice) => Plane16View { - data: std::borrow::Cow::Borrowed(slice), + data: alloc::borrow::Cow::Borrowed(slice), stride: chroma_plane_stride, }, Err(_) => { shape_chroma_plane(); Plane16View { - data: std::borrow::Cow::Owned(bind_chroma), + data: alloc::borrow::Cow::Owned(bind_chroma), stride: chroma_plane_stride, } } @@ -223,7 +223,7 @@ fn transmute_chroma_plane16( } else { shape_chroma_plane(); Plane16View { - data: std::borrow::Cow::Owned(bind_chroma), + data: alloc::borrow::Cow::Owned(bind_chroma), stride: chroma_plane_stride, } } diff --git a/src/codecs/avif/encoder.rs b/src/codecs/avif/encoder.rs index 4980b95801..395651b7d3 100644 --- a/src/codecs/avif/encoder.rs +++ b/src/codecs/avif/encoder.rs @@ -3,10 +3,12 @@ /// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec. /// /// [AVIF]: https://aomediacodec.github.io/av1-avif/ -use std::borrow::Cow; -use std::cmp::min; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use alloc::{format, vec}; +use core::cmp::min; +use core::mem::size_of; use std::io::Write; -use std::mem::size_of; use crate::buffer::ConvertBuffer; use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; diff --git a/src/codecs/avif/yuv.rs b/src/codecs/avif/yuv.rs index 62190c9038..dda0fda36f 100644 --- a/src/codecs/avif/yuv.rs +++ b/src/codecs/avif/yuv.rs @@ -1,8 +1,8 @@ use crate::error::DecodingError; use crate::{ImageError, ImageFormat}; +use core::fmt::{Display, Formatter}; +use core::mem::size_of; use num_traits::AsPrimitive; -use std::fmt::{Display, Formatter}; -use std::mem::size_of; #[derive(Debug, Copy, Clone)] /// Representation of inversion matrix @@ -46,7 +46,7 @@ enum PlaneDefinition { } impl Display for PlaneDefinition { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { match self { PlaneDefinition::Y => f.write_str("Luma"), PlaneDefinition::U => f.write_str("U chroma"), @@ -62,7 +62,7 @@ enum YuvConversionError { } impl Display for YuvConversionError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { YuvConversionError::YuvPlaneSizeMismatch(plane, error_size) => { f.write_fmt(format_args!( @@ -80,7 +80,7 @@ impl Display for YuvConversionError { } } -impl std::error::Error for YuvConversionError {} +impl core::error::Error for YuvConversionError {} #[inline] fn check_yuv_plane_preconditions( diff --git a/src/codecs/bmp/decoder.rs b/src/codecs/bmp/decoder.rs index 109470cbf8..16edbc0731 100644 --- a/src/codecs/bmp/decoder.rs +++ b/src/codecs/bmp/decoder.rs @@ -1,8 +1,12 @@ -use std::cmp::{self, Ordering}; +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::vec::Vec; +use alloc::{format, vec}; +use core::cmp::{self, Ordering}; +use core::iter::{repeat, Rev}; +use core::slice::ChunksMut; +use core::{error, fmt}; use std::io::{self, BufRead, Seek, SeekFrom}; -use std::iter::{repeat, Rev}; -use std::slice::ChunksMut; -use std::{error, fmt}; use byteorder_lite::{LittleEndian, ReadBytesExt}; diff --git a/src/codecs/bmp/encoder.rs b/src/codecs/bmp/encoder.rs index 457e5d9d12..c2f23a84cb 100644 --- a/src/codecs/bmp/encoder.rs +++ b/src/codecs/bmp/encoder.rs @@ -1,3 +1,5 @@ +use alloc::format; +use alloc::string::String; use byteorder_lite::{LittleEndian, WriteBytesExt}; use std::io::{self, Write}; diff --git a/src/codecs/dds.rs b/src/codecs/dds.rs index e23a23beb2..f6b80118d2 100644 --- a/src/codecs/dds.rs +++ b/src/codecs/dds.rs @@ -5,8 +5,11 @@ //! # Related Links //! * - Description of the DDS format. +use alloc::boxed::Box; +use alloc::format; +use alloc::string::ToString; +use core::{error, fmt}; use std::io::Read; -use std::{error, fmt}; use byteorder_lite::{LittleEndian, ReadBytesExt}; diff --git a/src/codecs/dxt.rs b/src/codecs/dxt.rs index 9f3ee253d3..a8f348939d 100644 --- a/src/codecs/dxt.rs +++ b/src/codecs/dxt.rs @@ -7,6 +7,8 @@ //! //! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds +use alloc::boxed::Box; +use alloc::vec; use std::io::{self, Read}; use crate::color::ColorType; diff --git a/src/codecs/farbfeld.rs b/src/codecs/farbfeld.rs index 32fdbc9c1e..322ca09ceb 100644 --- a/src/codecs/farbfeld.rs +++ b/src/codecs/farbfeld.rs @@ -16,6 +16,8 @@ //! # Related Links //! * - the farbfeld specification +use alloc::boxed::Box; +use alloc::format; use std::io::{self, Read, Seek, SeekFrom, Write}; use crate::color::ExtendedColorType; diff --git a/src/codecs/gif.rs b/src/codecs/gif.rs index f67a161306..013b14ddbe 100644 --- a/src/codecs/gif.rs +++ b/src/codecs/gif.rs @@ -27,9 +27,13 @@ //! ``` #![allow(clippy::while_let_loop)] +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::vec::Vec; +use alloc::{format, vec}; +use core::marker::PhantomData; +use core::mem; use std::io::{self, BufRead, Cursor, Read, Seek, Write}; -use std::marker::PhantomData; -use std::mem; use gif::ColorOutput; use gif::{DisposalMethod, Frame}; diff --git a/src/codecs/hdr/decoder.rs b/src/codecs/hdr/decoder.rs index 08f8483d5f..91012d697d 100644 --- a/src/codecs/hdr/decoder.rs +++ b/src/codecs/hdr/decoder.rs @@ -1,8 +1,12 @@ +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::{format, vec}; +use core::num::{ParseFloatError, ParseIntError}; +use core::{error, fmt}; use std::io::{self, Read}; -use std::num::{ParseFloatError, ParseIntError}; -use std::{error, fmt}; - use crate::color::{ColorType, Rgb}; use crate::error::{ DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind, @@ -701,7 +705,8 @@ fn read_line_u8(r: &mut R) -> io::Result>> { #[cfg(test)] mod tests { - use std::{borrow::Cow, io::Cursor}; + use alloc::borrow::Cow; + use std::io::Cursor; use super::*; diff --git a/src/codecs/hdr/encoder.rs b/src/codecs/hdr/encoder.rs index a4565dbc7e..a601c02af4 100644 --- a/src/codecs/hdr/encoder.rs +++ b/src/codecs/hdr/encoder.rs @@ -2,7 +2,10 @@ use crate::codecs::hdr::{rgbe8, Rgbe8Pixel, SIGNATURE}; use crate::color::Rgb; use crate::error::{EncodingError, ImageFormatHint, ImageResult}; use crate::{ExtendedColorType, ImageEncoder, ImageError, ImageFormat}; -use std::cmp::Ordering; +use alloc::string::ToString; +use alloc::vec::Vec; +use alloc::{format, vec}; +use core::cmp::Ordering; use std::io::{Result, Write}; /// Radiance HDR encoder @@ -466,7 +469,7 @@ fn noruncombine_test() { assert_eq!(rsi.next(), Some(Norun(129, 7))); assert_eq!(rsi.next(), None); - let v: Vec<_> = std::iter::repeat(()) + let v: Vec<_> = core::iter::repeat(()) .flat_map(|_| (0..2)) .take(257) .collect(); diff --git a/src/codecs/ico/decoder.rs b/src/codecs/ico/decoder.rs index e1c2c57e5d..71528b024f 100644 --- a/src/codecs/ico/decoder.rs +++ b/src/codecs/ico/decoder.rs @@ -1,6 +1,8 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; use byteorder_lite::{LittleEndian, ReadBytesExt}; +use core::{error, fmt}; use std::io::{BufRead, Read, Seek, SeekFrom}; -use std::{error, fmt}; use crate::color::ColorType; use crate::error::{ diff --git a/src/codecs/ico/encoder.rs b/src/codecs/ico/encoder.rs index 7c51996d94..f67113d322 100644 --- a/src/codecs/ico/encoder.rs +++ b/src/codecs/ico/encoder.rs @@ -1,5 +1,7 @@ +use alloc::borrow::Cow; +use alloc::format; +use alloc::vec::Vec; use byteorder_lite::{LittleEndian, WriteBytesExt}; -use std::borrow::Cow; use std::io::{self, Write}; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; diff --git a/src/codecs/jpeg/decoder.rs b/src/codecs/jpeg/decoder.rs index b98d24747e..a99a0fca71 100644 --- a/src/codecs/jpeg/decoder.rs +++ b/src/codecs/jpeg/decoder.rs @@ -1,5 +1,8 @@ +use alloc::boxed::Box; +use alloc::format; +use alloc::vec::Vec; +use core::marker::PhantomData; use std::io::{BufRead, Seek}; -use std::marker::PhantomData; use crate::color::ColorType; use crate::error::{ diff --git a/src/codecs/jpeg/encoder.rs b/src/codecs/jpeg/encoder.rs index 4e2e828642..9e90fbefe1 100644 --- a/src/codecs/jpeg/encoder.rs +++ b/src/codecs/jpeg/encoder.rs @@ -7,8 +7,10 @@ use crate::error::{ use crate::image::{ImageEncoder, ImageFormat}; use crate::utils::clamp; use crate::{ExtendedColorType, GenericImageView, ImageBuffer, Luma, Pixel, Rgb}; +use alloc::borrow::Cow; +use alloc::vec; +use alloc::vec::Vec; use num_traits::ToPrimitive; -use std::borrow::Cow; use std::io::{self, Write}; use super::entropy::build_huff_lut_const; diff --git a/src/codecs/openexr.rs b/src/codecs/openexr.rs index 1795151387..d83bc2268b 100644 --- a/src/codecs/openexr.rs +++ b/src/codecs/openexr.rs @@ -27,6 +27,9 @@ use crate::{ ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, }; +use alloc::boxed::Box; +use alloc::string::ToString; +use alloc::{format, vec}; use std::io::{BufRead, Seek, Write}; /// An OpenEXR decoder. Immediately reads the meta data from the file. diff --git a/src/codecs/pcx.rs b/src/codecs/pcx.rs index b7c9a507b7..ff29e22cea 100644 --- a/src/codecs/pcx.rs +++ b/src/codecs/pcx.rs @@ -7,10 +7,10 @@ extern crate pcx; +use core::iter; +use core::marker::PhantomData; +use core::mem; use std::io::{self, BufRead, Cursor, Read, Seek}; -use std::iter; -use std::marker::PhantomData; -use std::mem; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ImageError, ImageResult}; diff --git a/src/codecs/png.rs b/src/codecs/png.rs index de6cfbdf9c..4d59038efc 100644 --- a/src/codecs/png.rs +++ b/src/codecs/png.rs @@ -5,8 +5,12 @@ //! # Related Links //! * - The PNG Specification -use std::borrow::Cow; -use std::fmt; +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; +use core::fmt; use std::io::{BufRead, Seek, Write}; use png::{BlendOp, DisposeOp}; @@ -720,7 +724,7 @@ impl fmt::Display for BadPngRepresentation { } } -impl std::error::Error for BadPngRepresentation {} +impl core::error::Error for BadPngRepresentation {} #[cfg(test)] mod tests { @@ -754,7 +758,7 @@ mod tests { #[test] fn underlying_error() { - use std::error::Error; + use core::error::Error; let mut not_png = std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png") diff --git a/src/codecs/pnm/autobreak.rs b/src/codecs/pnm/autobreak.rs index cea2cd8f2b..f798220548 100644 --- a/src/codecs/pnm/autobreak.rs +++ b/src/codecs/pnm/autobreak.rs @@ -1,4 +1,5 @@ //! Insert line breaks between written buffers when they would overflow the line length. +use alloc::vec::Vec; use std::io; // The pnm standard says to insert line breaks after 70 characters. Assumes that no line breaks diff --git a/src/codecs/pnm/decoder.rs b/src/codecs/pnm/decoder.rs index 61a2544720..91b65bf9ef 100644 --- a/src/codecs/pnm/decoder.rs +++ b/src/codecs/pnm/decoder.rs @@ -1,9 +1,14 @@ -use std::error; -use std::fmt::{self, Display}; +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use alloc::{format, vec}; +use core::error; +use core::fmt::{self, Display}; +use core::mem::size_of; +use core::num::ParseIntError; +use core::str; use std::io::{self, Read}; -use std::mem::size_of; -use std::num::ParseIntError; -use std::str; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index 4b6cc18fb0..e87616cb5a 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -1,8 +1,9 @@ //! Encoding of PNM Images -use std::fmt; -use std::io; - -use std::io::Write; +use alloc::borrow::ToOwned; +use alloc::format; +use alloc::vec::Vec; +use core::fmt; +use std::io::{self, Write}; use super::AutoBreak; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; diff --git a/src/codecs/pnm/header.rs b/src/codecs/pnm/header.rs index 40e82dc165..19c3d45969 100644 --- a/src/codecs/pnm/header.rs +++ b/src/codecs/pnm/header.rs @@ -1,4 +1,7 @@ -use std::{fmt, io}; +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt; +use std::io; /// The kind of encoding used to store sample values #[derive(Clone, Copy, PartialEq, Eq, Debug)] diff --git a/src/codecs/qoi.rs b/src/codecs/qoi.rs index f42d21a470..a65fd83713 100644 --- a/src/codecs/qoi.rs +++ b/src/codecs/qoi.rs @@ -1,10 +1,13 @@ //! Decoding and encoding of QOI images +use alloc::boxed::Box; +use alloc::format; +use std::io::{Read, Write}; + use crate::error::{DecodingError, EncodingError}; use crate::{ ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, }; -use std::io::{Read, Write}; /// QOI decoder pub struct QoiDecoder { diff --git a/src/codecs/tga/decoder.rs b/src/codecs/tga/decoder.rs index 3ad28e72a4..ef2513416a 100644 --- a/src/codecs/tga/decoder.rs +++ b/src/codecs/tga/decoder.rs @@ -6,6 +6,9 @@ use crate::{ }, image::{ImageDecoder, ImageFormat}, }; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; use byteorder_lite::ReadBytesExt; use std::io::{self, Read}; diff --git a/src/codecs/tga/encoder.rs b/src/codecs/tga/encoder.rs index 2b3383fc74..b427492cb4 100644 --- a/src/codecs/tga/encoder.rs +++ b/src/codecs/tga/encoder.rs @@ -3,7 +3,9 @@ use crate::{ codecs::tga::header::ImageType, error::EncodingError, ExtendedColorType, ImageEncoder, ImageError, ImageFormat, ImageResult, }; -use std::{error, fmt, io::Write}; +use alloc::vec::Vec; +use core::{error, fmt}; +use std::io::Write; /// Errors that can occur during encoding and saving of a TGA image. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -250,7 +252,8 @@ impl ImageEncoder for TgaEncoder { mod tests { use super::{EncoderError, TgaEncoder}; use crate::{codecs::tga::TgaDecoder, ExtendedColorType, ImageDecoder, ImageError}; - use std::{error::Error, io::Cursor}; + use core::error::Error; + use std::io::Cursor; #[test] fn test_image_width_too_large() { diff --git a/src/codecs/tiff.rs b/src/codecs/tiff.rs index 9a46a79456..440c5d2898 100644 --- a/src/codecs/tiff.rs +++ b/src/codecs/tiff.rs @@ -8,9 +8,13 @@ extern crate tiff; +use alloc::boxed::Box; +use alloc::format; +use alloc::string::ToString; +use alloc::vec::Vec; +use core::marker::PhantomData; +use core::mem; use std::io::{self, BufRead, Cursor, Read, Seek, Write}; -use std::marker::PhantomData; -use std::mem; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ diff --git a/src/codecs/webp/decoder.rs b/src/codecs/webp/decoder.rs index 1f20191b92..756b90522b 100644 --- a/src/codecs/webp/decoder.rs +++ b/src/codecs/webp/decoder.rs @@ -1,3 +1,5 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; use std::io::{BufRead, Read, Seek}; use crate::buffer::ConvertBuffer; diff --git a/src/codecs/webp/encoder.rs b/src/codecs/webp/encoder.rs index 422a36b795..3ca5c4caac 100644 --- a/src/codecs/webp/encoder.rs +++ b/src/codecs/webp/encoder.rs @@ -1,5 +1,6 @@ //! Encoding of WebP images. +use alloc::vec::Vec; use std::io::Write; use crate::error::{EncodingError, UnsupportedError, UnsupportedErrorKind}; diff --git a/src/color.rs b/src/color.rs index 2d35871d84..5235ed1bb9 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,9 +1,12 @@ -use std::ops::{Index, IndexMut}; +use core::ops::{Index, IndexMut}; use num_traits::{NumCast, ToPrimitive, Zero}; use crate::traits::{Enlargeable, Pixel, Primitive}; +#[cfg(not(feature = "std"))] +use num_traits::float::FloatCore as _; + /// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/src/dynimage.rs b/src/dynimage.rs index b55851b745..b4d1234179 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1,11 +1,16 @@ -use std::io::{self, Seek, Write}; -use std::path::Path; +use alloc::borrow::ToOwned; +use alloc::vec::Vec; #[cfg(feature = "gif")] use crate::codecs::gif; #[cfg(feature = "png")] use crate::codecs::png; +#[cfg(feature = "std")] +use std::io::{self, Seek, Write}; +#[cfg(feature = "std")] +use std::path::Path; + use crate::buffer_::{ ConvertBuffer, Gray16Image, GrayAlpha16Image, GrayAlphaImage, GrayImage, ImageBuffer, Rgb16Image, RgbImage, Rgba16Image, RgbaImage, @@ -13,16 +18,20 @@ use crate::buffer_::{ use crate::color::{self, FromColor, IntoColor}; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::flat::FlatSamples; -use crate::image::{GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat}; +use crate::image::ImageFormat; +use crate::image::{GenericImage, GenericImageView, ImageDecoder, ImageEncoder}; use crate::image_reader::free_functions; +use crate::imageops; use crate::math::resize_dimensions; use crate::metadata::Orientation; use crate::traits::Pixel; -use crate::ImageReader; +use crate::ExtendedColorType; use crate::{image, Luma, LumaA}; -use crate::{imageops, ExtendedColorType}; use crate::{Rgb32FImage, Rgba32FImage}; +#[cfg(feature = "std")] +use crate::ImageReader; + /// A Dynamic Image /// /// This represents a _matrix_ of _pixels_ which are _convertible_ from and to an _RGBA_ @@ -664,6 +673,7 @@ impl DynamicImage { ) } + #[cfg_attr(not(feature = "std"), expect(dead_code))] // TODO: choose a name under which to expose? fn inner_bytes(&self) -> &[u8] { // we can do this because every variant contains an `ImageBuffer<_, Vec<_>>` @@ -854,6 +864,7 @@ impl DynamicImage { /// /// This method typically assumes that the input is scene-linear light. /// If it is not, color distortion may occur. + #[cfg(any(feature = "std", feature = "libm"))] #[must_use] pub fn blur(&self, sigma: f32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::blur(p, sigma)) @@ -867,6 +878,7 @@ impl DynamicImage { /// /// This method typically assumes that the input is scene-linear light. /// If it is not, color distortion may occur. + #[cfg(any(feature = "std", feature = "libm"))] #[must_use] pub fn fast_blur(&self, sigma: f32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::fast_blur(p, sigma)) @@ -884,6 +896,7 @@ impl DynamicImage { /// /// See [Digital unsharp masking](https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking) /// for more information + #[cfg(any(feature = "std", feature = "libm"))] #[must_use] pub fn unsharpen(&self, sigma: f32, threshold: i32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::unsharpen(p, sigma, threshold)) @@ -924,6 +937,7 @@ impl DynamicImage { /// `value` is the degrees to rotate each pixel by. /// 0 and 360 do nothing, the rest rotates by the given degree value. /// just like the css webkit filter hue-rotate(180) + #[cfg(any(feature = "std", feature = "libm"))] #[must_use] pub fn huerotate(&self, value: i32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::huerotate(p, value)) @@ -986,7 +1000,7 @@ impl DynamicImage { /// e.g. to correctly display a photo taken by a smartphone camera: /// /// ``` - /// # fn only_check_if_this_compiles() -> Result<(), Box> { + /// # fn only_check_if_this_compiles() -> Result<(), Box> { /// use image::{DynamicImage, ImageReader, ImageDecoder}; /// /// let mut decoder = ImageReader::open("file.jpg")?.into_decoder()?; @@ -1028,6 +1042,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, format: ImageFormat) -> ImageResult<()> { let bytes = self.inner_bytes(); let (width, height) = self.dimensions(); @@ -1063,6 +1078,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, @@ -1075,6 +1091,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, @@ -1290,6 +1307,7 @@ fn decoder_to_image(decoder: I) -> ImageResult { /// /// Try [`ImageReader`] for more advanced uses, including guessing the format based on the file's /// content before its path. +#[cfg(feature = "std")] pub fn open

(path: P) -> ImageResult where P: AsRef, @@ -1302,6 +1320,7 @@ where /// /// Try [`ImageReader`] for more advanced uses, including guessing the format based on the file's /// content before its path or manually supplying the format. +#[cfg(feature = "std")] pub fn image_dimensions

(path: P) -> ImageResult<(u32, u32)> where P: AsRef, @@ -1316,6 +1335,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: impl AsRef, buf: &[u8], @@ -1335,6 +1355,7 @@ pub fn save_buffer( /// 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: impl AsRef, buf: &[u8], @@ -1361,6 +1382,7 @@ pub fn save_buffer_with_format( /// /// 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], @@ -1379,6 +1401,7 @@ pub fn write_buffer_with_format( /// TGA is not supported by this function. /// /// Try [`ImageReader`] for more advanced uses. +#[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) @@ -1392,6 +1415,7 @@ pub fn load_from_memory(buffer: &[u8]) -> ImageResult { /// Try [`ImageReader`] for more advanced uses. /// /// [`load`]: fn.load.html +#[cfg(feature = "std")] #[inline(always)] pub fn load_from_memory_with_format(buf: &[u8], format: ImageFormat) -> ImageResult { let b = io::Cursor::new(buf); diff --git a/src/error.rs b/src/error.rs index 5201c7b8c8..77ea420259 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,17 +13,23 @@ //! //! [`ImageError`]: enum.ImageError.html -use std::error::Error; -use std::{fmt, io}; +use alloc::boxed::Box; +use alloc::string::String; +use core::error::Error; +use core::fmt; use crate::color::ExtendedColorType; use crate::image::ImageFormat; +#[cfg(feature = "std")] +use std::io; + /// The generic error type for image operations. /// /// 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)] +#[non_exhaustive] pub enum ImageError { /// An error was encountered while decoding. /// @@ -61,6 +67,7 @@ pub enum ImageError { Unsupported(UnsupportedError), /// An error occurred while interacting with the environment. + #[cfg(feature = "std")] IoError(io::Error), } @@ -183,6 +190,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. @@ -297,6 +305,7 @@ impl LimitError { } } +#[cfg(feature = "std")] impl From for ImageError { fn from(err: io::Error) -> ImageError { ImageError::IoError(err) @@ -309,6 +318,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() { @@ -333,6 +343,7 @@ 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), @@ -346,6 +357,7 @@ impl fmt::Display for ImageError { 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(), @@ -362,6 +374,7 @@ impl fmt::Display for UnsupportedError { 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 {format} was not recognized as an image format", @@ -490,6 +503,7 @@ impl fmt::Display for ImageFormatHint { 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`"), } @@ -499,7 +513,7 @@ impl fmt::Display for ImageFormatHint { #[cfg(test)] mod tests { use super::*; - use std::mem::size_of; + use core::mem::size_of; #[allow(dead_code)] // This will fail to compile if the size of this type is large. diff --git a/src/flat.rs b/src/flat.rs index 4ba1b6eab0..185cc6ce1e 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -7,8 +7,8 @@ //! to help you transition from raw memory data to Rust representation. //! //! ```no_run -//! use std::ptr; -//! use std::slice; +//! use core::ptr; +//! use core::slice; //! use image::Rgb; //! use image::flat::{FlatSamples, SampleLayout}; //! use image::imageops::thumbnail; @@ -41,9 +41,10 @@ //! } //! ``` //! -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, error, fmt}; use num_traits::Zero; @@ -536,7 +537,7 @@ impl FlatSamples { /// Get a reference to a single sample. /// - /// This more restrictive than the method based on `std::ops::Index` but guarantees to properly + /// This more restrictive than the method based on `core::ops::Index` but guarantees to properly /// check all bounds and not panic as long as `Buffer::as_ref` does not do so. /// /// ``` @@ -565,7 +566,7 @@ impl FlatSamples { /// Get a mutable reference to a single sample. /// - /// This more restrictive than the method based on `std::ops::IndexMut` but guarantees to + /// This more restrictive than the method based on `core::ops::IndexMut` but guarantees to /// properly check all bounds and not panic as long as `Buffer::as_ref` does not do so. /// Contrary to conversion to `ViewMut`, this does not require that samples are packed since it /// does not need to convert samples to a color representation. diff --git a/src/image.rs b/src/image.rs index fef9a754bf..c7d0487ea2 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,10 +1,11 @@ #![allow(clippy::too_many_arguments)] -use std::ffi::OsStr; -use std::io::{self, Write}; -use std::mem::size_of; -use std::ops::{Deref, DerefMut}; -use std::path::Path; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; +use core::mem::size_of; +use core::ops::{Deref, DerefMut}; +use crate::animation::Frames; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, ParameterError, @@ -15,7 +16,12 @@ use crate::metadata::Orientation; use crate::traits::Pixel; use crate::ImageBuffer; -use crate::animation::Frames; +#[cfg(feature = "std")] +use std::ffi::OsStr; +#[cfg(feature = "std")] +use std::io::{self, Write}; +#[cfg(feature = "std")] +use std::path::Path; /// An enumeration of supported image formats. /// Not all formats support both encoding and decoding. @@ -83,6 +89,7 @@ impl ImageFormat { /// let format = ImageFormat::from_extension("jpg"); /// assert_eq!(format, Some(ImageFormat::Jpeg)); /// ``` + #[cfg(feature = "std")] #[inline] pub fn from_extension(ext: S) -> Option where @@ -128,6 +135,7 @@ impl ImageFormat { /// /// # Ok::<(), image::error::ImageError>(()) /// ``` + #[cfg(feature = "std")] #[inline] pub fn from_path

(path: P) -> ImageResult where @@ -654,6 +662,7 @@ pub trait ImageDecoder { /// /// This is usually obtained from the Exif metadata, if present. Formats that don't support /// indicating orientation in their image metadata will return `Ok(Orientation::NoTransforms)`. + #[cfg(feature = "std")] fn orientation(&mut self) -> ImageResult { Ok(self .exif_metadata()? diff --git a/src/image_reader/free_functions.rs b/src/image_reader/free_functions.rs index 57fbb72466..bbf4e8b412 100644 --- a/src/image_reader/free_functions.rs +++ b/src/image_reader/free_functions.rs @@ -1,16 +1,25 @@ -use std::fs::File; -use std::io::{BufRead, BufWriter, Seek}; -use std::iter; -use std::path::Path; - -use crate::{codecs::*, ExtendedColorType, ImageReader}; +use alloc::format; +use core::iter; +use crate::codecs::*; use crate::dynimage::DynamicImage; +use crate::error::UnsupportedError; +use crate::error::UnsupportedErrorKind; use crate::error::{ImageError, ImageFormatHint, ImageResult}; -use crate::error::{UnsupportedError, UnsupportedErrorKind}; use crate::image::ImageFormat; #[allow(unused_imports)] // When no features are supported use crate::image::{ImageDecoder, ImageEncoder}; +use crate::ExtendedColorType; + +#[cfg(feature = "std")] +use crate::ImageReader; + +#[cfg(feature = "std")] +use std::fs::File; +#[cfg(feature = "std")] +use std::io::{BufRead, BufWriter, Seek}; +#[cfg(feature = "std")] +use std::path::Path; /// Create a new image from a Reader. /// @@ -18,6 +27,7 @@ use crate::image::{ImageDecoder, ImageEncoder}; /// consider wrapping the reader with a `BufReader::new()`. /// /// Try [`ImageReader`] for more advanced uses. +#[cfg(feature = "std")] pub fn load(r: R, format: ImageFormat) -> ImageResult { let mut reader = ImageReader::new(r); reader.set_format(format); @@ -26,6 +36,7 @@ pub fn load(r: R, format: ImageFormat) -> ImageResult( buffered_write: &mut W, diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index 364f389588..ddade1b701 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -1,14 +1,23 @@ -use std::fs::File; -use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; -use std::path::Path; +use alloc::boxed::Box; use crate::dynimage::DynamicImage; -use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; +use crate::error::ImageFormatHint; +use crate::error::UnsupportedError; +use crate::error::UnsupportedErrorKind; use crate::image::ImageFormat; -use crate::{ImageDecoder, ImageError, ImageResult}; +use crate::ImageDecoder; +use crate::ImageError; +use crate::ImageResult; use super::free_functions; +#[cfg(feature = "std")] +use std::fs::File; +#[cfg(feature = "std")] +use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; +#[cfg(feature = "std")] +use std::path::Path; + /// A multi-format image reader. /// /// Wraps an input reader to facilitate automatic detection of an image's format, appropriate @@ -58,6 +67,7 @@ use super::free_functions; /// /// [`set_format`]: #method.set_format /// [`ImageDecoder`]: ../trait.ImageDecoder.html +#[cfg(feature = "std")] pub struct ImageReader { /// The reader. Should be buffered. inner: R, @@ -67,6 +77,7 @@ pub struct ImageReader { limits: super::Limits, } +#[cfg(feature = "std")] impl<'a, R: 'a + BufRead + Seek> ImageReader { /// Create a new image reader without a preset format. /// @@ -283,6 +294,7 @@ impl<'a, R: 'a + BufRead + Seek> ImageReader { } } +#[cfg(feature = "std")] impl ImageReader> { /// Open a file to read, format will be guessed from path. /// diff --git a/src/image_reader/mod.rs b/src/image_reader/mod.rs index a1673a3cec..16d4e55f4e 100644 --- a/src/image_reader/mod.rs +++ b/src/image_reader/mod.rs @@ -5,6 +5,7 @@ use crate::{error, ColorType, ImageError, ImageResult}; pub(crate) mod free_functions; mod image_reader_type; +#[cfg(feature = "std")] pub use self::image_reader_type::ImageReader; /// Set of supported strict limits for a decoder. diff --git a/src/imageops/affine.rs b/src/imageops/affine.rs index 6e4f14581f..f2e4294b59 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 { diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index 7289c09ac8..ee24aaedc7 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -1,5 +1,6 @@ //! Functions for altering and converting the color of pixelbufs +use alloc::vec::Vec; use num_traits::NumCast; use crate::color::{FromColor, IntoColor, Luma, LumaA}; @@ -8,6 +9,12 @@ use crate::traits::{Pixel, Primitive}; use crate::utils::clamp; use crate::ImageBuffer; +#[cfg(all(not(feature = "std"), feature = "libm"))] +use num_traits::Float as _; + +#[cfg(not(any(feature = "std", feature = "libm")))] +use num_traits::float::FloatCore as _; + type Subpixel = <::Pixel as Pixel>::Subpixel; /// Convert the supplied image to grayscale. Alpha channel is discarded. @@ -218,6 +225,7 @@ where /// just like the css webkit filter hue-rotate(180) /// /// *[See also `huerotate_in_place`.][huerotate_in_place]* +#[cfg(any(feature = "std", feature = "libm"))] pub fn huerotate(image: &I, value: i32) -> ImageBuffer> where I: GenericImageView, @@ -285,6 +293,7 @@ where /// just like the css webkit filter hue-rotate(180) /// /// *[See also `huerotate`.][huerotate]* +#[cfg(any(feature = "std", feature = "libm"))] pub fn huerotate_in_place(image: &mut I, value: i32) where I: GenericImage, diff --git a/src/imageops/fast_blur.rs b/src/imageops/fast_blur.rs index dd40ba4f95..33be5dc0a7 100644 --- a/src/imageops/fast_blur.rs +++ b/src/imageops/fast_blur.rs @@ -1,3 +1,4 @@ +use alloc::{vec, vec::Vec}; use num_traits::clamp; use crate::{ImageBuffer, Pixel, Primitive}; diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index 72e70e5fc8..ab4ebf32e7 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}; @@ -16,23 +16,31 @@ pub use self::affine::{ }; pub use self::sample::{ - blur, filter3x3, interpolate_bilinear, interpolate_nearest, resize, sample_bilinear, - sample_nearest, thumbnail, unsharpen, + filter3x3, interpolate_bilinear, interpolate_nearest, resize, sample_bilinear, sample_nearest, + thumbnail, }; +#[cfg(any(feature = "std", feature = "libm"))] +pub use self::sample::{blur, unsharpen}; + /// Color operations pub use self::colorops::{ brighten, contrast, dither, grayscale, grayscale_alpha, grayscale_with_type, - grayscale_with_type_alpha, huerotate, index_colors, invert, BiLevel, ColorMap, + grayscale_with_type_alpha, index_colors, invert, BiLevel, ColorMap, }; +#[cfg(any(feature = "std", feature = "libm"))] +pub use self::colorops::huerotate; + mod affine; // Public only because of Rust bug: // https://github.com/rust-lang/rust/issues/18241 pub mod colorops; +#[cfg(any(feature = "std", feature = "libm"))] mod fast_blur; mod sample; +#[cfg(any(feature = "std", feature = "libm"))] pub use fast_blur::fast_blur; /// Return a mutable view into an image diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 05a4a43640..5087f3f210 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -3,7 +3,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}; @@ -12,6 +14,12 @@ use crate::traits::{Enlargeable, Pixel, Primitive}; use crate::utils::clamp; use crate::{ImageBuffer, Rgba32FImage}; +#[cfg(all(not(feature = "std"), feature = "libm"))] +use num_traits::Float as _; + +#[cfg(not(any(feature = "std", feature = "libm")))] +use num_traits::float::FloatCore as _; + /// Available Sampling Filters. /// /// ## Examples @@ -137,6 +145,7 @@ impl ToPrimitive for FloatNearest { } // sinc function: the ideal sampling filter. +#[cfg(any(feature = "std", feature = "libm"))] fn sinc(t: f32) -> f32 { let a = t * f32::consts::PI; @@ -148,6 +157,7 @@ fn sinc(t: f32) -> f32 { } // lanczos kernel function. A windowed sinc function. +#[cfg(any(feature = "std", feature = "libm"))] fn lanczos(x: f32, t: f32) -> f32 { if x.abs() < t { sinc(x) * sinc(x / t) @@ -179,17 +189,20 @@ fn bc_cubic_spline(x: f32, b: f32, c: f32) -> f32 { /// The Gaussian Function. /// ```r``` is the standard deviation. +#[cfg(any(feature = "std", feature = "libm"))] pub(crate) fn gaussian(x: f32, r: f32) -> f32 { ((2.0 * f32::consts::PI).sqrt() * r).recip() * (-x.powi(2) / (2.0 * r.powi(2))).exp() } /// Calculate the lanczos kernel with a window of 3 +#[cfg(any(feature = "std", feature = "libm"))] pub(crate) fn lanczos3_kernel(x: f32) -> f32 { lanczos(x, 3.0) } /// Calculate the gaussian function with a /// standard deviation of 0.5 +#[cfg(any(feature = "std", feature = "libm"))] pub(crate) fn gaussian_kernel(x: f32) -> f32 { gaussian(x, 0.5) } @@ -984,14 +997,20 @@ where kernel: Box::new(catmullrom_kernel), support: 2.0, }, + #[cfg(any(feature = "std", feature = "libm"))] FilterType::Gaussian => Filter { kernel: Box::new(gaussian_kernel), support: 3.0, }, + #[cfg(any(feature = "std", feature = "libm"))] FilterType::Lanczos3 => Filter { kernel: Box::new(lanczos3_kernel), support: 3.0, }, + #[cfg(not(any(feature = "std", feature = "libm")))] + FilterType::Gaussian | FilterType::Lanczos3 => { + unimplemented!("Gaussian and Lanczos3 filter types require either std or libm features to be enabled.") + } }; // Note: tmp is not necessarily actually Rgba @@ -1006,6 +1025,7 @@ where /// This method assumes alpha pre-multiplication for images that contain non-constant alpha. /// This method typically assumes that the input is scene-linear light. /// If it is not, color distortion may occur. +#[cfg(any(feature = "std", feature = "libm"))] pub fn blur( image: &I, sigma: f32, @@ -1045,6 +1065,7 @@ where /// If it is not, color distortion may occur. /// /// See [Digital unsharp masking](https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking) for more information. +#[cfg(any(feature = "std", feature = "libm"))] pub fn unsharpen(image: &I, sigma: f32, threshold: i32) -> ImageBuffer> where I: GenericImageView, diff --git a/src/lib.rs b/src/lib.rs index b691d10a82..7d264b76df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,12 @@ // even to people using the crate as a dependency, // so we have to suppress those warnings. #![allow(unexpected_cfgs)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; + +extern crate alloc; #[cfg(all(test, feature = "benchmarks"))] extern crate test; @@ -170,12 +176,18 @@ 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::image_reader::free_functions::{guess_format, load}; -pub use crate::image_reader::{ImageReader, LimitSupport, Limits}; +pub use crate::image_reader::free_functions::guess_format; +pub use crate::image_reader::{LimitSupport, Limits}; + +#[cfg(feature = "std")] +pub use crate::image_reader::free_functions::load; +#[cfg(feature = "std")] +pub use crate::image_reader::ImageReader; pub use crate::dynimage::DynamicImage; @@ -303,6 +315,7 @@ pub mod metadata; //TODO delete this module after a few releases /// deprecated io module the original io module has been renamed to `image_reader` pub mod io { + #[cfg(feature = "std")] #[deprecated(note = "this type has been moved and renamed to image::ImageReader")] /// Deprecated re-export of `ImageReader` as `Reader` pub type Reader = super::ImageReader; diff --git a/src/math/utils.rs b/src/math/utils.rs index 12fa46e20d..223e48697d 100644 --- a/src/math/utils.rs +++ b/src/math/utils.rs @@ -1,6 +1,9 @@ //! Shared mathematical utility functions. -use std::cmp::max; +use core::cmp::max; + +#[cfg(not(feature = "std"))] +use num_traits::float::FloatCore as _; /// 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/metadata.rs b/src/metadata.rs index 5a0ff535eb..7e86bed4af 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,8 +1,11 @@ //! Types describing image metadata +#[cfg(feature = "std")] +use byteorder_lite::ReadBytesExt; +#[cfg(feature = "std")] use std::io::{Cursor, Read}; -use byteorder_lite::{BigEndian, LittleEndian, ReadBytesExt}; +use byteorder_lite::{BigEndian, LittleEndian}; /// Describes the transformations to be applied to the image. /// Compatible with [Exif orientation](https://web.archive.org/web/20200412005226/https://www.impulseadventure.com/photo/exif-orientation.html). @@ -63,6 +66,7 @@ impl Orientation { } } + #[cfg(feature = "std")] pub(crate) fn from_exif_chunk(chunk: &[u8]) -> Option { let mut reader = Cursor::new(chunk); diff --git a/src/traits.rs b/src/traits.rs index 325926d2ad..ed40ee1850 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::{Luma, LumaA, Rgb, Rgba}; use crate::ExtendedColorType; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f64834e28d..b59fd2ee6d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,8 @@ //! Utilities -use std::iter::repeat; +use alloc::borrow::ToOwned; +use alloc::vec::Vec; +use core::iter::repeat; #[inline(always)] pub(crate) fn expand_packed(buf: &mut [u8], channels: usize, bit_depth: u8, mut func: F) From 4f7783291e05954cbc3f72dd90c45e826601cb73 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:19:45 +1000 Subject: [PATCH 05/42] Add Clippy lints for easier `no_std` maintenance Highlights when `std` is being used when it doesn't have to be. --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7d264b76df..a8395268b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,9 @@ // even to people using the crate as a dependency, // so we have to suppress those warnings. #![allow(unexpected_cfgs)] +#![warn(clippy::alloc_instead_of_core)] +#![warn(clippy::std_instead_of_alloc)] +#![warn(clippy::alloc_instead_of_core)] #![no_std] #[cfg(feature = "std")] From 8449f87df7d497457b4916ccce97668011c2b51d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:20:02 +1000 Subject: [PATCH 06/42] Respond to Clippy lints --- src/buffer.rs | 1 + src/codecs/bmp/decoder.rs | 2 +- src/codecs/dds.rs | 7 +------ src/codecs/dxt.rs | 4 +--- src/codecs/gif.rs | 4 +--- src/codecs/ico/decoder.rs | 8 ++++---- src/codecs/ico/mod.rs | 1 - src/codecs/jpeg/encoder.rs | 2 -- src/codecs/pcx.rs | 4 ++-- src/codecs/pnm/decoder.rs | 8 ++------ src/codecs/tga/decoder.rs | 6 +++--- src/codecs/tiff.rs | 3 +-- src/color.rs | 12 ++++++------ src/dynimage.rs | 9 +++++---- src/error.rs | 3 +-- src/flat.rs | 6 +++--- src/image.rs | 19 +++++++++---------- src/image_reader/free_functions.rs | 9 ++++++--- src/image_reader/image_reader_type.rs | 12 ++++++++++-- src/image_reader/mod.rs | 2 -- src/imageops/affine.rs | 2 +- src/imageops/colorops.rs | 10 +++++----- src/imageops/sample.rs | 26 +++++++++++++------------- src/metadata.rs | 1 + src/utils/mod.rs | 4 +--- 25 files changed, 78 insertions(+), 87 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index cb380f05cd..9ae2e6a4d6 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -10,6 +10,7 @@ use num_traits::Zero; use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; use crate::error::ImageResult; use crate::flat::{FlatSamples, SampleLayout}; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::image::ImageFormat; use crate::image::{GenericImage, GenericImageView, ImageEncoder}; use crate::math::Rect; diff --git a/src/codecs/bmp/decoder.rs b/src/codecs/bmp/decoder.rs index 16edbc0731..7f7b61c94f 100644 --- a/src/codecs/bmp/decoder.rs +++ b/src/codecs/bmp/decoder.rs @@ -1170,7 +1170,7 @@ impl BmpDecoder { _ => { let mut length = op as usize; if self.image_type == ImageType::RLE4 { - length = (length + 1) / 2; + length = length.div_ceil(2); } length += length & 1; let mut buffer = vec![0; length]; diff --git a/src/codecs/dds.rs b/src/codecs/dds.rs index f6b80118d2..b07ede102f 100644 --- a/src/codecs/dds.rs +++ b/src/codecs/dds.rs @@ -13,7 +13,6 @@ use std::io::Read; use byteorder_lite::{LittleEndian, ReadBytesExt}; -#[allow(deprecated)] use crate::codecs::dxt::{DxtDecoder, DxtVariant}; use crate::color::ColorType; use crate::error::{ @@ -23,7 +22,7 @@ use crate::image::{ImageDecoder, ImageFormat}; /// Errors that can occur during decoding and parsing a DDS image #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[allow(clippy::enum_variant_names)] +#[expect(clippy::enum_variant_names)] enum DecoderError { /// Wrong DDS channel width PixelFormatSizeInvalid(u32), @@ -244,7 +243,6 @@ impl DX10Header { /// The representation of a DDS decoder pub struct DdsDecoder { - #[allow(deprecated)] inner: DxtDecoder, } @@ -260,7 +258,6 @@ impl DdsDecoder { let header = Header::from_reader(&mut r)?; if header.pixel_format.flags & 0x4 != 0 { - #[allow(deprecated)] let variant = match &header.pixel_format.fourcc { b"DXT1" => DxtVariant::DXT1, b"DXT3" => DxtVariant::DXT3, @@ -297,7 +294,6 @@ impl DdsDecoder { } }; - #[allow(deprecated)] let bytes_per_pixel = variant.color_type().bytes_per_pixel(); if crate::utils::check_dimension_overflow(header.width, header.height, bytes_per_pixel) @@ -313,7 +309,6 @@ impl DdsDecoder { )); } - #[allow(deprecated)] let inner = DxtDecoder::new(r, header.width, header.height, variant)?; Ok(Self { inner }) } else { diff --git a/src/codecs/dxt.rs b/src/codecs/dxt.rs index a8f348939d..22073be800 100644 --- a/src/codecs/dxt.rs +++ b/src/codecs/dxt.rs @@ -108,7 +108,6 @@ impl DxtDecoder { assert_eq!( u64::try_from(buf.len()), Ok( - #[allow(deprecated)] self.scanline_bytes() ) ); @@ -140,7 +139,6 @@ impl ImageDecoder for DxtDecoder { fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); - #[allow(deprecated)] for chunk in buf.chunks_mut(self.scanline_bytes().max(1) as usize) { self.read_scanline(chunk)?; } @@ -226,7 +224,7 @@ fn decode_dxt_colors(source: &[u8], dest: &mut [u8], is_dxt1: bool) { } else { // linearly interpolate one other entry, keep the other at 0 for i in 0..3 { - colors[2][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) + 1) / 2) as u8; + colors[2][i] = (u16::from(colors[0][i]) + u16::from(colors[1][i])).div_ceil(2) as u8; } } diff --git a/src/codecs/gif.rs b/src/codecs/gif.rs index 013b14ddbe..4dcec922fb 100644 --- a/src/codecs/gif.rs +++ b/src/codecs/gif.rs @@ -25,7 +25,6 @@ //! # Ok(()) //! # } //! ``` -#![allow(clippy::while_let_loop)] use alloc::borrow::ToOwned; use alloc::boxed::Box; @@ -72,10 +71,9 @@ impl GifDecoder { } /// Wrapper struct around a `Cursor>` -#[allow(dead_code)] #[deprecated] pub struct GifReader(Cursor>, PhantomData); -#[allow(deprecated)] +#[expect(deprecated)] impl Read for GifReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) diff --git a/src/codecs/ico/decoder.rs b/src/codecs/ico/decoder.rs index 71528b024f..da64fa051b 100644 --- a/src/codecs/ico/decoder.rs +++ b/src/codecs/ico/decoder.rs @@ -123,18 +123,18 @@ struct DirEntry { height: u8, // We ignore some header fields as they will be replicated in the PNG, BMP and they are not // necessary for determining the best_entry. - #[allow(unused)] + #[expect(unused)] color_count: u8, // Wikipedia has this to say: // Although Microsoft's technical documentation states that this value must be zero, the icon // encoder built into .NET (System.Drawing.Icon.Save) sets this value to 255. It appears that // the operating system ignores this value altogether. - #[allow(unused)] + #[expect(unused)] reserved: u8, // We ignore some header fields as they will be replicated in the PNG, BMP and they are not // necessary for determining the best_entry. - #[allow(unused)] + #[expect(unused)] num_color_planes: u16, bits_per_pixel: u16, @@ -337,7 +337,7 @@ impl ImageDecoder for IcoDecoder { let data_end = u64::from(self.selected_entry.image_offset) + u64::from(self.selected_entry.image_length); - let mask_row_bytes = ((width + 31) / 32) * 4; + let mask_row_bytes = width.div_ceil(32) * 4; let mask_length = u64::from(mask_row_bytes) * u64::from(height); // data_end should be image_end + the mask length (mask_row_bytes * height). diff --git a/src/codecs/ico/mod.rs b/src/codecs/ico/mod.rs index 11493ac220..f7b0f852b9 100644 --- a/src/codecs/ico/mod.rs +++ b/src/codecs/ico/mod.rs @@ -7,7 +7,6 @@ //! * pub use self::decoder::IcoDecoder; -#[allow(deprecated)] pub use self::encoder::{IcoEncoder, IcoFrame}; mod decoder; diff --git a/src/codecs/jpeg/encoder.rs b/src/codecs/jpeg/encoder.rs index 9e90fbefe1..07caf708b0 100644 --- a/src/codecs/jpeg/encoder.rs +++ b/src/codecs/jpeg/encoder.rs @@ -1,5 +1,3 @@ -#![allow(clippy::too_many_arguments)] - use crate::error::{ ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, diff --git a/src/codecs/pcx.rs b/src/codecs/pcx.rs index ff29e22cea..06ad413a38 100644 --- a/src/codecs/pcx.rs +++ b/src/codecs/pcx.rs @@ -45,10 +45,10 @@ impl ImageError { } /// Wrapper struct around a `Cursor>` -#[allow(dead_code)] +#[expect(dead_code)] #[deprecated] pub struct PCXReader(Cursor>, PhantomData); -#[allow(deprecated)] +#[expect(deprecated)] impl Read for PCXReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) diff --git a/src/codecs/pnm/decoder.rs b/src/codecs/pnm/decoder.rs index 91b65bf9ef..cd03fdc9d6 100644 --- a/src/codecs/pnm/decoder.rs +++ b/src/codecs/pnm/decoder.rs @@ -48,7 +48,6 @@ enum DecoderError { /// At least one of the required lines were missing from the header (are `None` here) /// /// Same names as [`PnmHeaderLine`](enum.PnmHeaderLine.html) - #[allow(missing_docs)] HeaderLineMissing { height: Option, width: Option, @@ -375,7 +374,6 @@ trait HeaderReader: Read { let mut bytes = Vec::new(); // pair input bytes with a bool mask to remove comments - #[allow(clippy::unbuffered_bytes)] let mark_comments = self.bytes().scan(true, |partof, read| { let byte = match read { Err(err) => return Some((*partof, Err(err))), @@ -495,7 +493,6 @@ trait HeaderReader: Read { } } - #[allow(clippy::unbuffered_bytes)] match self.bytes().next() { None => return Err(ImageError::IoError(io::ErrorKind::UnexpectedEof.into())), Some(Err(io)) => return Err(ImageError::IoError(io)), @@ -520,7 +517,7 @@ trait HeaderReader: Read { if !line.is_ascii() { return Err(DecoderError::NonAsciiLineInPamHeader.into()); } - #[allow(deprecated)] + #[expect(deprecated)] let (identifier, rest) = line .trim_left() .split_at(line.find(char::is_whitespace).unwrap_or(line.len())); @@ -696,7 +693,6 @@ fn read_separated_ascii>(reader: &mut dyn Read) -> ImageResult ImageResult<()> { let mut expanded = utils::expand_bits(1, row_size.try_into().unwrap(), bytes); + #[expect(clippy::manual_slice_fill)] for b in &mut expanded { *b = !*b; } @@ -778,7 +775,6 @@ impl Sample for PbmBit { } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { - #[allow(clippy::unbuffered_bytes)] let mut bytes = reader.bytes(); for b in output_buf { loop { diff --git a/src/codecs/tga/decoder.rs b/src/codecs/tga/decoder.rs index ef2513416a..dd45ca7aa9 100644 --- a/src/codecs/tga/decoder.rs +++ b/src/codecs/tga/decoder.rs @@ -26,7 +26,7 @@ impl ColorMap { num_entries: u16, bits_per_entry: u8, ) -> ImageResult { - let bytes_per_entry = (bits_per_entry as usize + 7) / 8; + let bytes_per_entry = (bits_per_entry as usize).div_ceil(8); let mut bytes = vec![0; bytes_per_entry * num_entries as usize]; r.read_exact(&mut bytes)?; @@ -89,7 +89,7 @@ impl TgaDecoder { self.image_type = ImageType::new(self.header.image_type); self.width = self.header.image_width as usize; self.height = self.header.image_height as usize; - self.bytes_per_pixel = (self.header.pixel_depth as usize + 7) / 8; + self.bytes_per_pixel = (self.header.pixel_depth as usize).div_ceil(8); Ok(()) } @@ -204,7 +204,7 @@ impl TgaDecoder { result } - let bytes_per_entry = (self.header.map_entry_size as usize + 7) / 8; + let bytes_per_entry = (self.header.map_entry_size as usize).div_ceil(8); let mut result = Vec::with_capacity(self.width * self.height * bytes_per_entry); if self.bytes_per_pixel == 0 { diff --git a/src/codecs/tiff.rs b/src/codecs/tiff.rs index 440c5d2898..4fbcbefc84 100644 --- a/src/codecs/tiff.rs +++ b/src/codecs/tiff.rs @@ -172,10 +172,9 @@ impl ImageError { } /// Wrapper struct around a `Cursor>` -#[allow(dead_code)] #[deprecated] pub struct TiffReader(Cursor>, PhantomData); -#[allow(deprecated)] +#[expect(deprecated)] impl Read for TiffReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) diff --git a/src/color.rs b/src/color.rs index 5235ed1bb9..8244be8dde 100644 --- a/src/color.rs +++ b/src/color.rs @@ -234,9 +234,10 @@ impl ExtendedColorType { } /// Returns the number of bytes required to hold a width x height image of this color type. + #[cfg_attr(not(feature = "std"), expect(dead_code))] pub(crate) fn buffer_size(self, width: u32, height: u32) -> u64 { let bpp = self.bits_per_pixel() as u64; - let row_pitch = (width as u64 * bpp + 7) / 8; + let row_pitch = (width as u64 * bpp).div_ceil(8); row_pitch.saturating_mul(height as u64) } } @@ -446,7 +447,7 @@ impl FromPrimitive for T { // 1.0 (white) was picked as firefox and chrome choose to map NaN to that. #[inline] fn normalize_float(float: f32, max: f32) -> f32 { - #[allow(clippy::neg_cmp_op_on_partial_ord)] + #[expect(clippy::neg_cmp_op_on_partial_ord)] let clamped = if !(float < 1.0) { 1.0 } else { float.max(0.0) }; (clamped * max).round() } @@ -504,7 +505,7 @@ impl FromPrimitive for u16 { /// Provides color conversions for the different pixel types. pub trait FromColor { /// Changes `self` to represent `Other` in the color space of `Self` - #[allow(clippy::wrong_self_convention)] + #[expect(clippy::wrong_self_convention)] fn from_color(&mut self, _: &Other); } @@ -513,7 +514,7 @@ pub trait FromColor { // rather than assuming sRGB. pub(crate) trait IntoColor { /// Constructs a pixel of the target type and converts this pixel into it. - #[allow(clippy::wrong_self_convention)] + #[expect(clippy::wrong_self_convention)] fn into_color(&self) -> Other; } @@ -521,11 +522,10 @@ impl IntoColor for S where O: Pixel + FromColor, { - #[allow(clippy::wrong_self_convention)] fn into_color(&self) -> O { // Note we cannot use Pixel::CHANNELS_COUNT here to directly construct // the pixel due to a current bug/limitation of consts. - #[allow(deprecated)] + #[expect(deprecated)] let mut pix = O::from_channels(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()); pix.from_color(self); pix diff --git a/src/dynimage.rs b/src/dynimage.rs index b4d1234179..4ccd38ddc5 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -18,13 +18,16 @@ use crate::buffer_::{ use crate::color::{self, FromColor, IntoColor}; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::flat::FlatSamples; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::image::ImageFormat; use crate::image::{GenericImage, GenericImageView, ImageDecoder, ImageEncoder}; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::image_reader::free_functions; use crate::imageops; use crate::math::resize_dimensions; use crate::metadata::Orientation; use crate::traits::Pixel; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ExtendedColorType; use crate::{image, Luma, LumaA}; use crate::{Rgb32FImage, Rgba32FImage}; @@ -1050,7 +1053,6 @@ impl DynamicImage { // TODO do not repeat this match statement across the crate - #[allow(deprecated)] match format { #[cfg(feature = "png")] ImageFormat::Png => { @@ -1172,7 +1174,6 @@ impl From, Vec>> for DynamicImage { } } -#[allow(deprecated)] impl GenericImageView for DynamicImage { type Pixel = color::Rgba; // TODO use f32 as default for best precision and unbounded color? @@ -1185,7 +1186,7 @@ impl GenericImageView for DynamicImage { } } -#[allow(deprecated)] +#[expect(deprecated)] impl GenericImage for DynamicImage { fn put_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba) { match *self { @@ -1541,7 +1542,7 @@ mod test { // Test that structs wrapping a DynamicImage are able to auto-derive the Default trait // ensures that DynamicImage implements Default (if it didn't, this would cause a compile error). #[derive(Default)] - #[allow(dead_code)] + #[expect(dead_code)] struct Foo { _image: super::DynamicImage, } diff --git a/src/error.rs b/src/error.rs index 77ea420259..4790226bca 100644 --- a/src/error.rs +++ b/src/error.rs @@ -164,7 +164,6 @@ pub struct LimitError { /// detailed information or to incorporate other resources types. #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[non_exhaustive] -#[allow(missing_copy_implementations)] // Might be non-Copy in the future. pub enum LimitErrorKind { /// The resulting image exceed dimension limits in either direction. DimensionError, @@ -515,7 +514,7 @@ mod tests { use super::*; use core::mem::size_of; - #[allow(dead_code)] + #[expect(dead_code)] // This will fail to compile if the size of this type is large. const ASSERT_SMALLISH: usize = [0][(size_of::() >= 200) as usize]; diff --git a/src/flat.rs b/src/flat.rs index 185cc6ce1e..8c02770cfd 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -1465,12 +1465,12 @@ where P::from_slice_mut(&mut self.inner.samples.as_mut()[pixel_range]) } - #[allow(deprecated)] + #[expect(deprecated)] fn put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { *self.get_pixel_mut(x, y) = pixel; } - #[allow(deprecated)] + #[expect(deprecated)] fn blend_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { self.get_pixel_mut(x, y).blend(&pixel); } @@ -1623,7 +1623,7 @@ mod tests { .as_view_mut::>() .expect("This should be a valid mutable buffer"); assert_eq!(view.dimensions(), (3, 3)); - #[allow(deprecated)] + #[expect(deprecated)] for i in 0..9 { *view.get_pixel_mut(i % 3, i / 3) = LumaA([2 * i as u16, 2 * i as u16 + 1]); } diff --git a/src/image.rs b/src/image.rs index c7d0487ea2..b9cbd370b9 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,4 +1,3 @@ -#![allow(clippy::too_many_arguments)] use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; @@ -12,6 +11,7 @@ use crate::error::{ ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::math::Rect; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::metadata::Orientation; use crate::traits::Pixel; use crate::ImageBuffer; @@ -401,8 +401,6 @@ impl ImageFormat { // This struct manages buffering associated with implementing `Read` and `Seek` on decoders that can // must decode ranges of bytes at a time. -#[allow(dead_code)] -// When no image formats that use it are enabled pub(crate) struct ImageReadBuffer { scanline_bytes: usize, buffer: Vec, @@ -417,7 +415,7 @@ impl ImageReadBuffer { /// Panics if `scanline_bytes` doesn't fit into a usize, because that would mean reading anything /// from the image would take more RAM than the entire virtual address space. In other words, /// actually using this struct would instantly OOM so just get it out of the way now. - #[allow(dead_code)] + #[expect(dead_code)] // When no image formats that use it are enabled pub(crate) fn new(scanline_bytes: u64, total_bytes: u64) -> Self { Self { @@ -429,7 +427,8 @@ impl ImageReadBuffer { } } - #[allow(dead_code)] + #[cfg(feature = "std")] + #[expect(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 where @@ -476,8 +475,8 @@ impl ImageReadBuffer { /// Decodes a specific region of the image, represented by the rectangle /// starting from ```x``` and ```y``` and having ```length``` and ```width``` -#[allow(dead_code)] -// When no image formats that use it are enabled +#[cfg(feature = "std")] +#[expect(clippy::too_many_arguments)] pub(crate) fn load_rect( x: u32, y: u32, @@ -1289,7 +1288,6 @@ where } } -#[allow(deprecated)] impl GenericImageView for SubImageInner where I: Deref, @@ -1306,13 +1304,13 @@ where } } -#[allow(deprecated)] impl GenericImage for SubImageInner where I: DerefMut, I::Target: GenericImage + Sized, { fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut Self::Pixel { + #[expect(deprecated)] self.image.get_pixel_mut(x + self.xoffset, y + self.yoffset) } @@ -1323,6 +1321,7 @@ where /// DEPRECATED: This method will be removed. Blend the pixel directly instead. fn blend_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { + #[expect(deprecated)] self.image .blend_pixel(x + self.xoffset, y + self.yoffset, pixel); } @@ -1343,7 +1342,7 @@ mod tests { use crate::{GrayImage, ImageBuffer}; #[test] - #[allow(deprecated)] + #[expect(deprecated)] /// Test that alpha blending works as expected fn test_image_alpha_blending() { let mut target = ImageBuffer::new(1, 1); diff --git a/src/image_reader/free_functions.rs b/src/image_reader/free_functions.rs index bbf4e8b412..c5620687ec 100644 --- a/src/image_reader/free_functions.rs +++ b/src/image_reader/free_functions.rs @@ -1,14 +1,20 @@ +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use alloc::format; use core::iter; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::codecs::*; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::dynimage::DynamicImage; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::error::UnsupportedError; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::error::UnsupportedErrorKind; use crate::error::{ImageError, ImageFormatHint, ImageResult}; use crate::image::ImageFormat; #[allow(unused_imports)] // When no features are supported use crate::image::{ImageDecoder, ImageEncoder}; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ExtendedColorType; #[cfg(feature = "std")] @@ -34,7 +40,6 @@ pub fn load(r: R, format: ImageFormat) -> ImageResult( diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index ddade1b701..da48039ab3 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -1,14 +1,24 @@ +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use alloc::boxed::Box; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::dynimage::DynamicImage; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::error::ImageFormatHint; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::error::UnsupportedError; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::error::UnsupportedErrorKind; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::image::ImageFormat; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ImageDecoder; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ImageError; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ImageResult; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use super::free_functions; #[cfg(feature = "std")] @@ -152,10 +162,8 @@ impl<'a, R: 'a + BufRead + Seek> ImageReader { reader: R, limits_for_png: super::Limits, ) -> ImageResult> { - #[allow(unused)] use crate::codecs::*; - #[allow(unreachable_patterns)] // Default is unreachable if all features are supported. Ok(match format { #[cfg(feature = "avif-native")] diff --git a/src/image_reader/mod.rs b/src/image_reader/mod.rs index 16d4e55f4e..9b248afcdc 100644 --- a/src/image_reader/mod.rs +++ b/src/image_reader/mod.rs @@ -10,7 +10,6 @@ pub use self::image_reader_type::ImageReader; /// Set of supported strict limits for a decoder. #[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] -#[allow(missing_copy_implementations)] #[non_exhaustive] pub struct LimitSupport {} @@ -36,7 +35,6 @@ pub struct LimitSupport {} /// [`LimitSupport`]: ./struct.LimitSupport.html /// [`ImageDecoder::set_limits`]: ../trait.ImageDecoder.html#method.set_limits #[derive(Clone, Debug, Eq, PartialEq, Hash)] -#[allow(missing_copy_implementations)] #[non_exhaustive] pub struct Limits { /// The maximum allowed image width. This limit is strict. The default is no limit. diff --git a/src/imageops/affine.rs b/src/imageops/affine.rs index f2e4294b59..a3e3daefcd 100644 --- a/src/imageops/affine.rs +++ b/src/imageops/affine.rs @@ -398,7 +398,7 @@ mod test { assert_pixels_eq!(&image, &expected); } - #[allow(clippy::type_complexity)] + #[expect(clippy::type_complexity)] fn pixel_diffs(left: &I, right: &J) -> Vec<((u32, u32, P), (u32, u32, P))> where I: GenericImage, diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index ee24aaedc7..71160bb375 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -256,7 +256,7 @@ where for (x, y, pixel) in out.enumerate_pixels_mut() { let p = image.get_pixel(x, y); - #[allow(deprecated)] + #[expect(deprecated)] let (k1, k2, k3, k4) = p.channels4(); let vec: (f64, f64, f64, f64) = ( NumCast::from(k1).unwrap(), @@ -274,7 +274,7 @@ where let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b; let max = 255f64; - #[allow(deprecated)] + #[expect(deprecated)] let outpixel = Pixel::from_channels( NumCast::from(clamp(new_r, 0.0, max)).unwrap(), NumCast::from(clamp(new_g, 0.0, max)).unwrap(), @@ -324,7 +324,7 @@ where for x in 0..width { let pixel = image.get_pixel(x, y); - #[allow(deprecated)] + #[expect(deprecated)] let (k1, k2, k3, k4) = pixel.channels4(); let vec: (f64, f64, f64, f64) = ( @@ -343,7 +343,7 @@ where let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b; let max = 255f64; - #[allow(deprecated)] + #[expect(deprecated)] let outpixel = Pixel::from_channels( NumCast::from(clamp(new_r, 0.0, max)).unwrap(), NumCast::from(clamp(new_g, 0.0, max)).unwrap(), @@ -642,7 +642,7 @@ mod test { assert_pixels_eq!(&image, &expected); } - #[allow(clippy::type_complexity)] + #[expect(clippy::type_complexity)] fn pixel_diffs(left: &I, right: &J) -> Vec<((u32, u32, P), (u32, u32, P))> where I: GenericImage, diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 5087f3f210..ad6f928b90 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -303,7 +303,7 @@ where for (i, w) in ws.iter().enumerate() { let p = image.get_pixel(left + i as u32, y); - #[allow(deprecated)] + #[expect(deprecated)] let vec = p.channels4(); t.0 += vec.0 * w; @@ -312,7 +312,7 @@ where t.3 += vec.3 * w; } - #[allow(deprecated)] + #[expect(deprecated)] let t = Pixel::from_channels( NumCast::from(FloatNearest(clamp(t.0, min, max))).unwrap(), NumCast::from(FloatNearest(clamp(t.1, min, max))).unwrap(), @@ -543,7 +543,7 @@ where for (i, w) in ws.iter().enumerate() { let p = image.get_pixel(x, left + i as u32); - #[allow(deprecated)] + #[expect(deprecated)] let (k1, k2, k3, k4) = p.channels4(); let vec: (f32, f32, f32, f32) = ( NumCast::from(k1).unwrap(), @@ -558,7 +558,7 @@ where t.3 += vec.3 * w; } - #[allow(deprecated)] + #[expect(deprecated)] // This is not necessarily Rgba. let t = Pixel::from_channels(t.0, t.1, t.2, t.3); @@ -587,7 +587,7 @@ impl ThumbnailSum { } fn add_pixel>(&mut self, pixel: P) { - #[allow(deprecated)] + #[expect(deprecated)] let pixel = pixel.channels4(); self.0 += Self::sample_val(pixel.0); self.1 += Self::sample_val(pixel.1); @@ -681,7 +681,7 @@ where ) }; - #[allow(deprecated)] + #[expect(deprecated)] let pixel = Pixel::from_channels(avg.0, avg.1, avg.2, avg.3); out.put_pixel(outx, outy, pixel); } @@ -821,13 +821,13 @@ where P: Pixel, S: Primitive + Enlargeable, { - #[allow(deprecated)] + #[expect(deprecated)] let k_bl = image.get_pixel(left, bottom).channels4(); - #[allow(deprecated)] + #[expect(deprecated)] let k_tl = image.get_pixel(left, bottom + 1).channels4(); - #[allow(deprecated)] + #[expect(deprecated)] let k_br = image.get_pixel(left + 1, bottom).channels4(); - #[allow(deprecated)] + #[expect(deprecated)] let k_tr = image.get_pixel(left + 1, bottom + 1).channels4(); let frac_v = fraction_vertical; @@ -891,7 +891,7 @@ where let max = S::DEFAULT_MAX_VALUE; let max: f32 = NumCast::from(max).unwrap(); - #[allow(clippy::redundant_guards)] + #[expect(clippy::redundant_guards)] let sum = match kernel.iter().fold(0.0, |s, &item| s + item) { x if x == 0.0 => 1.0, sum => sum, @@ -912,7 +912,7 @@ where let p = image.get_pixel(x0 as u32, y0 as u32); - #[allow(deprecated)] + #[expect(deprecated)] let (k1, k2, k3, k4) = p.channels4(); let vec: (f32, f32, f32, f32) = ( @@ -930,7 +930,7 @@ where let (t1, t2, t3, t4) = (t.0 / sum.0, t.1 / sum.1, t.2 / sum.2, t.3 / sum.3); - #[allow(deprecated)] + #[expect(deprecated)] let t = Pixel::from_channels( NumCast::from(clamp(t1, 0.0, max)).unwrap(), NumCast::from(clamp(t2, 0.0, max)).unwrap(), diff --git a/src/metadata.rs b/src/metadata.rs index 7e86bed4af..c6b81722c4 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -5,6 +5,7 @@ use byteorder_lite::ReadBytesExt; #[cfg(feature = "std")] use std::io::{Cursor, Read}; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use byteorder_lite::{BigEndian, LittleEndian}; /// Describes the transformations to be applied to the image. diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b59fd2ee6d..802caaa765 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -34,7 +34,6 @@ where /// Expand a buffer of packed 1, 2, or 4 bits integers into u8's. Assumes that /// every `row_size` entries there are padding bits up to the next byte boundary. -#[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec { // Note: this conversion assumes that the scanlines begin on byte boundaries @@ -65,13 +64,12 @@ pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec { } /// Checks if the provided dimensions would cause an overflow. -#[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 { u64::from(width) * u64::from(height) > u64::MAX / u64::from(bytes_per_pixel) } -#[allow(dead_code)] +#[expect(dead_code)] // When no image formats that use it are enabled pub(crate) fn vec_copy_to_u8(vec: &[T]) -> Vec where From 71d362a2fdaccef1592ffe8b9a8a5cac8432599b Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:20:48 +1000 Subject: [PATCH 07/42] Add `no_std` CI task Testing against `wasm32v1-none` as a target which is known to not have an implementation of `std`, making compilation a sufficient test for general `no_std` compatibility. --- .github/workflows/rust.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 66b5eeb07f..1ebd67e037 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -207,3 +207,15 @@ jobs: with: feature-group: default-features release-type: minor + + clippy_no_std: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + targets: "wasm32v1-none" + - run: cargo clippy --no-default-features --target wasm32v1-none --features serde -- -D warnings + env: + SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always From 112b846ca2f85590e2c874ffca968608de739807 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:46:34 +1000 Subject: [PATCH 08/42] Formatting --- src/codecs/dxt.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/codecs/dxt.rs b/src/codecs/dxt.rs index 22073be800..12c19b6e21 100644 --- a/src/codecs/dxt.rs +++ b/src/codecs/dxt.rs @@ -105,12 +105,7 @@ impl DxtDecoder { } fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result { - assert_eq!( - u64::try_from(buf.len()), - Ok( - self.scanline_bytes() - ) - ); + assert_eq!(u64::try_from(buf.len()), Ok(self.scanline_bytes())); let mut src = vec![0u8; self.variant.encoded_bytes_per_block() * self.width_blocks as usize]; From a68e050f124abe5634f059928a06bc74d70475a4 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:49:11 +1000 Subject: [PATCH 09/42] `dead_code` --- src/image.rs | 3 ++- src/utils/mod.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/image.rs b/src/image.rs index b9cbd370b9..fbfda34c70 100644 --- a/src/image.rs +++ b/src/image.rs @@ -401,11 +401,12 @@ impl ImageFormat { // This struct manages buffering associated with implementing `Read` and `Seek` on decoders that can // must decode ranges of bytes at a time. +#[allow(dead_code)] +// When no image formats that use it are enabled pub(crate) struct ImageReadBuffer { scanline_bytes: usize, buffer: Vec, consumed: usize, - total_bytes: u64, offset: u64, } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 802caaa765..b59fd2ee6d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -34,6 +34,7 @@ where /// Expand a buffer of packed 1, 2, or 4 bits integers into u8's. Assumes that /// every `row_size` entries there are padding bits up to the next byte boundary. +#[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec { // Note: this conversion assumes that the scanlines begin on byte boundaries @@ -64,12 +65,13 @@ pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec { } /// Checks if the provided dimensions would cause an overflow. +#[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 { u64::from(width) * u64::from(height) > u64::MAX / u64::from(bytes_per_pixel) } -#[expect(dead_code)] +#[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn vec_copy_to_u8(vec: &[T]) -> Vec where From dbd54679bbe279ff5548cf1ed58ea588e50394df Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:53:28 +1000 Subject: [PATCH 10/42] Leave `std` prelude for tests --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a8395268b3..2d541e81c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,7 +129,7 @@ #![warn(clippy::alloc_instead_of_core)] #![warn(clippy::std_instead_of_alloc)] #![warn(clippy::alloc_instead_of_core)] -#![no_std] +#![cfg_attr(not(test), no_std)] #[cfg(feature = "std")] extern crate std; From fefc0a35312ec4d4df18a472a7e7ba0238393905 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:55:29 +1000 Subject: [PATCH 11/42] Don't test without `std` `no-default-features` is separately checked using Clippy --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1ebd67e037..f039e15b23 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - features: ['', default, rayon, avif, bmp, dds, exr, ff, gif, hdr, ico, jpeg, png, pnm, qoi, tga, tiff, webp] + features: [std, default, rayon, avif, bmp, dds, exr, ff, gif, hdr, ico, jpeg, png, pnm, qoi, tga, tiff, webp] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable From a4f65efa211502647e157c4838d305b8f9693b92 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 15:57:30 +1000 Subject: [PATCH 12/42] Bumped `image` to `0.26.0` for Sematic-Versioning --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e38182db46..0537d0d4be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "image" -version = "0.25.6" +version = "0.26.0" edition = "2021" resolver = "2" From 46e026f99d9ab3cf4fdf40537869513eca4478e0 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 16:08:58 +1000 Subject: [PATCH 13/42] Fixed `avif` Clippy --- src/codecs/avif/decoder.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/codecs/avif/decoder.rs b/src/codecs/avif/decoder.rs index acbd3e6223..f44884e8fe 100644 --- a/src/codecs/avif/decoder.rs +++ b/src/codecs/avif/decoder.rs @@ -4,6 +4,11 @@ use crate::error::{ UnsupportedErrorKind, }; use crate::{ColorType, ImageDecoder, ImageError, ImageFormat, ImageResult}; +use alloc::boxed::Box; +use alloc::format; +use alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; /// /// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec. /// From be6da9255ab5f8fdcbe827694d773d7decee57ed Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 16:10:25 +1000 Subject: [PATCH 14/42] Revert "Bumped `image` to `0.26.0` for Sematic-Versioning" This reverts commit a4f65efa211502647e157c4838d305b8f9693b92. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0537d0d4be..e38182db46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "image" -version = "0.26.0" +version = "0.25.6" edition = "2021" resolver = "2" From 9e5048ac8074573e4bff579fe4299492a824e5bf Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 16:22:29 +1000 Subject: [PATCH 15/42] Nightly Lints --- benches/encode.rs | 2 +- src/codecs/avif/decoder.rs | 4 ++-- src/codecs/avif/yuv.rs | 4 ++-- src/codecs/pnm/decoder.rs | 5 ++++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/benches/encode.rs b/benches/encode.rs index c6339b5cb1..5f74cc9bc5 100644 --- a/benches/encode.rs +++ b/benches/encode.rs @@ -52,7 +52,7 @@ type BenchGroup<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::Wall /// For compressed formats this is surely not representative of encoding a normal image but it's a /// start for benchmarking. fn encode_zeroed(group: &mut BenchGroup, with: &dyn Encoder, size: u32, color: ExtendedColorType) { - let im = vec![0; (color.bits_per_pixel() as usize * size as usize + 7) / 8 * size as usize]; + let im = vec![0; (color.bits_per_pixel() as usize * size as usize).div_ceil(8) * size as usize]; group.bench_with_input( BenchmarkId::new(format!("zero-{color:?}-rawvec"), size), diff --git a/src/codecs/avif/decoder.rs b/src/codecs/avif/decoder.rs index f44884e8fe..c9863fdbeb 100644 --- a/src/codecs/avif/decoder.rs +++ b/src/codecs/avif/decoder.rs @@ -200,12 +200,12 @@ fn transmute_chroma_plane16( let mut shape_chroma_plane = || { chroma_plane_stride = match pixel_layout { PixelLayout::I400 => unreachable!(), - PixelLayout::I420 | PixelLayout::I422 => (width + 1) / 2, + PixelLayout::I420 | PixelLayout::I422 => width.div_ceil(2), PixelLayout::I444 => width, }; let u_plane_height = match pixel_layout { PixelLayout::I400 => unreachable!(), - PixelLayout::I420 => (height + 1) / 2, + PixelLayout::I420 => height.div_ceil(2), PixelLayout::I422 | PixelLayout::I444 => height, }; bind_chroma = reshape_plane(plane_ref, stride, chroma_plane_stride, u_plane_height); diff --git a/src/codecs/avif/yuv.rs b/src/codecs/avif/yuv.rs index dda0fda36f..313a932af4 100644 --- a/src/codecs/avif/yuv.rs +++ b/src/codecs/avif/yuv.rs @@ -521,7 +521,7 @@ fn process_halved_chroma_row< // preventing accidental use of invalid values from the trailing region. let y_plane = &image.y_plane[0..image.width]; - let chroma_size = (image.width + 1) / 2; + let chroma_size = image.width.div_ceil(2); let u_plane = &image.u_plane[0..chroma_size]; let v_plane = &image.v_plane[0..chroma_size]; let rgba = &mut rgba[0..image.width * CHANNELS]; @@ -662,7 +662,7 @@ where let y_stride = image.y_stride; let u_stride = image.u_stride; let v_stride = image.v_stride; - let chroma_height = (image.height + 1) / 2; + let chroma_height = image.height.div_ceil(2); check_yuv_plane_preconditions(y_plane, PlaneDefinition::Y, y_stride, image.height)?; check_yuv_plane_preconditions(u_plane, PlaneDefinition::U, u_stride, chroma_height)?; diff --git a/src/codecs/pnm/decoder.rs b/src/codecs/pnm/decoder.rs index cd03fdc9d6..bbe9acb977 100644 --- a/src/codecs/pnm/decoder.rs +++ b/src/codecs/pnm/decoder.rs @@ -374,6 +374,7 @@ trait HeaderReader: Read { let mut bytes = Vec::new(); // pair input bytes with a bool mask to remove comments + #[allow(clippy::unbuffered_bytes)] let mark_comments = self.bytes().scan(true, |partof, read| { let byte = match read { Err(err) => return Some((*partof, Err(err))), @@ -493,6 +494,7 @@ trait HeaderReader: Read { } } + #[allow(clippy::unbuffered_bytes)] match self.bytes().next() { None => return Err(ImageError::IoError(io::ErrorKind::UnexpectedEof.into())), Some(Err(io)) => return Err(ImageError::IoError(io)), @@ -693,6 +695,7 @@ fn read_separated_ascii>(reader: &mut dyn Read) -> ImageResult ImageResult<()> { let mut expanded = utils::expand_bits(1, row_size.try_into().unwrap(), bytes); - #[expect(clippy::manual_slice_fill)] for b in &mut expanded { *b = !*b; } @@ -775,6 +777,7 @@ impl Sample for PbmBit { } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { + #[allow(clippy::unbuffered_bytes)] let mut bytes = reader.bytes(); for b in output_buf { loop { From 8712e320b781b1ba258f0651c52f6b0340c4eb4c Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 8 May 2025 16:41:16 +1000 Subject: [PATCH 16/42] Ensure `num-bigint` is at-least 0.4.2 to avoid `div_ceil` bug https://github.com/rust-num/num-bigint/pull/219 --- Cargo.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e38182db46..cc4aabc507 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ num-traits = { version = "0.2.0", default-features = false } # Optional dependencies color_quant = { version = "1.1", optional = true } -dav1d = { version = "0.10.3", optional = true } +dav1d = { version = "0.10.4", optional = true } exr = { version = "1.5.0", optional = true } gif = { version = "0.13.1", optional = true } image-webp = { version = "0.2.0", optional = true } @@ -56,6 +56,9 @@ zune-core = { version = "0.4.12", default-features = false, optional = true } zune-jpeg = { version = "0.4.13", optional = true } serde = { version = "1.0.214", default-features = false, optional = true, features = ["derive"] } +# Patches +num-bigint = { version = "0.4.2", default-features = false, optional = true } + [dev-dependencies] crc32fast = "1.2.0" num-complex = "0.4" @@ -68,7 +71,7 @@ default = ["std", "rayon", "default-formats"] # Format features default-formats = ["avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"] -avif = ["dep:ravif", "dep:rgb", "std"] +avif = ["dep:ravif", "dep:rgb", "std", "dep:num-bigint"] bmp = ["std"] dds = ["std"] exr = ["dep:exr", "std"] @@ -88,7 +91,7 @@ webp = ["dep:image-webp", "std"] rayon = ["dep:rayon", "ravif?/threading", "std"] # Enables multi-threading nasm = ["ravif?/asm", "std"] # Enables use of nasm by rav1e (requires nasm to be installed) color_quant = ["dep:color_quant", "std"] # Enables color quantization -avif-native = ["dep:mp4parse", "dep:dav1d", "std"] # Enable native dependency libdav1d +avif-native = ["dep:mp4parse", "dep:dav1d", "std", "dep:num-bigint"] # Enable native dependency libdav1d benchmarks = ["std"] # Build some inline benchmarks. Useful only during development (requires nightly Rust) serde = ["dep:serde"] std = ["byteorder-lite/std", "num-traits/std"] From cfbd0586c08b4082fb90686f897c06b233ddb3a2 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 9 May 2025 08:57:21 +1000 Subject: [PATCH 17/42] Bump major version Verified with `cargo semver-checks check-release --default-features` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cc4aabc507..c977d1c276 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "image" -version = "0.25.6" +version = "0.26.0" edition = "2021" resolver = "2" From 1546c2cbf8bce47cf782871bc10a96827a80ff04 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 9 May 2025 22:33:12 +1000 Subject: [PATCH 18/42] Fix missing `f32`/`f64` ops --- src/imageops/fast_blur.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/imageops/fast_blur.rs b/src/imageops/fast_blur.rs index 33be5dc0a7..f4633b4747 100644 --- a/src/imageops/fast_blur.rs +++ b/src/imageops/fast_blur.rs @@ -3,6 +3,12 @@ use num_traits::clamp; use crate::{ImageBuffer, Pixel, Primitive}; +#[cfg(all(not(feature = "std"), feature = "libm"))] +use num_traits::Float as _; + +#[cfg(not(any(feature = "std", feature = "libm")))] +use num_traits::float::FloatCore as _; + /// Approximation of Gaussian blur. /// /// # Arguments From a32ba19b61c564717228910513d98724fb827871 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 9 May 2025 22:34:20 +1000 Subject: [PATCH 19/42] `no_std` metadata.rs --- src/image.rs | 2 - src/metadata.rs | 114 ++++++++++++++++++++++++++++++------------------ 2 files changed, 71 insertions(+), 45 deletions(-) diff --git a/src/image.rs b/src/image.rs index fbfda34c70..20a38d15cc 100644 --- a/src/image.rs +++ b/src/image.rs @@ -11,7 +11,6 @@ use crate::error::{ ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::math::Rect; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::metadata::Orientation; use crate::traits::Pixel; use crate::ImageBuffer; @@ -662,7 +661,6 @@ pub trait ImageDecoder { /// /// This is usually obtained from the Exif metadata, if present. Formats that don't support /// indicating orientation in their image metadata will return `Ok(Orientation::NoTransforms)`. - #[cfg(feature = "std")] fn orientation(&mut self) -> ImageResult { Ok(self .exif_metadata()? diff --git a/src/metadata.rs b/src/metadata.rs index c6b81722c4..9e790b639c 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,12 +1,8 @@ //! Types describing image metadata -#[cfg(feature = "std")] -use byteorder_lite::ReadBytesExt; -#[cfg(feature = "std")] -use std::io::{Cursor, Read}; +use core::mem::offset_of; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use byteorder_lite::{BigEndian, LittleEndian}; +use byteorder_lite::{BigEndian, ByteOrder, LittleEndian}; /// Describes the transformations to be applied to the image. /// Compatible with [Exif orientation](https://web.archive.org/web/20200412005226/https://www.impulseadventure.com/photo/exif-orientation.html). @@ -67,46 +63,78 @@ impl Orientation { } } - #[cfg(feature = "std")] pub(crate) fn from_exif_chunk(chunk: &[u8]) -> Option { - let mut reader = Cursor::new(chunk); + match chunk { + [0x49, 0x49, 42, 0, ..] => Self::parse_exif_chunk::(chunk), + [0x4d, 0x4d, 0, 42, ..] => Self::parse_exif_chunk::(chunk), + _ => None, + } + } + + fn parse_exif_chunk(chunk: &[u8]) -> Option { + // First 4 bytes are magic + let mut i = 4usize; + + if chunk.len() < i + size_of::() { + return None; + } + let ifd_offset = T::read_u32(&chunk[i..]); + i = usize::try_from(ifd_offset).ok()?; + + if chunk.len() < i + size_of::() { + return None; + } + let entries = T::read_u16(&chunk[i..]); + i += size_of::(); - let mut magic = [0; 4]; - reader.read_exact(&mut magic).ok()?; + let value = chunk[i..] + .chunks_exact(size_of::()) + .take(usize::from(entries)) + .filter_map(ExifEntry::try_from_bytes::) + .find(|entry| entry.tag == 0x112 && entry.format == 3 && entry.count == 1) + .map(|entry| entry.value.min(255) as u8)?; - match magic { - [0x49, 0x49, 42, 0] => { - let ifd_offset = reader.read_u32::().ok()?; - reader.set_position(u64::from(ifd_offset)); - let entries = reader.read_u16::().ok()?; - for _ in 0..entries { - let tag = reader.read_u16::().ok()?; - let format = reader.read_u16::().ok()?; - let count = reader.read_u32::().ok()?; - let value = reader.read_u16::().ok()?; - let _padding = reader.read_u16::().ok()?; - if tag == 0x112 && format == 3 && count == 1 { - return Self::from_exif(value.min(255) as u8); - } - } - } - [0x4d, 0x4d, 0, 42] => { - let ifd_offset = reader.read_u32::().ok()?; - reader.set_position(u64::from(ifd_offset)); - let entries = reader.read_u16::().ok()?; - for _ in 0..entries { - let tag = reader.read_u16::().ok()?; - let format = reader.read_u16::().ok()?; - let count = reader.read_u32::().ok()?; - let value = reader.read_u16::().ok()?; - let _padding = reader.read_u16::().ok()?; - if tag == 0x112 && format == 3 && count == 1 { - return Self::from_exif(value.min(255) as u8); - } - } - } - _ => {} + Self::from_exif(value) + } +} + +#[repr(C)] +struct ExifEntry { + tag: u16, + format: u16, + count: u32, + value: u16, + _padding: u16, +} + +impl ExifEntry { + fn try_from_bytes(bytes: &[u8]) -> Option { + if bytes.len() < size_of::() { + return None; } - None + + let tag = T::read_u16( + &bytes[(offset_of!(Self, tag))..(offset_of!(Self, tag) + size_of::())], + ); + let format = T::read_u16( + &bytes[(offset_of!(Self, format))..(offset_of!(Self, format) + size_of::())], + ); + let count = T::read_u32( + &bytes[(offset_of!(Self, count))..(offset_of!(Self, count) + size_of::())], + ); + let value = T::read_u16( + &bytes[(offset_of!(Self, value))..(offset_of!(Self, value) + size_of::())], + ); + let _padding = T::read_u16( + &bytes[(offset_of!(Self, _padding))..(offset_of!(Self, _padding) + size_of::())], + ); + + Some(Self { + tag, + format, + count, + value, + _padding, + }) } } From 216db04831806966060a31b48e26e16b1ee2cab4 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 9 May 2025 22:47:34 +1000 Subject: [PATCH 20/42] `no_std` `load_rect` --- src/codecs/bmp/decoder.rs | 2 +- src/image.rs | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/codecs/bmp/decoder.rs b/src/codecs/bmp/decoder.rs index 7f7b61c94f..55e2091673 100644 --- a/src/codecs/bmp/decoder.rs +++ b/src/codecs/bmp/decoder.rs @@ -1373,7 +1373,7 @@ impl ImageDecoderRect for BmpDecoder { row_pitch, self, self.total_bytes() as usize, - |_, _| Ok(()), + |_, _| ImageResult::Ok(()), |s, buf| s.read_image_data(buf), )?; self.reader.seek(SeekFrom::Start(start))?; diff --git a/src/image.rs b/src/image.rs index 20a38d15cc..6696bcc1f7 100644 --- a/src/image.rs +++ b/src/image.rs @@ -475,9 +475,9 @@ 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)] #[expect(clippy::too_many_arguments)] -pub(crate) fn load_rect( +pub(crate) fn load_rect( x: u32, y: u32, width: u32, @@ -491,9 +491,9 @@ pub(crate) fn load_rect( ) -> ImageResult<()> where D: ImageDecoder, - F1: FnMut(&mut D, u64) -> io::Result<()>, - F2: FnMut(&mut D, &mut [u8]) -> Result<(), E>, - ImageError: From, + F1: FnMut(&mut D, u64) -> Result<(), E1>, + F2: FnMut(&mut D, &mut [u8]) -> Result<(), E2>, + ImageError: From + From, { let scanline_bytes = u64::try_from(scanline_bytes).unwrap(); let row_pitch = u64::try_from(row_pitch).unwrap(); @@ -535,9 +535,8 @@ where .min(scanline_bytes - offset) .min(end - position); - output - .write_all(&tmp[offset as usize..][..len as usize]) - .unwrap(); + let tmp = &tmp[offset as usize..][..len as usize]; + output[..tmp.len()].copy_from_slice(tmp); start += len; if start == end { @@ -566,9 +565,8 @@ where .min(scanline_bytes - offset) .min(end - position); - output - .write_all(&tmp[offset as usize..][..len as usize]) - .unwrap(); + let tmp = &tmp[offset as usize..][..len as usize]; + output[..tmp.len()].copy_from_slice(tmp); } current_scanline += 1; From 05874ecc6c5ea02d9b92d5ac9b7ed900bdb65804 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 9 May 2025 22:53:57 +1000 Subject: [PATCH 21/42] Add `no_std` alternative to `ImageFormat::from_extension` --- src/image.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/image.rs b/src/image.rs index 6696bcc1f7..6441f41a56 100644 --- a/src/image.rs +++ b/src/image.rs @@ -18,8 +18,6 @@ use crate::ImageBuffer; #[cfg(feature = "std")] use std::ffi::OsStr; #[cfg(feature = "std")] -use std::io::{self, Write}; -#[cfg(feature = "std")] use std::path::Path; /// An enumeration of supported image formats. @@ -93,10 +91,28 @@ impl ImageFormat { pub fn from_extension(ext: S) -> Option where S: AsRef, + { + Self::from_extension_str(ext.as_ref().to_str()?) + } + + /// Return the image format specified by a path's file extension. + /// + /// # Example + /// + /// ``` + /// use image::ImageFormat; + /// + /// let format = ImageFormat::from_extension_str("jpg"); + /// assert_eq!(format, Some(ImageFormat::Jpeg)); + /// ``` + #[inline] + pub fn from_extension_str(ext: S) -> Option + where + S: AsRef, { // thin wrapper function to strip generics - fn inner(ext: &OsStr) -> Option { - let ext = ext.to_str()?.to_ascii_lowercase(); + fn inner(ext: &str) -> Option { + let ext = ext.to_ascii_lowercase(); Some(match ext.as_str() { "avif" => ImageFormat::Avif, @@ -427,12 +443,11 @@ impl ImageReadBuffer { } } - #[cfg(feature = "std")] #[expect(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) -> Result where - F: FnMut(&mut [u8]) -> io::Result, + F: FnMut(&mut [u8]) -> Result, { if self.buffer.len() == self.consumed { if self.offset == self.total_bytes { From 9b1160b9efde01e654df11e7a9594a9cb638f903 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 9 May 2025 22:33:56 +1000 Subject: [PATCH 22/42] `no_std` `ImageReader` --- src/dynimage.rs | 5 ++-- src/image_reader/free_functions.rs | 3 +- src/image_reader/image_reader_type.rs | 41 +++++++++++---------------- src/image_reader/mod.rs | 1 - src/lib.rs | 2 -- 5 files changed, 20 insertions(+), 32 deletions(-) diff --git a/src/dynimage.rs b/src/dynimage.rs index 4ccd38ddc5..2106c01f77 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -29,12 +29,11 @@ use crate::metadata::Orientation; use crate::traits::Pixel; #[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ExtendedColorType; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] +use crate::ImageReader; use crate::{image, Luma, LumaA}; use crate::{Rgb32FImage, Rgba32FImage}; -#[cfg(feature = "std")] -use crate::ImageReader; - /// A Dynamic Image /// /// This represents a _matrix_ of _pixels_ which are _convertible_ from and to an _RGBA_ diff --git a/src/image_reader/free_functions.rs b/src/image_reader/free_functions.rs index c5620687ec..cb95429dde 100644 --- a/src/image_reader/free_functions.rs +++ b/src/image_reader/free_functions.rs @@ -16,8 +16,7 @@ use crate::image::ImageFormat; use crate::image::{ImageDecoder, ImageEncoder}; #[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ExtendedColorType; - -#[cfg(feature = "std")] +#[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ImageReader; #[cfg(feature = "std")] diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index da48039ab3..a9b79f5859 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -3,20 +3,11 @@ use alloc::boxed::Box; #[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::dynimage::DynamicImage; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::error::ImageFormatHint; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::error::UnsupportedError; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::error::UnsupportedErrorKind; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] +use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::image::ImageFormat; #[cfg_attr(not(feature = "std"), expect(unused_imports))] use crate::ImageDecoder; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::ImageError; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::ImageResult; +use crate::{ImageError, ImageResult}; #[cfg_attr(not(feature = "std"), expect(unused_imports))] use super::free_functions; @@ -77,8 +68,7 @@ use std::path::Path; /// /// [`set_format`]: #method.set_format /// [`ImageDecoder`]: ../trait.ImageDecoder.html -#[cfg(feature = "std")] -pub struct ImageReader { +pub struct ImageReader { /// The reader. Should be buffered. inner: R, /// The format, if one has been set or deduced. @@ -87,8 +77,7 @@ pub struct ImageReader { limits: super::Limits, } -#[cfg(feature = "std")] -impl<'a, R: 'a + BufRead + Seek> ImageReader { +impl ImageReader { /// Create a new image reader without a preset format. /// /// Assumes the reader is already buffered. For optimal performance, @@ -152,6 +141,19 @@ impl<'a, R: 'a + BufRead + Seek> ImageReader { self.inner } + #[cfg_attr(not(feature = "std"), expect(dead_code))] + fn require_format(&mut self) -> ImageResult { + self.format.ok_or_else(|| { + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormatHint::Unknown, + UnsupportedErrorKind::Format(ImageFormatHint::Unknown), + )) + }) + } +} + +#[cfg(feature = "std")] +impl<'a, R: 'a + BufRead + Seek> ImageReader { /// Makes a decoder. /// /// For all formats except PNG, the limits are ignored and can be set with @@ -291,15 +293,6 @@ impl<'a, R: 'a + BufRead + Seek> ImageReader { DynamicImage::from_decoder(decoder) } - - fn require_format(&mut self) -> ImageResult { - self.format.ok_or_else(|| { - ImageError::Unsupported(UnsupportedError::from_format_and_kind( - ImageFormatHint::Unknown, - UnsupportedErrorKind::Format(ImageFormatHint::Unknown), - )) - }) - } } #[cfg(feature = "std")] diff --git a/src/image_reader/mod.rs b/src/image_reader/mod.rs index 9b248afcdc..b37f0bfeba 100644 --- a/src/image_reader/mod.rs +++ b/src/image_reader/mod.rs @@ -5,7 +5,6 @@ use crate::{error, ColorType, ImageError, ImageResult}; pub(crate) mod free_functions; mod image_reader_type; -#[cfg(feature = "std")] pub use self::image_reader_type::ImageReader; /// Set of supported strict limits for a decoder. diff --git a/src/lib.rs b/src/lib.rs index 2d541e81c5..be8ad5ea96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,7 +189,6 @@ pub use crate::image_reader::{LimitSupport, Limits}; #[cfg(feature = "std")] pub use crate::image_reader::free_functions::load; -#[cfg(feature = "std")] pub use crate::image_reader::ImageReader; pub use crate::dynimage::DynamicImage; @@ -318,7 +317,6 @@ pub mod metadata; //TODO delete this module after a few releases /// deprecated io module the original io module has been renamed to `image_reader` pub mod io { - #[cfg(feature = "std")] #[deprecated(note = "this type has been moved and renamed to image::ImageReader")] /// Deprecated re-export of `ImageReader` as `Reader` pub type Reader = super::ImageReader; From 07462451e0274e5de283a12b84c85d6388b4a834 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 9 May 2025 23:38:11 +1000 Subject: [PATCH 23/42] Fix `load_rect` --- src/image.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/image.rs b/src/image.rs index 6441f41a56..10e9ca5e43 100644 --- a/src/image.rs +++ b/src/image.rs @@ -551,7 +551,11 @@ where .min(end - position); let tmp = &tmp[offset as usize..][..len as usize]; - output[..tmp.len()].copy_from_slice(tmp); + let amt = tmp.len(); + let (a, b) = core::mem::take(&mut output).split_at_mut(amt); + a.copy_from_slice(tmp); + output = b; + start += len; if start == end { @@ -581,7 +585,10 @@ where .min(end - position); let tmp = &tmp[offset as usize..][..len as usize]; - output[..tmp.len()].copy_from_slice(tmp); + let amt = tmp.len(); + let (a, b) = core::mem::take(&mut output).split_at_mut(amt); + a.copy_from_slice(tmp); + output = b; } current_scanline += 1; From bfee6a8ac402204b099b3da9f51ad798c9402ca8 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 10 May 2025 22:38:40 +1000 Subject: [PATCH 24/42] `no_std` TGA --- .github/workflows/rust.yml | 2 +- Cargo.toml | 2 +- src/codecs/tga/decoder.rs | 266 +++++++++++++++++++------------------ src/codecs/tga/encoder.rs | 12 +- src/codecs/tga/header.rs | 11 +- 5 files changed, 160 insertions(+), 133 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f039e15b23..c83686d085 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -216,6 +216,6 @@ jobs: with: components: clippy targets: "wasm32v1-none" - - run: cargo clippy --no-default-features --target wasm32v1-none --features serde -- -D warnings + - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga -- -D warnings env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always diff --git a/Cargo.toml b/Cargo.toml index c977d1c276..dda39bcd82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ jpeg = ["dep:zune-core", "dep:zune-jpeg", "std"] png = ["dep:png", "std"] pnm = ["std"] qoi = ["dep:qoi", "std"] -tga = ["std"] +tga = [] tiff = ["dep:tiff", "std"] webp = ["dep:image-webp", "std"] diff --git a/src/codecs/tga/decoder.rs b/src/codecs/tga/decoder.rs index dd45ca7aa9..4479e03ae3 100644 --- a/src/codecs/tga/decoder.rs +++ b/src/codecs/tga/decoder.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use super::header::{Header, ImageType, ALPHA_BIT_MASK, SCREEN_ORIGIN_BIT_MASK}; use crate::{ color::{ColorType, ExtendedColorType}, @@ -9,7 +11,11 @@ use crate::{ use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; + +#[cfg(feature = "std")] use byteorder_lite::ReadBytesExt; + +#[cfg(feature = "std")] use std::io::{self, Read}; struct ColorMap { @@ -20,6 +26,7 @@ struct ColorMap { } impl ColorMap { + #[cfg(feature = "std")] pub(crate) fn from_reader( r: &mut dyn Read, start_offset: u16, @@ -62,48 +69,7 @@ pub struct TgaDecoder { color_map: Option, } -impl TgaDecoder { - /// Create a new decoder that decodes from the stream `r` - pub fn new(r: R) -> ImageResult> { - let mut decoder = TgaDecoder { - r, - - width: 0, - height: 0, - bytes_per_pixel: 0, - has_loaded_metadata: false, - - image_type: ImageType::Unknown, - color_type: ColorType::L8, - original_color_type: None, - - header: Header::default(), - color_map: None, - }; - decoder.read_metadata()?; - Ok(decoder) - } - - fn read_header(&mut self) -> ImageResult<()> { - self.header = Header::from_reader(&mut self.r)?; - self.image_type = ImageType::new(self.header.image_type); - self.width = self.header.image_width as usize; - self.height = self.header.image_height as usize; - self.bytes_per_pixel = (self.header.pixel_depth as usize).div_ceil(8); - Ok(()) - } - - fn read_metadata(&mut self) -> ImageResult<()> { - if !self.has_loaded_metadata { - self.read_header()?; - self.read_image_id()?; - self.read_color_map()?; - self.read_color_information()?; - self.has_loaded_metadata = true; - } - Ok(()) - } - +impl TgaDecoder { /// Loads the color information for the decoder /// /// To keep things simple, we won't handle bit depths that aren't divisible @@ -169,32 +135,8 @@ impl TgaDecoder { Ok(()) } - /// Read the image id field - /// - /// We're not interested in this field, so this function skips it if it - /// is present - fn read_image_id(&mut self) -> ImageResult<()> { - self.r - .read_exact(&mut vec![0; self.header.id_length as usize])?; - Ok(()) - } - - fn read_color_map(&mut self) -> ImageResult<()> { - if self.header.map_type == 1 { - // FIXME: we could reverse the map entries, which avoids having to reverse all pixels - // in the final output individually. - self.color_map = Some(ColorMap::from_reader( - &mut self.r, - self.header.map_origin, - self.header.map_length, - self.header.map_entry_size, - )?); - } - Ok(()) - } - /// Expands indices into its mapped color - fn expand_color_map(&self, pixel_data: &[u8]) -> io::Result> { + fn expand_color_map(&self, pixel_data: &[u8]) -> Option> { #[inline] fn bytes_to_index(bytes: &[u8]) -> usize { let mut result = 0usize; @@ -208,77 +150,21 @@ impl TgaDecoder { let mut result = Vec::with_capacity(self.width * self.height * bytes_per_entry); if self.bytes_per_pixel == 0 { - return Err(io::ErrorKind::Other.into()); + return None; } - let color_map = self - .color_map - .as_ref() - .ok_or_else(|| io::Error::from(io::ErrorKind::Other))?; + let color_map = self.color_map.as_ref()?; for chunk in pixel_data.chunks(self.bytes_per_pixel) { let index = bytes_to_index(chunk); if let Some(color) = color_map.get(index) { result.extend_from_slice(color); } else { - return Err(io::ErrorKind::Other.into()); - } - } - - Ok(result) - } - - /// Reads a run length encoded data for given number of bytes - fn read_encoded_data(&mut self, num_bytes: usize) -> io::Result> { - let mut pixel_data = Vec::with_capacity(num_bytes); - let mut repeat_buf = Vec::with_capacity(self.bytes_per_pixel); - - while pixel_data.len() < num_bytes { - let run_packet = self.r.read_u8()?; - // If the highest bit in `run_packet` is set, then we repeat pixels - // - // Note: the TGA format adds 1 to both counts because having a count - // of 0 would be pointless. - if (run_packet & 0x80) != 0 { - // high bit set, so we will repeat the data - let repeat_count = ((run_packet & !0x80) + 1) as usize; - self.r - .by_ref() - .take(self.bytes_per_pixel as u64) - .read_to_end(&mut repeat_buf)?; - - // get the repeating pixels from the bytes of the pixel stored in `repeat_buf` - let data = repeat_buf - .iter() - .cycle() - .take(repeat_count * self.bytes_per_pixel); - pixel_data.extend(data); - repeat_buf.clear(); - } else { - // not set, so `run_packet+1` is the number of non-encoded pixels - let num_raw_bytes = (run_packet + 1) as usize * self.bytes_per_pixel; - self.r - .by_ref() - .take(num_raw_bytes as u64) - .read_to_end(&mut pixel_data)?; + return None; } } - if pixel_data.len() > num_bytes { - // FIXME: the last packet contained more data than we asked for! - // This is at least a warning. We truncate the data since some methods rely on the - // length to be accurate in the success case. - pixel_data.truncate(num_bytes); - } - - Ok(pixel_data) - } - - /// Reads a run length encoded packet - fn read_all_encoded_data(&mut self) -> ImageResult> { - let num_bytes = self.width * self.height * self.bytes_per_pixel; - - Ok(self.read_encoded_data(num_bytes)?) + Some(result) } /// Reverse from BGR encoding to RGB encoding @@ -339,6 +225,128 @@ impl TgaDecoder { } } +#[cfg(feature = "std")] +impl TgaDecoder { + /// Create a new decoder that decodes from the stream `r` + pub fn new(r: R) -> ImageResult> { + let mut decoder = TgaDecoder { + r, + + width: 0, + height: 0, + bytes_per_pixel: 0, + has_loaded_metadata: false, + + image_type: ImageType::Unknown, + color_type: ColorType::L8, + original_color_type: None, + + header: Header::default(), + color_map: None, + }; + decoder.read_metadata()?; + Ok(decoder) + } + + fn read_header(&mut self) -> ImageResult<()> { + self.header = Header::from_reader(&mut self.r)?; + self.image_type = ImageType::new(self.header.image_type); + self.width = self.header.image_width as usize; + self.height = self.header.image_height as usize; + self.bytes_per_pixel = (self.header.pixel_depth as usize).div_ceil(8); + Ok(()) + } + + fn read_metadata(&mut self) -> ImageResult<()> { + if !self.has_loaded_metadata { + self.read_header()?; + self.read_image_id()?; + self.read_color_map()?; + self.read_color_information()?; + self.has_loaded_metadata = true; + } + Ok(()) + } + + /// Read the image id field + /// + /// We're not interested in this field, so this function skips it if it + /// is present + fn read_image_id(&mut self) -> ImageResult<()> { + self.r + .read_exact(&mut vec![0; self.header.id_length as usize])?; + Ok(()) + } + + fn read_color_map(&mut self) -> ImageResult<()> { + if self.header.map_type == 1 { + // FIXME: we could reverse the map entries, which avoids having to reverse all pixels + // in the final output individually. + self.color_map = Some(ColorMap::from_reader( + &mut self.r, + self.header.map_origin, + self.header.map_length, + self.header.map_entry_size, + )?); + } + Ok(()) + } + + /// Reads a run length encoded data for given number of bytes + fn read_encoded_data(&mut self, num_bytes: usize) -> io::Result> { + let mut pixel_data = Vec::with_capacity(num_bytes); + let mut repeat_buf = Vec::with_capacity(self.bytes_per_pixel); + + while pixel_data.len() < num_bytes { + let run_packet = self.r.read_u8()?; + // If the highest bit in `run_packet` is set, then we repeat pixels + // + // Note: the TGA format adds 1 to both counts because having a count + // of 0 would be pointless. + if (run_packet & 0x80) != 0 { + // high bit set, so we will repeat the data + let repeat_count = ((run_packet & !0x80) + 1) as usize; + self.r + .by_ref() + .take(self.bytes_per_pixel as u64) + .read_to_end(&mut repeat_buf)?; + + // get the repeating pixels from the bytes of the pixel stored in `repeat_buf` + let data = repeat_buf + .iter() + .cycle() + .take(repeat_count * self.bytes_per_pixel); + pixel_data.extend(data); + repeat_buf.clear(); + } else { + // not set, so `run_packet+1` is the number of non-encoded pixels + let num_raw_bytes = (run_packet + 1) as usize * self.bytes_per_pixel; + self.r + .by_ref() + .take(num_raw_bytes as u64) + .read_to_end(&mut pixel_data)?; + } + } + + if pixel_data.len() > num_bytes { + // FIXME: the last packet contained more data than we asked for! + // This is at least a warning. We truncate the data since some methods rely on the + // length to be accurate in the success case. + pixel_data.truncate(num_bytes); + } + + Ok(pixel_data) + } + + /// Reads a run length encoded packet + fn read_all_encoded_data(&mut self) -> ImageResult> { + let num_bytes = self.width * self.height * self.bytes_per_pixel; + + Ok(self.read_encoded_data(num_bytes)?) + } +} + +#[cfg(feature = "std")] impl ImageDecoder for TgaDecoder { fn dimensions(&self) -> (u32, u32) { (self.width as u32, self.height as u32) @@ -385,7 +393,9 @@ impl ImageDecoder for TgaDecoder { // expand the indices using the color map if necessary if self.image_type.is_color_mapped() { - let pixel_data = self.expand_color_map(rawbuf)?; + let pixel_data = self + .expand_color_map(rawbuf) + .ok_or(io::Error::from(io::ErrorKind::Other))?; // not enough data to fill the buffer, or would overflow the buffer if pixel_data.len() != buf.len() { return Err(ImageError::Limits(LimitError::from_kind( diff --git a/src/codecs/tga/encoder.rs b/src/codecs/tga/encoder.rs index b427492cb4..25ab7adfde 100644 --- a/src/codecs/tga/encoder.rs +++ b/src/codecs/tga/encoder.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use super::header::Header; use crate::{ codecs::tga::header::ImageType, error::EncodingError, ExtendedColorType, ImageEncoder, @@ -5,6 +7,8 @@ use crate::{ }; use alloc::vec::Vec; use core::{error, fmt}; + +#[cfg(feature = "std")] use std::io::Write; /// Errors that can occur during encoding and saving of a TGA image. @@ -35,7 +39,7 @@ impl From for ImageError { impl error::Error for EncoderError {} /// TGA encoder. -pub struct TgaEncoder { +pub struct TgaEncoder { writer: W, /// Run-length encoding @@ -50,7 +54,7 @@ enum PacketType { Rle, } -impl TgaEncoder { +impl TgaEncoder { /// Create a new encoder that writes its output to ```w```. pub fn new(w: W) -> TgaEncoder { TgaEncoder { @@ -64,7 +68,10 @@ impl TgaEncoder { self.use_rle = false; self } +} +#[cfg(feature = "std")] +impl TgaEncoder { /// Writes a raw packet to the writer fn write_raw_packet(&mut self, pixels: &[u8], counter: u8) -> ImageResult<()> { // Set high bit = 0 and store counter - 1 (because 0 would be useless) @@ -235,6 +242,7 @@ impl TgaEncoder { } } +#[cfg(feature = "std")] impl ImageEncoder for TgaEncoder { #[track_caller] fn write_image( diff --git a/src/codecs/tga/header.rs b/src/codecs/tga/header.rs index 4dff32e777..c564e50066 100644 --- a/src/codecs/tga/header.rs +++ b/src/codecs/tga/header.rs @@ -1,8 +1,15 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use crate::error::{UnsupportedError, UnsupportedErrorKind}; use crate::{ExtendedColorType, ImageError, ImageFormat, ImageResult}; -use byteorder_lite::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use byteorder_lite::LittleEndian; + +#[cfg(feature = "std")] use std::io::{Read, Write}; +#[cfg(feature = "std")] +use byteorder_lite::{ReadBytesExt, WriteBytesExt}; + pub(crate) const ALPHA_BIT_MASK: u8 = 0b1111; pub(crate) const SCREEN_ORIGIN_BIT_MASK: u8 = 0b10_0000; @@ -117,6 +124,7 @@ impl Header { } /// Load the header with values from the reader. + #[cfg(feature = "std")] pub(crate) fn from_reader(r: &mut dyn Read) -> ImageResult { Ok(Self { id_length: r.read_u8()?, @@ -135,6 +143,7 @@ impl Header { } /// Write out the header values. + #[cfg(feature = "std")] pub(crate) fn write_to(&self, w: &mut dyn Write) -> ImageResult<()> { w.write_u8(self.id_length)?; w.write_u8(self.map_type)?; From 93ec7fa5ec1f712870857bbe295fc60d28b7824f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 10 May 2025 22:55:28 +1000 Subject: [PATCH 25/42] `no_std` Qoi --- Cargo.toml | 6 ++-- src/codecs/qoi.rs | 86 ++++++++++++++++++++++++++++++++++------------- src/color.rs | 3 +- 3 files changed, 67 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dda39bcd82..a167cf37c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ gif = { version = "0.13.1", optional = true } image-webp = { version = "0.2.0", optional = true } mp4parse = { version = "0.17.0", optional = true } png = { version = "0.17.11", optional = true } -qoi = { version = "0.4", optional = true } +qoi = { version = "0.4", default-features = false, optional = true, features = ["alloc"] } ravif = { version = "0.11.12", default-features = false, optional = true } rayon = { version = "1.7.0", optional = true } rgb = { version = "0.8.48", default-features = false, optional = true } @@ -82,7 +82,7 @@ ico = ["bmp", "png", "std"] jpeg = ["dep:zune-core", "dep:zune-jpeg", "std"] png = ["dep:png", "std"] pnm = ["std"] -qoi = ["dep:qoi", "std"] +qoi = ["dep:qoi"] tga = [] tiff = ["dep:tiff", "std"] webp = ["dep:image-webp", "std"] @@ -94,7 +94,7 @@ color_quant = ["dep:color_quant", "std"] # Enables color quantization avif-native = ["dep:mp4parse", "dep:dav1d", "std", "dep:num-bigint"] # Enable native dependency libdav1d benchmarks = ["std"] # Build some inline benchmarks. Useful only during development (requires nightly Rust) serde = ["dep:serde"] -std = ["byteorder-lite/std", "num-traits/std"] +std = ["byteorder-lite/std", "num-traits/std", "qoi?/std"] libm = ["num-traits/libm"] [[bench]] diff --git a/src/codecs/qoi.rs b/src/codecs/qoi.rs index a65fd83713..f5724a31a8 100644 --- a/src/codecs/qoi.rs +++ b/src/codecs/qoi.rs @@ -1,19 +1,25 @@ //! Decoding and encoding of QOI images +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::boxed::Box; use alloc::format; -use std::io::{Read, Write}; +use alloc::vec::Vec; use crate::error::{DecodingError, EncodingError}; use crate::{ ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, }; +#[cfg(feature = "std")] +use std::io::{Read, Write}; + /// QOI decoder pub struct QoiDecoder { decoder: qoi::Decoder, } +#[cfg(feature = "std")] impl QoiDecoder where R: Read, @@ -25,6 +31,7 @@ where } } +#[cfg(feature = "std")] impl ImageDecoder for QoiDecoder { fn dimensions(&self) -> (u32, u32) { (self.decoder.header().width, self.decoder.header().height) @@ -47,12 +54,30 @@ impl ImageDecoder for QoiDecoder { } } +/// Wrapper to implement [`Error`](core::error::Error) for [`qoi::Error`]. +#[repr(transparent)] +struct QoiError(qoi::Error); + +impl core::fmt::Debug for QoiError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(&self.0, f) + } +} + +impl core::fmt::Display for QoiError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(&self.0, f) + } +} + +impl core::error::Error for QoiError {} + fn decoding_error(error: qoi::Error) -> ImageError { - ImageError::Decoding(DecodingError::new(ImageFormat::Qoi.into(), error)) + ImageError::Decoding(DecodingError::new(ImageFormat::Qoi.into(), QoiError(error))) } fn encoding_error(error: qoi::Error) -> ImageError { - ImageError::Encoding(EncodingError::new(ImageFormat::Qoi.into(), error)) + ImageError::Encoding(EncodingError::new(ImageFormat::Qoi.into(), QoiError(error))) } /// QOI encoder @@ -60,13 +85,14 @@ pub struct QoiEncoder { writer: W, } -impl QoiEncoder { +impl QoiEncoder { /// Creates a new encoder that writes its output to ```writer``` pub fn new(writer: W) -> Self { Self { writer } } } +#[cfg(feature = "std")] impl ImageEncoder for QoiEncoder { #[track_caller] fn write_image( @@ -76,26 +102,7 @@ impl ImageEncoder for QoiEncoder { height: u32, color_type: ExtendedColorType, ) -> ImageResult<()> { - if !matches!( - color_type, - ExtendedColorType::Rgba8 | ExtendedColorType::Rgb8 - ) { - return Err(ImageError::Encoding(EncodingError::new( - ImageFormat::Qoi.into(), - format!("unsupported color type {color_type:?}. Supported are Rgba8 and Rgb8."), - ))); - } - - let expected_buffer_len = color_type.buffer_size(width, height); - assert_eq!( - expected_buffer_len, - buf.len() as u64, - "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", - buf.len(), - ); - - // Encode data in QOI - let data = qoi::encode_to_vec(buf, width, height).map_err(encoding_error)?; + let data = write_image_to_vec(buf, width, height, color_type)?; // Write data to buffer self.writer.write_all(&data[..])?; @@ -105,6 +112,37 @@ impl ImageEncoder for QoiEncoder { } } +#[track_caller] +fn write_image_to_vec( + buf: &[u8], + width: u32, + height: u32, + color_type: ExtendedColorType, +) -> ImageResult> { + if !matches!( + color_type, + ExtendedColorType::Rgba8 | ExtendedColorType::Rgb8 + ) { + return Err(ImageError::Encoding(EncodingError::new( + ImageFormat::Qoi.into(), + format!("unsupported color type {color_type:?}. Supported are Rgba8 and Rgb8."), + ))); + } + + let expected_buffer_len = color_type.buffer_size(width, height); + assert_eq!( + expected_buffer_len, + buf.len() as u64, + "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", + buf.len(), + ); + + // Encode data in QOI + let data = qoi::encode_to_vec(buf, width, height).map_err(encoding_error)?; + + Ok(data) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/color.rs b/src/color.rs index 8244be8dde..12fc322c68 100644 --- a/src/color.rs +++ b/src/color.rs @@ -234,7 +234,8 @@ impl ExtendedColorType { } /// Returns the number of bytes required to hold a width x height image of this color type. - #[cfg_attr(not(feature = "std"), expect(dead_code))] + #[allow(dead_code)] + // Only used with certain features pub(crate) fn buffer_size(self, width: u32, height: u32) -> u64 { let bpp = self.bits_per_pixel() as u64; let row_pitch = (width as u64 * bpp).div_ceil(8); From 3f5714b7fdb6ed105baecfc3867b62ca4821daf5 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 10 May 2025 23:08:49 +1000 Subject: [PATCH 26/42] `no_std` BMP --- Cargo.toml | 2 +- src/codecs/bmp/decoder.rs | 152 ++++++++++++++++++++------------------ src/codecs/bmp/encoder.rs | 40 ++++++---- 3 files changed, 107 insertions(+), 87 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a167cf37c8..6a434a66f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ default = ["std", "rayon", "default-formats"] # Format features default-formats = ["avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"] avif = ["dep:ravif", "dep:rgb", "std", "dep:num-bigint"] -bmp = ["std"] +bmp = [] dds = ["std"] exr = ["dep:exr", "std"] ff = ["std"] # Farbfeld image format diff --git a/src/codecs/bmp/decoder.rs b/src/codecs/bmp/decoder.rs index 55e2091673..fc6187ff4f 100644 --- a/src/codecs/bmp/decoder.rs +++ b/src/codecs/bmp/decoder.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::vec::Vec; @@ -6,9 +8,8 @@ use core::cmp::{self, Ordering}; use core::iter::{repeat, Rev}; use core::slice::ChunksMut; use core::{error, fmt}; -use std::io::{self, BufRead, Seek, SeekFrom}; -use byteorder_lite::{LittleEndian, ReadBytesExt}; +use byteorder_lite::LittleEndian; use crate::color::ColorType; use crate::error::{ @@ -17,6 +18,12 @@ use crate::error::{ use crate::image::{self, ImageDecoder, ImageFormat}; use crate::ImageDecoderRect; +#[cfg(feature = "std")] +use std::io::{BufRead, Seek, SeekFrom}; + +#[cfg(feature = "std")] +use byteorder_lite::ReadBytesExt; + const BITMAPCOREHEADER_SIZE: u32 = 12; const BITMAPINFOHEADER_SIZE: u32 = 40; const BITMAPV2HEADER_SIZE: u32 = 52; @@ -267,16 +274,16 @@ fn num_bytes(width: i32, length: i32, channels: usize) -> Option { /// Call the provided function on each row of the provided buffer, returning Err if the provided /// function returns an error, extends the buffer if it's not large enough. -fn with_rows( +fn with_rows( buffer: &mut [u8], width: i32, height: i32, channels: usize, top_down: bool, mut func: F, -) -> io::Result<()> +) -> Result<(), E> where - F: FnMut(&mut [u8]) -> io::Result<()>, + F: FnMut(&mut [u8]) -> Result<(), E>, { // An overflow should already have been checked for when this is called, // though we check anyhow, as it somehow seems to increase performance slightly. @@ -502,7 +509,7 @@ enum RLEInsn { PixelRun(u8, u8), } -impl BmpDecoder { +impl BmpDecoder { fn new_decoder(reader: R) -> BmpDecoder { BmpDecoder { reader, @@ -526,6 +533,71 @@ impl BmpDecoder { } } + /// If true, the palette in BMP does not apply to the image even if it is found. + /// In other words, the output image is the indexed color. + pub fn set_indexed_color(&mut self, indexed_color: bool) { + self.indexed_color = indexed_color; + } + + #[cfg(feature = "ico")] + pub(crate) fn reader(&mut self) -> &mut R { + &mut self.reader + } + + fn get_palette_size(&mut self) -> ImageResult { + match self.colors_used { + 0 => Ok(1 << self.bit_count), + _ => { + if self.colors_used > 1 << self.bit_count { + return Err(DecoderError::PaletteSizeExceeded { + colors_used: self.colors_used, + bit_count: self.bit_count, + } + .into()); + } + Ok(self.colors_used as usize) + } + } + } + + fn bytes_per_color(&self) -> usize { + match self.bmp_header_type { + BMPHeaderType::Core => 3, + _ => 4, + } + } + + /// Get the palette that is embedded in the BMP image, if any. + pub fn get_palette(&self) -> Option<&[[u8; 3]]> { + self.palette.as_ref().map(|vec| &vec[..]) + } + + fn num_channels(&self) -> usize { + if self.indexed_color { + 1 + } else if self.add_alpha_channel { + 4 + } else { + 3 + } + } + + fn rows<'a>(&self, pixel_data: &'a mut [u8]) -> RowIterator<'a> { + let stride = self.width as usize * self.num_channels(); + if self.top_down { + RowIterator { + chunks: Chunker::FromTop(pixel_data.chunks_mut(stride)), + } + } else { + RowIterator { + chunks: Chunker::FromBottom(pixel_data.chunks_mut(stride).rev()), + } + } + } +} + +#[cfg(feature = "std")] +impl BmpDecoder { /// Create a new decoder that decodes from the stream ```r``` pub fn new(reader: R) -> ImageResult> { let mut decoder = Self::new_decoder(reader); @@ -550,17 +622,6 @@ impl BmpDecoder { Ok(decoder) } - /// If true, the palette in BMP does not apply to the image even if it is found. - /// In other words, the output image is the indexed color. - pub fn set_indexed_color(&mut self, indexed_color: bool) { - self.indexed_color = indexed_color; - } - - #[cfg(feature = "ico")] - pub(crate) fn reader(&mut self) -> &mut R { - &mut self.reader - } - fn read_file_header(&mut self) -> ImageResult<()> { if self.no_file_header { return Ok(()); @@ -858,29 +919,6 @@ impl BmpDecoder { Ok(()) } - fn get_palette_size(&mut self) -> ImageResult { - match self.colors_used { - 0 => Ok(1 << self.bit_count), - _ => { - if self.colors_used > 1 << self.bit_count { - return Err(DecoderError::PaletteSizeExceeded { - colors_used: self.colors_used, - bit_count: self.bit_count, - } - .into()); - } - Ok(self.colors_used as usize) - } - } - } - - fn bytes_per_color(&self) -> usize { - match self.bmp_header_type { - BMPHeaderType::Core => 3, - _ => 4, - } - } - fn read_palette(&mut self) -> ImageResult<()> { const MAX_PALETTE_SIZE: usize = 256; // Palette indices are u8. @@ -922,34 +960,6 @@ impl BmpDecoder { Ok(()) } - /// Get the palette that is embedded in the BMP image, if any. - pub fn get_palette(&self) -> Option<&[[u8; 3]]> { - self.palette.as_ref().map(|vec| &vec[..]) - } - - fn num_channels(&self) -> usize { - if self.indexed_color { - 1 - } else if self.add_alpha_channel { - 4 - } else { - 3 - } - } - - fn rows<'a>(&self, pixel_data: &'a mut [u8]) -> RowIterator<'a> { - let stride = self.width as usize * self.num_channels(); - if self.top_down { - RowIterator { - chunks: Chunker::FromTop(pixel_data.chunks_mut(stride)), - } - } else { - RowIterator { - chunks: Chunker::FromBottom(pixel_data.chunks_mut(stride).rev()), - } - } - } - fn read_palettized_pixel_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { let num_channels = self.num_channels(); let row_byte_length = ((i32::from(self.bit_count) * self.width + 31) / 32 * 4) as usize; @@ -994,7 +1004,7 @@ impl BmpDecoder { _ => panic!(), }; } - Ok(()) + ImageResult::Ok(()) }, )?; @@ -1075,7 +1085,7 @@ impl BmpDecoder { } } } - Ok(()) + ImageResult::Ok(()) }, )?; @@ -1328,6 +1338,7 @@ impl BmpDecoder { } } +#[cfg(feature = "std")] impl ImageDecoder for BmpDecoder { fn dimensions(&self) -> (u32, u32) { (self.width as u32, self.height as u32) @@ -1353,6 +1364,7 @@ impl ImageDecoder for BmpDecoder { } } +#[cfg(feature = "std")] impl ImageDecoderRect for BmpDecoder { fn read_rect( &mut self, diff --git a/src/codecs/bmp/encoder.rs b/src/codecs/bmp/encoder.rs index c2f23a84cb..af126feca4 100644 --- a/src/codecs/bmp/encoder.rs +++ b/src/codecs/bmp/encoder.rs @@ -1,7 +1,8 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::format; use alloc::string::String; -use byteorder_lite::{LittleEndian, WriteBytesExt}; -use std::io::{self, Write}; +use byteorder_lite::LittleEndian; use crate::error::{ EncodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, @@ -9,6 +10,12 @@ use crate::error::{ use crate::image::ImageEncoder; use crate::{ExtendedColorType, ImageFormat}; +#[cfg(feature = "std")] +use std::io::{self, Write}; + +#[cfg(feature = "std")] +use byteorder_lite::WriteBytesExt; + const BITMAPFILEHEADER_SIZE: u32 = 14; const BITMAPINFOHEADER_SIZE: u32 = 40; const BITMAPV4HEADER_SIZE: u32 = 108; @@ -18,12 +25,15 @@ pub struct BmpEncoder<'a, W: 'a> { writer: &'a mut W, } -impl<'a, W: Write + 'a> BmpEncoder<'a, W> { +impl<'a, W: 'a> BmpEncoder<'a, W> { /// Create a new encoder that writes its output to ```w```. pub fn new(w: &'a mut W) -> Self { BmpEncoder { writer: w } } +} +#[cfg(feature = "std")] +impl<'a, W: Write + 'a> BmpEncoder<'a, W> { /// Encodes the image `image` that has dimensions `width` and `height` and `ExtendedColorType` `c`. /// /// # Panics @@ -74,8 +84,13 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { let bmp_header_size = BITMAPFILEHEADER_SIZE; - let (dib_header_size, written_pixel_size, palette_color_count) = - get_pixel_info(c, palette)?; + let (dib_header_size, written_pixel_size, palette_color_count) = get_pixel_info(c, palette) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + &get_unsupported_error_message(c)[..], + ) + })?; let row_pad_size = (4 - (width * written_pixel_size) % 4) % 4; // each row must be padded to a multiple of 4 bytes let image_size = width .checked_mul(height) @@ -274,6 +289,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { } } +#[cfg(feature = "std")] impl ImageEncoder for BmpEncoder<'_, W> { #[track_caller] fn write_image( @@ -292,10 +308,7 @@ fn get_unsupported_error_message(c: ExtendedColorType) -> String { } /// Returns a tuple representing: (dib header size, written pixel size, palette color count). -fn get_pixel_info( - c: ExtendedColorType, - palette: Option<&[[u8; 3]]>, -) -> io::Result<(u32, u32, u32)> { +fn get_pixel_info(c: ExtendedColorType, palette: Option<&[[u8; 3]]>) -> Option<(u32, u32, u32)> { let sizes = match c { ExtendedColorType::Rgb8 => (BITMAPINFOHEADER_SIZE, 3, 0), ExtendedColorType::Rgba8 => (BITMAPV4HEADER_SIZE, 4, 0), @@ -309,15 +322,10 @@ fn get_pixel_info( 1, palette.map(|p| p.len()).unwrap_or(256) as u32, ), - _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - &get_unsupported_error_message(c)[..], - )) - } + _ => return None, }; - Ok(sizes) + Some(sizes) } #[cfg(test)] From 15281b410472ebe49643e971ba6d5f37e034a9a4 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 10 May 2025 23:17:23 +1000 Subject: [PATCH 27/42] `no_std` DDS --- Cargo.toml | 2 +- src/codecs/dds.rs | 18 +++++++++++++++--- src/codecs/dxt.rs | 14 +++++++++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a434a66f8..65b994ba5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ default = ["std", "rayon", "default-formats"] default-formats = ["avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"] avif = ["dep:ravif", "dep:rgb", "std", "dep:num-bigint"] bmp = [] -dds = ["std"] +dds = [] exr = ["dep:exr", "std"] ff = ["std"] # Farbfeld image format gif = ["dep:gif", "dep:color_quant", "std"] diff --git a/src/codecs/dds.rs b/src/codecs/dds.rs index b07ede102f..6a3cce2ddc 100644 --- a/src/codecs/dds.rs +++ b/src/codecs/dds.rs @@ -5,13 +5,14 @@ //! # Related Links //! * - Description of the DDS format. +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::boxed::Box; use alloc::format; use alloc::string::ToString; use core::{error, fmt}; -use std::io::Read; -use byteorder_lite::{LittleEndian, ReadBytesExt}; +use byteorder_lite::LittleEndian; use crate::codecs::dxt::{DxtDecoder, DxtVariant}; use crate::color::ColorType; @@ -20,6 +21,12 @@ use crate::error::{ }; use crate::image::{ImageDecoder, ImageFormat}; +#[cfg(feature = "std")] +use std::io::Read; + +#[cfg(feature = "std")] +use byteorder_lite::ReadBytesExt; + /// Errors that can occur during decoding and parsing a DDS image #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[expect(clippy::enum_variant_names)] @@ -118,6 +125,7 @@ struct PixelFormat { } impl PixelFormat { + #[cfg(feature = "std")] fn from_reader(r: &mut dyn Read) -> ImageResult { let size = r.read_u32::()?; if size != 32 { @@ -141,6 +149,7 @@ impl PixelFormat { } impl Header { + #[cfg(feature = "std")] fn from_reader(r: &mut dyn Read) -> ImageResult { let size = r.read_u32::()?; if size != 124 { @@ -188,6 +197,7 @@ impl Header { } impl DX10Header { + #[cfg(feature = "std")] fn from_reader(r: &mut dyn Read) -> ImageResult { let dxgi_format = r.read_u32::()?; let resource_dimension = r.read_u32::()?; @@ -242,10 +252,11 @@ impl DX10Header { } /// The representation of a DDS decoder -pub struct DdsDecoder { +pub struct DdsDecoder { inner: DxtDecoder, } +#[cfg(feature = "std")] impl DdsDecoder { /// Create a new decoder that decodes from the stream `r` pub fn new(mut r: R) -> ImageResult { @@ -323,6 +334,7 @@ impl DdsDecoder { } } +#[cfg(feature = "std")] impl ImageDecoder for DdsDecoder { fn dimensions(&self) -> (u32, u32) { self.inner.dimensions() diff --git a/src/codecs/dxt.rs b/src/codecs/dxt.rs index 12c19b6e21..7b1457d5d1 100644 --- a/src/codecs/dxt.rs +++ b/src/codecs/dxt.rs @@ -7,14 +7,18 @@ //! //! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::boxed::Box; use alloc::vec; -use std::io::{self, Read}; use crate::color::ColorType; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::image::ImageDecoder; +#[cfg(feature = "std")] +use std::io::{self, Read}; + /// What version of DXT compression are we using? /// Note that DXT2 and DXT4 are left away as they're /// just DXT3 and DXT5 with premultiplied alpha @@ -59,7 +63,7 @@ impl DxtVariant { } /// DXT decoder -pub(crate) struct DxtDecoder { +pub(crate) struct DxtDecoder { inner: R, width_blocks: u32, height_blocks: u32, @@ -67,7 +71,7 @@ pub(crate) struct DxtDecoder { row: u32, } -impl DxtDecoder { +impl DxtDecoder { /// Create a new DXT decoder that decodes from the stream ```r```. /// As DXT is often stored as raw buffers with the width/height /// somewhere else the width and height of the image need @@ -103,7 +107,10 @@ impl DxtDecoder { fn scanline_bytes(&self) -> u64 { self.variant.decoded_bytes_per_block() as u64 * u64::from(self.width_blocks) } +} +#[cfg(feature = "std")] +impl DxtDecoder { fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result { assert_eq!(u64::try_from(buf.len()), Ok(self.scanline_bytes())); @@ -122,6 +129,7 @@ impl DxtDecoder { // Note that, due to the way that DXT compression works, a scanline is considered to consist out of // 4 lines of pixels. +#[cfg(feature = "std")] impl ImageDecoder for DxtDecoder { fn dimensions(&self) -> (u32, u32) { (self.width_blocks * 4, self.height_blocks * 4) From e1cbdcdf68fdb232813e53c5b11bded4d0ae1198 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:00:54 +1000 Subject: [PATCH 28/42] Allow `color_quant` in `no_std` Only works on `no_std` platforms once `color_quant` itself is `no_std` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 65b994ba5d..943789fbba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ webp = ["dep:image-webp", "std"] # Other features rayon = ["dep:rayon", "ravif?/threading", "std"] # Enables multi-threading nasm = ["ravif?/asm", "std"] # Enables use of nasm by rav1e (requires nasm to be installed) -color_quant = ["dep:color_quant", "std"] # Enables color quantization +color_quant = ["dep:color_quant"] # Enables color quantization avif-native = ["dep:mp4parse", "dep:dav1d", "std", "dep:num-bigint"] # Enable native dependency libdav1d benchmarks = ["std"] # Build some inline benchmarks. Useful only during development (requires nightly Rust) serde = ["dep:serde"] From 519a80377736bebf1aa191b66f79b7889fa98e1c Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:01:47 +1000 Subject: [PATCH 29/42] Update CI --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c83686d085..971a3fe460 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -216,6 +216,6 @@ jobs: with: components: clippy targets: "wasm32v1-none" - - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga -- -D warnings + - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga,qoi,bmp,dds -- -D warnings env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always From 1161d274c7bcd117e0ef709edc4a80ed802ccbad Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:05:54 +1000 Subject: [PATCH 30/42] `no_std` Farbfeld --- .github/workflows/rust.yml | 2 +- Cargo.toml | 2 +- src/codecs/farbfeld.rs | 26 +++++++++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 971a3fe460..4cf5fe461a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -216,6 +216,6 @@ jobs: with: components: clippy targets: "wasm32v1-none" - - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga,qoi,bmp,dds -- -D warnings + - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga,qoi,bmp,dds,ff -- -D warnings env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always diff --git a/Cargo.toml b/Cargo.toml index 943789fbba..b88ee02a8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ avif = ["dep:ravif", "dep:rgb", "std", "dep:num-bigint"] bmp = [] dds = [] exr = ["dep:exr", "std"] -ff = ["std"] # Farbfeld image format +ff = [] # Farbfeld image format gif = ["dep:gif", "dep:color_quant", "std"] hdr = ["std"] ico = ["bmp", "png", "std"] diff --git a/src/codecs/farbfeld.rs b/src/codecs/farbfeld.rs index 322ca09ceb..988221a666 100644 --- a/src/codecs/farbfeld.rs +++ b/src/codecs/farbfeld.rs @@ -16,9 +16,10 @@ //! # Related Links //! * - the farbfeld specification +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::boxed::Box; use alloc::format; -use std::io::{self, Read, Seek, SeekFrom, Write}; use crate::color::ExtendedColorType; use crate::error::{ @@ -27,8 +28,11 @@ use crate::error::{ use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageEncoder, ImageFormat}; use crate::ColorType; +#[cfg(feature = "std")] +use std::io::{self, Read, Seek, SeekFrom, Write}; + /// farbfeld Reader -pub struct FarbfeldReader { +pub struct FarbfeldReader { width: u32, height: u32, inner: R, @@ -37,6 +41,7 @@ pub struct FarbfeldReader { cached_byte: Option, } +#[cfg(feature = "std")] impl FarbfeldReader { fn new(mut buffered_read: R) -> ImageResult> { fn read_dimm(from: &mut R) -> ImageResult { @@ -87,6 +92,7 @@ impl FarbfeldReader { } } +#[cfg(feature = "std")] impl Read for FarbfeldReader { fn read(&mut self, mut buf: &mut [u8]) -> io::Result { let mut bytes_written = 0; @@ -113,6 +119,7 @@ impl Read for FarbfeldReader { } } +#[cfg(feature = "std")] impl Seek for FarbfeldReader { fn seek(&mut self, pos: SeekFrom) -> io::Result { fn parse_offset(original_offset: u64, end_offset: u64, pos: SeekFrom) -> Option { @@ -168,6 +175,7 @@ impl Seek for FarbfeldReader { } } +#[cfg(feature = "std")] fn consume_channel(from: &mut R, mut to: &mut [u8]) -> io::Result<()> { let mut ibuf = [0u8; 2]; from.read_exact(&mut ibuf)?; @@ -176,6 +184,7 @@ fn consume_channel(from: &mut R, mut to: &mut [u8]) -> io::Result<()> { Ok(()) } +#[cfg(feature = "std")] fn cache_byte(from: &mut R, cached_byte: &mut Option) -> io::Result { let mut obuf = [0u8; 2]; consume_channel(from, &mut obuf)?; @@ -184,10 +193,11 @@ fn cache_byte(from: &mut R, cached_byte: &mut Option) -> io::Result } /// farbfeld decoder -pub struct FarbfeldDecoder { +pub struct FarbfeldDecoder { reader: FarbfeldReader, } +#[cfg(feature = "std")] impl FarbfeldDecoder { /// Creates a new decoder that decodes from the stream ```r``` pub fn new(buffered_read: R) -> ImageResult> { @@ -197,6 +207,7 @@ impl FarbfeldDecoder { } } +#[cfg(feature = "std")] impl ImageDecoder for FarbfeldDecoder { fn dimensions(&self) -> (u32, u32) { (self.reader.width, self.reader.height) @@ -217,6 +228,7 @@ impl ImageDecoder for FarbfeldDecoder { } } +#[cfg(feature = "std")] impl ImageDecoderRect for FarbfeldDecoder { fn read_rect( &mut self, @@ -248,16 +260,19 @@ impl ImageDecoderRect for FarbfeldDecoder { } /// farbfeld encoder -pub struct FarbfeldEncoder { +pub struct FarbfeldEncoder { w: W, } -impl FarbfeldEncoder { +impl FarbfeldEncoder { /// Create a new encoder that writes its output to ```w```. The writer should be buffered. pub fn new(buffered_writer: W) -> FarbfeldEncoder { FarbfeldEncoder { w: buffered_writer } } +} +#[cfg(feature = "std")] +impl FarbfeldEncoder { /// Encodes the image `data` (native endian) that has dimensions `width` and `height`. /// /// # Panics @@ -291,6 +306,7 @@ impl FarbfeldEncoder { } } +#[cfg(feature = "std")] impl ImageEncoder for FarbfeldEncoder { #[track_caller] fn write_image( From 5754ac5ccd529ee7cfc9bb0e3c02d5560aa2892c Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:15:59 +1000 Subject: [PATCH 31/42] `no_std` HDR --- .github/workflows/rust.yml | 2 +- Cargo.toml | 2 +- src/codecs/hdr/decoder.rs | 30 ++++++++++++++++++++++++------ src/codecs/hdr/encoder.rs | 20 ++++++++++++++++++-- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4cf5fe461a..6d650407d2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -216,6 +216,6 @@ jobs: with: components: clippy targets: "wasm32v1-none" - - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga,qoi,bmp,dds,ff -- -D warnings + - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga,qoi,bmp,dds,ff,hdr -- -D warnings env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always diff --git a/Cargo.toml b/Cargo.toml index b88ee02a8b..3176a5462c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ dds = [] exr = ["dep:exr", "std"] ff = [] # Farbfeld image format gif = ["dep:gif", "dep:color_quant", "std"] -hdr = ["std"] +hdr = [] ico = ["bmp", "png", "std"] jpeg = ["dep:zune-core", "dep:zune-jpeg", "std"] png = ["dep:png", "std"] diff --git a/src/codecs/hdr/decoder.rs b/src/codecs/hdr/decoder.rs index 91012d697d..e9cf9fd251 100644 --- a/src/codecs/hdr/decoder.rs +++ b/src/codecs/hdr/decoder.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::string::String; @@ -5,7 +7,6 @@ use alloc::vec::Vec; use alloc::{format, vec}; use core::num::{ParseFloatError, ParseIntError}; use core::{error, fmt}; -use std::io::{self, Read}; use crate::color::{ColorType, Rgb}; use crate::error::{ @@ -13,6 +14,12 @@ use crate::error::{ }; use crate::image::{ImageDecoder, ImageFormat}; +#[cfg(feature = "std")] +use std::io::{self, Read}; + +#[cfg(all(not(feature = "std"), feature = "libm"))] +use num_traits::Float; + /// Errors that can occur during decoding and parsing of a HDR image #[derive(Debug, Clone, PartialEq, Eq)] enum DecoderError { @@ -146,6 +153,7 @@ pub(crate) fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel { impl Rgbe8Pixel { /// Converts `Rgbe8Pixel` into `Rgb` linearly + #[cfg(any(feature = "std", feature = "libm"))] #[inline] pub(crate) fn to_hdr(self) -> Rgb { if self.e == 0 { @@ -162,6 +170,7 @@ impl Rgbe8Pixel { } } +#[cfg(feature = "std")] impl HdrDecoder { /// Reads Radiance HDR image header from stream ```r``` /// if the header is valid, creates `HdrDecoder` @@ -261,11 +270,6 @@ impl HdrDecoder { }) } // end with_strictness - /// Returns file metadata. Refer to `HdrMetadata` for details. - pub fn metadata(&self) -> HdrMetadata { - self.meta.clone() - } - /// Consumes decoder and returns a vector of transformed pixels fn read_image_transform T>( mut self, @@ -297,6 +301,14 @@ impl HdrDecoder { } } +impl HdrDecoder { + /// Returns file metadata. Refer to `HdrMetadata` for details. + pub fn metadata(&self) -> HdrMetadata { + self.meta.clone() + } +} + +#[cfg(feature = "std")] impl ImageDecoder for HdrDecoder { fn dimensions(&self) -> (u32, u32) { (self.meta.width, self.meta.height) @@ -325,6 +337,7 @@ impl ImageDecoder for HdrDecoder { } // Precondition: buf.len() > 0 +#[cfg(feature = "std")] fn read_scanline(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> { assert!(!buf.is_empty()); let width = buf.len(); @@ -345,6 +358,7 @@ fn read_scanline(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> Ok(()) } +#[cfg(feature = "std")] #[inline(always)] fn read_byte(r: &mut R) -> io::Result { let mut buf = [0u8]; @@ -353,6 +367,7 @@ fn read_byte(r: &mut R) -> io::Result { } // Guarantees that first parameter of set_component will be within pos .. pos+width +#[cfg(feature = "std")] #[inline] fn decode_component( r: &mut R, @@ -401,6 +416,7 @@ fn decode_component( // Decodes scanline, places it into buf // Precondition: buf.len() > 0 // fb - first 4 bytes of scanline +#[cfg(feature = "std")] fn decode_old_rle(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> { assert!(!buf.is_empty()); let width = buf.len(); @@ -455,6 +471,7 @@ fn decode_old_rle(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> Ok(()) } +#[cfg(feature = "std")] fn read_rgbe(r: &mut R) -> io::Result { let mut buf = [0u8; 4]; r.read_exact(&mut buf[..])?; @@ -688,6 +705,7 @@ fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> // Reads input until b"\n" or EOF // Returns vector of read bytes NOT including end of line characters // or return None to indicate end of file +#[cfg(feature = "std")] fn read_line_u8(r: &mut R) -> io::Result>> { let mut ret = Vec::with_capacity(16); loop { diff --git a/src/codecs/hdr/encoder.rs b/src/codecs/hdr/encoder.rs index a601c02af4..0272d0639e 100644 --- a/src/codecs/hdr/encoder.rs +++ b/src/codecs/hdr/encoder.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use crate::codecs::hdr::{rgbe8, Rgbe8Pixel, SIGNATURE}; use crate::color::Rgb; use crate::error::{EncodingError, ImageFormatHint, ImageResult}; @@ -6,13 +8,22 @@ use alloc::string::ToString; use alloc::vec::Vec; use alloc::{format, vec}; use core::cmp::Ordering; + +#[cfg(feature = "std")] use std::io::{Result, Write}; +#[cfg(all(not(feature = "std"), feature = "libm"))] +use num_traits::Float; + +#[cfg(all(not(feature = "std"), not(feature = "libm")))] +use num_traits::float::FloatCore; + /// Radiance HDR encoder -pub struct HdrEncoder { +pub struct HdrEncoder { w: W, } +#[cfg(feature = "std")] impl ImageEncoder for HdrEncoder { fn write_image( self, @@ -40,12 +51,15 @@ impl ImageEncoder for HdrEncoder { } } -impl HdrEncoder { +impl HdrEncoder { /// Creates encoder pub fn new(w: W) -> HdrEncoder { HdrEncoder { w } } +} +#[cfg(feature = "std")] +impl HdrEncoder { /// Encodes the image ```rgb``` /// that has dimensions ```width``` and ```height``` pub fn encode(self, rgb: &[Rgb], width: usize, height: usize) -> ImageResult<()> { @@ -273,11 +287,13 @@ fn rle_compress(data: &[u8], rle: &mut Vec) { } } +#[cfg(feature = "std")] fn write_rgbe8(w: &mut W, v: Rgbe8Pixel) -> Result<()> { w.write_all(&[v.c[0], v.c[1], v.c[2], v.e]) } /// Converts ```Rgb``` into ```Rgbe8Pixel``` +#[cfg(any(feature = "std", feature = "libm"))] pub(crate) fn to_rgbe8(pix: Rgb) -> Rgbe8Pixel { let pix = pix.0; let mx = f32::max(pix[0], f32::max(pix[1], pix[2])); From d554519955fbf112d425d9a8054123af7bafb819 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:34:48 +1000 Subject: [PATCH 32/42] `no_std` PNM --- .github/workflows/rust.yml | 2 +- Cargo.toml | 2 +- src/codecs/pnm/autobreak.rs | 14 ++++++++++++-- src/codecs/pnm/decoder.rs | 32 ++++++++++++++++++++++++++------ src/codecs/pnm/encoder.rs | 25 +++++++++++++++++++++---- src/codecs/pnm/header.rs | 5 +++++ 6 files changed, 66 insertions(+), 14 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6d650407d2..68320b8701 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -216,6 +216,6 @@ jobs: with: components: clippy targets: "wasm32v1-none" - - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga,qoi,bmp,dds,ff,hdr -- -D warnings + - run: cargo clippy --no-default-features --target wasm32v1-none --features serde,libm,tga,qoi,bmp,dds,ff,hdr,pnm -- -D warnings env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always diff --git a/Cargo.toml b/Cargo.toml index 3176a5462c..829326f4cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ hdr = [] ico = ["bmp", "png", "std"] jpeg = ["dep:zune-core", "dep:zune-jpeg", "std"] png = ["dep:png", "std"] -pnm = ["std"] +pnm = [] qoi = ["dep:qoi"] tga = [] tiff = ["dep:tiff", "std"] diff --git a/src/codecs/pnm/autobreak.rs b/src/codecs/pnm/autobreak.rs index f798220548..5152fb84f1 100644 --- a/src/codecs/pnm/autobreak.rs +++ b/src/codecs/pnm/autobreak.rs @@ -1,11 +1,16 @@ //! Insert line breaks between written buffers when they would overflow the line length. + +#![cfg_attr(not(feature = "std"), expect(dead_code))] + use alloc::vec::Vec; + +#[cfg(feature = "std")] use std::io; // The pnm standard says to insert line breaks after 70 characters. Assumes that no line breaks // are actually written. We have to be careful to fully commit buffers or not commit them at all, // otherwise we might insert a newline in the middle of a token. -pub(crate) struct AutoBreak { +pub(crate) struct AutoBreak { wrapped: W, line_capacity: usize, line: Vec, @@ -13,7 +18,7 @@ pub(crate) struct AutoBreak { panicked: bool, // see https://github.com/rust-lang/rust/issues/30888 } -impl AutoBreak { +impl AutoBreak { pub(crate) fn new(writer: W, line_capacity: usize) -> Self { AutoBreak { wrapped: writer, @@ -23,7 +28,10 @@ impl AutoBreak { panicked: false, } } +} +#[cfg(feature = "std")] +impl AutoBreak { fn flush_buf(&mut self) -> io::Result<()> { // from BufWriter let mut written = 0; @@ -56,6 +64,7 @@ impl AutoBreak { } } +#[cfg(feature = "std")] impl io::Write for AutoBreak { fn write(&mut self, buffer: &[u8]) -> io::Result { if self.has_newline { @@ -80,6 +89,7 @@ impl io::Write for AutoBreak { } } +#[cfg(feature = "std")] impl Drop for AutoBreak { fn drop(&mut self) { if !self.panicked { diff --git a/src/codecs/pnm/decoder.rs b/src/codecs/pnm/decoder.rs index bbe9acb977..2566353012 100644 --- a/src/codecs/pnm/decoder.rs +++ b/src/codecs/pnm/decoder.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::string::{String, ToString}; @@ -8,7 +10,6 @@ use core::fmt::{self, Display}; use core::mem::size_of; use core::num::ParseIntError; use core::str; -use std::io::{self, Read}; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; @@ -21,6 +22,9 @@ use crate::utils; use byteorder_lite::{BigEndian, ByteOrder, NativeEndian}; +#[cfg(feature = "std")] +use std::io::{self, Read}; + /// All errors that can occur when attempting to parse a PNM #[derive(Debug, Clone)] enum DecoderError { @@ -242,6 +246,8 @@ trait Sample { Ok((width * height * samples * Self::sample_size()) as usize) } fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()>; + + #[cfg(feature = "std")] fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()>; } @@ -261,6 +267,7 @@ pub struct PnmDecoder { tuple: TupleType, } +#[cfg(feature = "std")] impl PnmDecoder { /// Create a new decoder that decodes from the stream ```read``` pub fn new(mut buffered_read: R) -> ImageResult> { @@ -304,11 +311,6 @@ impl PnmDecoder { Ok(decoder) } - /// Extract the reader and header after an image has been read. - pub fn into_inner(self) -> (R, PnmHeader) { - (self.reader, self.header) - } - fn read_bitmap_header(mut reader: R, encoding: SampleEncoding) -> ImageResult> { let header = reader.read_bitmap_header(encoding)?; Ok(PnmDecoder { @@ -361,6 +363,14 @@ impl PnmDecoder { } } +impl PnmDecoder { + /// Extract the reader and header after an image has been read. + pub fn into_inner(self) -> (R, PnmHeader) { + (self.reader, self.header) + } +} + +#[cfg(feature = "std")] trait HeaderReader: Read { /// Reads the two magic constant bytes fn read_magic_constant(&mut self) -> ImageResult<[u8; 2]> { @@ -579,8 +589,10 @@ trait HeaderReader: Read { } } +#[cfg(feature = "std")] impl HeaderReader for R where R: Read {} +#[cfg(feature = "std")] impl ImageDecoder for PnmDecoder { fn dimensions(&self) -> (u32, u32) { (self.header.width(), self.header.height()) @@ -625,6 +637,7 @@ impl ImageDecoder for PnmDecoder { } } +#[cfg(feature = "std")] impl PnmDecoder { fn read_samples(&mut self, components: u32, buf: &mut [u8]) -> ImageResult<()> { match self.subtype().sample_encoding() { @@ -683,13 +696,16 @@ impl PnmDecoder { fn read_ascii(&mut self, output_buf: &mut [u8]) -> ImageResult<()> { Basic::from_ascii(&mut self.reader, output_buf) } +} +impl PnmDecoder { /// Get the pnm subtype, depending on the magic constant contained in the header pub fn subtype(&self) -> PnmSubtype { self.header.subtype() } } +#[cfg(feature = "std")] fn read_separated_ascii>(reader: &mut dyn Read) -> ImageResult { let is_separator = |v: &u8| matches!(*v, b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r' | b' '); @@ -726,6 +742,7 @@ impl Sample for U8 { Ok(()) } + #[cfg(feature = "std")] fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { for b in output_buf { *b = read_separated_ascii(reader)?; @@ -746,6 +763,7 @@ impl Sample for U16 { Ok(()) } + #[cfg(feature = "std")] fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { for chunk in output_buf.chunks_exact_mut(2) { let v = read_separated_ascii::(reader)?; @@ -776,6 +794,7 @@ impl Sample for PbmBit { Ok(()) } + #[cfg(feature = "std")] fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { #[allow(clippy::unbuffered_bytes)] let mut bytes = reader.bytes(); @@ -810,6 +829,7 @@ impl Sample for BWBit { Ok(()) } + #[cfg(feature = "std")] fn from_ascii(_reader: &mut dyn Read, _output_buf: &mut [u8]) -> ImageResult<()> { unreachable!("BW bits from anymaps are never encoded as ASCII") } diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index e87616cb5a..4fc992cc03 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -1,9 +1,11 @@ //! Encoding of PNM Images + +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::borrow::ToOwned; use alloc::format; use alloc::vec::Vec; use core::fmt; -use std::io::{self, Write}; use super::AutoBreak; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; @@ -15,7 +17,13 @@ use crate::error::{ }; use crate::image::{ImageEncoder, ImageFormat}; -use byteorder_lite::{BigEndian, WriteBytesExt}; +use byteorder_lite::BigEndian; + +#[cfg(feature = "std")] +use std::io::{self, Write}; + +#[cfg(feature = "std")] +use byteorder_lite::WriteBytesExt; enum HeaderStrategy { Dynamic, @@ -23,6 +31,7 @@ enum HeaderStrategy { Chosen(PnmHeader), } +#[cfg_attr(not(feature = "std"), expect(unreachable_pub))] #[derive(Clone, Copy)] pub enum FlatSamples<'a> { U8(&'a [u8]), @@ -30,7 +39,7 @@ pub enum FlatSamples<'a> { } /// Encodes images to any of the `pnm` image formats. -pub struct PnmEncoder { +pub struct PnmEncoder { writer: W, header: HeaderStrategy, } @@ -79,7 +88,7 @@ enum TupleEncoding<'a> { }, } -impl PnmEncoder { +impl PnmEncoder { /// Create new `PnmEncoder` from the `writer`. /// /// The encoded images will have some `pnm` format. If more control over the image type is @@ -135,7 +144,10 @@ impl PnmEncoder { header: HeaderStrategy::Dynamic, } } +} +#[cfg(feature = "std")] +impl PnmEncoder { /// Encode an image whose samples are represented as `u8`. /// /// Some `pnm` subtypes are incompatible with some color options, a chosen header most @@ -283,6 +295,7 @@ impl PnmEncoder { } } +#[cfg(feature = "std")] impl ImageEncoder for PnmEncoder { #[track_caller] fn write_image( @@ -511,6 +524,7 @@ impl<'a> CheckedHeaderColor<'a> { } impl<'a> CheckedHeader<'a> { + #[cfg(feature = "std")] fn write_header(self, writer: &mut dyn Write) -> ImageResult> { self.header().write(writer)?; Ok(self.encoding) @@ -521,8 +535,10 @@ impl<'a> CheckedHeader<'a> { } } +#[cfg(feature = "std")] struct SampleWriter<'a>(&'a mut dyn Write); +#[cfg(feature = "std")] impl SampleWriter<'_> { fn write_samples_ascii(self, samples: V) -> io::Result<()> where @@ -635,6 +651,7 @@ impl<'a> From<&'a [u16]> for FlatSamples<'a> { } impl TupleEncoding<'_> { + #[cfg(feature = "std")] fn write_image(&self, writer: &mut dyn Write) -> ImageResult<()> { match *self { TupleEncoding::PbmBits { diff --git a/src/codecs/pnm/header.rs b/src/codecs/pnm/header.rs index 19c3d45969..1b380b7ae7 100644 --- a/src/codecs/pnm/header.rs +++ b/src/codecs/pnm/header.rs @@ -1,6 +1,10 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use alloc::string::String; use alloc::vec::Vec; use core::fmt; + +#[cfg(feature = "std")] use std::io; /// The kind of encoding used to store sample values @@ -259,6 +263,7 @@ impl PnmHeader { } /// Write the header back into a binary stream + #[cfg(feature = "std")] pub fn write(&self, writer: &mut dyn io::Write) -> io::Result<()> { writer.write_all(self.subtype().magic_constant())?; match *self { From d7270fe7618f1569a932e06549f781e36158ec8f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:39:05 +1000 Subject: [PATCH 33/42] AutoBreak --- src/codecs/pnm/autobreak.rs | 11 +++++------ src/codecs/pnm/encoder.rs | 5 +++-- src/codecs/pnm/mod.rs | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/codecs/pnm/autobreak.rs b/src/codecs/pnm/autobreak.rs index 5152fb84f1..397bbfbb6c 100644 --- a/src/codecs/pnm/autobreak.rs +++ b/src/codecs/pnm/autobreak.rs @@ -1,6 +1,6 @@ //! Insert line breaks between written buffers when they would overflow the line length. -#![cfg_attr(not(feature = "std"), expect(dead_code))] +#![cfg_attr(not(feature = "std"), expect(unused_imports))] use alloc::vec::Vec; @@ -10,7 +10,8 @@ use std::io; // The pnm standard says to insert line breaks after 70 characters. Assumes that no line breaks // are actually written. We have to be careful to fully commit buffers or not commit them at all, // otherwise we might insert a newline in the middle of a token. -pub(crate) struct AutoBreak { +#[cfg(feature = "std")] +pub(crate) struct AutoBreak { wrapped: W, line_capacity: usize, line: Vec, @@ -18,7 +19,8 @@ pub(crate) struct AutoBreak { panicked: bool, // see https://github.com/rust-lang/rust/issues/30888 } -impl AutoBreak { +#[cfg(feature = "std")] +impl AutoBreak { pub(crate) fn new(writer: W, line_capacity: usize) -> Self { AutoBreak { wrapped: writer, @@ -28,10 +30,7 @@ impl AutoBreak { panicked: false, } } -} -#[cfg(feature = "std")] -impl AutoBreak { fn flush_buf(&mut self) -> io::Result<()> { // from BufWriter let mut written = 0; diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index 4fc992cc03..eb5a1344a6 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -6,8 +6,6 @@ use alloc::borrow::ToOwned; use alloc::format; use alloc::vec::Vec; use core::fmt; - -use super::AutoBreak; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; use crate::color::ExtendedColorType; @@ -19,6 +17,9 @@ use crate::image::{ImageEncoder, ImageFormat}; use byteorder_lite::BigEndian; +#[cfg(feature = "std")] +use super::AutoBreak; + #[cfg(feature = "std")] use std::io::{self, Write}; diff --git a/src/codecs/pnm/mod.rs b/src/codecs/pnm/mod.rs index 1e7f324e93..c56402f161 100644 --- a/src/codecs/pnm/mod.rs +++ b/src/codecs/pnm/mod.rs @@ -3,6 +3,7 @@ //! The formats pbm, pgm and ppm are fully supported. The pam decoder recognizes the tuple types //! `BLACKANDWHITE`, `GRAYSCALE` and `RGB` and explicitly recognizes but rejects their `_ALPHA` //! variants for now as alpha color types are unsupported. +#[cfg(feature = "std")] use self::autobreak::AutoBreak; pub use self::decoder::PnmDecoder; pub use self::encoder::PnmEncoder; From 77f7991de715d128f4ad0ca57f468e6f88b81d10 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:42:42 +1000 Subject: [PATCH 34/42] Ensure `std` is active for tests --- .github/workflows/rust.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 68320b8701..d9f0d1e4f7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - features: [std, default, rayon, avif, bmp, dds, exr, ff, gif, hdr, ico, jpeg, png, pnm, qoi, tga, tiff, webp] + features: ["", default, rayon, avif, bmp, dds, exr, ff, gif, hdr, ico, jpeg, png, pnm, qoi, tga, tiff, webp] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -28,8 +28,8 @@ jobs: FEATURES: ${{ matrix.features }} - name: test run: > - cargo test -v --no-default-features --features "$FEATURES" && - cargo doc -v --no-default-features --features "$FEATURES" + cargo test -v --no-default-features --features "$FEATURES" --features std && + cargo doc -v --no-default-features --features "$FEATURES" --features std env: FEATURES: ${{ matrix.features }} From d5facae4aa200368288c40341700b5ccc4f475af Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 11 May 2025 00:44:12 +1000 Subject: [PATCH 35/42] fmt --- src/codecs/pnm/encoder.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index eb5a1344a6..94229f2de8 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -2,10 +2,6 @@ #![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] -use alloc::borrow::ToOwned; -use alloc::format; -use alloc::vec::Vec; -use core::fmt; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; use crate::color::ExtendedColorType; @@ -14,6 +10,10 @@ use crate::error::{ UnsupportedErrorKind, }; use crate::image::{ImageEncoder, ImageFormat}; +use alloc::borrow::ToOwned; +use alloc::format; +use alloc::vec::Vec; +use core::fmt; use byteorder_lite::BigEndian; From 30faf2b6a4b2ae8e6eb7c069963377918d9b76dd Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 12 May 2025 19:44:51 +1000 Subject: [PATCH 36/42] Clippy --- src/codecs/pnm/decoder.rs | 1 + src/codecs/pnm/encoder.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/codecs/pnm/decoder.rs b/src/codecs/pnm/decoder.rs index 2566353012..9dfc011f00 100644 --- a/src/codecs/pnm/decoder.rs +++ b/src/codecs/pnm/decoder.rs @@ -787,6 +787,7 @@ impl Sample for PbmBit { fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { let mut expanded = utils::expand_bits(1, row_size.try_into().unwrap(), bytes); + #[allow(clippy::manual_slice_fill)] for b in &mut expanded { *b = !*b; } diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index 94229f2de8..6139cad408 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -524,7 +524,7 @@ impl<'a> CheckedHeaderColor<'a> { } } -impl<'a> CheckedHeader<'a> { +impl CheckedHeader<'_> { #[cfg(feature = "std")] fn write_header(self, writer: &mut dyn Write) -> ImageResult> { self.header().write(writer)?; From b8311aadcdb564572ff9effeded9c8ba3c9137cb Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 12 May 2025 20:57:22 +1000 Subject: [PATCH 37/42] Clippy --- src/image_reader/image_reader_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index a9b79f5859..acf186fecb 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -162,7 +162,7 @@ impl<'a, R: 'a + BufRead + Seek> ImageReader { fn make_decoder( format: ImageFormat, reader: R, - limits_for_png: super::Limits, + #[cfg_attr(not(feature = "png"), expect(unused_variables))] limits_for_png: super::Limits, ) -> ImageResult> { use crate::codecs::*; From a576e4b9263dc6b059844c9c436b271fd585c844 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 12 May 2025 21:48:44 +1000 Subject: [PATCH 38/42] Organise imports to reduce `std` gates --- src/buffer.rs | 12 ++++++----- src/codecs/bmp/decoder.rs | 8 +++---- src/codecs/bmp/encoder.rs | 8 +++---- src/codecs/dds.rs | 5 +---- src/codecs/hdr/decoder.rs | 2 +- src/codecs/hdr/encoder.rs | 4 ++-- src/codecs/pnm/encoder.rs | 12 +++++------ src/codecs/pnm/mod.rs | 5 +++-- src/codecs/tga/decoder.rs | 8 +++---- src/codecs/tga/header.rs | 8 +++---- src/dynimage.rs | 25 +++++++++++----------- src/image.rs | 4 +--- src/image_reader/free_functions.rs | 30 +++++++++++---------------- src/image_reader/image_reader_type.rs | 21 +++++++------------ src/imageops/mod.rs | 12 +++++------ 15 files changed, 73 insertions(+), 91 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 9ae2e6a4d6..3d2e92a6ca 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -10,18 +10,20 @@ use num_traits::Zero; use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; use crate::error::ImageResult; use crate::flat::{FlatSamples, SampleLayout}; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::image::ImageFormat; use crate::image::{GenericImage, GenericImageView, ImageEncoder}; use crate::math::Rect; use crate::traits::{EncodableLayout, Pixel, PixelWithColorType}; use crate::utils::expand_packed; use crate::DynamicImage; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] +use crate::image::ImageFormat; + #[cfg(feature = "std")] -use crate::dynimage::{save_buffer, save_buffer_with_format, write_buffer_with_format}; -#[cfg(feature = "std")] -use std::path::Path; +use { + crate::dynimage::{save_buffer, save_buffer_with_format, write_buffer_with_format}, + std::path::Path, +}; /// Iterate over pixel refs. pub struct Pixels<'a, P: Pixel + 'a> diff --git a/src/codecs/bmp/decoder.rs b/src/codecs/bmp/decoder.rs index fc6187ff4f..c893d3fb9b 100644 --- a/src/codecs/bmp/decoder.rs +++ b/src/codecs/bmp/decoder.rs @@ -19,10 +19,10 @@ use crate::image::{self, ImageDecoder, ImageFormat}; use crate::ImageDecoderRect; #[cfg(feature = "std")] -use std::io::{BufRead, Seek, SeekFrom}; - -#[cfg(feature = "std")] -use byteorder_lite::ReadBytesExt; +use { + byteorder_lite::ReadBytesExt, + std::io::{BufRead, Seek, SeekFrom}, +}; const BITMAPCOREHEADER_SIZE: u32 = 12; const BITMAPINFOHEADER_SIZE: u32 = 40; diff --git a/src/codecs/bmp/encoder.rs b/src/codecs/bmp/encoder.rs index af126feca4..4ae5ef7ac7 100644 --- a/src/codecs/bmp/encoder.rs +++ b/src/codecs/bmp/encoder.rs @@ -11,10 +11,10 @@ use crate::image::ImageEncoder; use crate::{ExtendedColorType, ImageFormat}; #[cfg(feature = "std")] -use std::io::{self, Write}; - -#[cfg(feature = "std")] -use byteorder_lite::WriteBytesExt; +use { + byteorder_lite::WriteBytesExt, + std::io::{self, Write}, +}; const BITMAPFILEHEADER_SIZE: u32 = 14; const BITMAPINFOHEADER_SIZE: u32 = 40; diff --git a/src/codecs/dds.rs b/src/codecs/dds.rs index 6a3cce2ddc..57c53177fd 100644 --- a/src/codecs/dds.rs +++ b/src/codecs/dds.rs @@ -22,10 +22,7 @@ use crate::error::{ use crate::image::{ImageDecoder, ImageFormat}; #[cfg(feature = "std")] -use std::io::Read; - -#[cfg(feature = "std")] -use byteorder_lite::ReadBytesExt; +use {byteorder_lite::ReadBytesExt, std::io::Read}; /// Errors that can occur during decoding and parsing a DDS image #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/codecs/hdr/decoder.rs b/src/codecs/hdr/decoder.rs index e9cf9fd251..9446955de2 100644 --- a/src/codecs/hdr/decoder.rs +++ b/src/codecs/hdr/decoder.rs @@ -18,7 +18,7 @@ use crate::image::{ImageDecoder, ImageFormat}; use std::io::{self, Read}; #[cfg(all(not(feature = "std"), feature = "libm"))] -use num_traits::Float; +use num_traits::Float as _; /// Errors that can occur during decoding and parsing of a HDR image #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/codecs/hdr/encoder.rs b/src/codecs/hdr/encoder.rs index 0272d0639e..1198c3a352 100644 --- a/src/codecs/hdr/encoder.rs +++ b/src/codecs/hdr/encoder.rs @@ -13,10 +13,10 @@ use core::cmp::Ordering; use std::io::{Result, Write}; #[cfg(all(not(feature = "std"), feature = "libm"))] -use num_traits::Float; +use num_traits::Float as _; #[cfg(all(not(feature = "std"), not(feature = "libm")))] -use num_traits::float::FloatCore; +use num_traits::float::FloatCore as _; /// Radiance HDR encoder pub struct HdrEncoder { diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index 6139cad408..3aa2a06a01 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -18,13 +18,11 @@ use core::fmt; use byteorder_lite::BigEndian; #[cfg(feature = "std")] -use super::AutoBreak; - -#[cfg(feature = "std")] -use std::io::{self, Write}; - -#[cfg(feature = "std")] -use byteorder_lite::WriteBytesExt; +use { + super::AutoBreak, + byteorder_lite::WriteBytesExt, + std::io::{self, Write}, +}; enum HeaderStrategy { Dynamic, diff --git a/src/codecs/pnm/mod.rs b/src/codecs/pnm/mod.rs index c56402f161..eeec35a68e 100644 --- a/src/codecs/pnm/mod.rs +++ b/src/codecs/pnm/mod.rs @@ -3,8 +3,6 @@ //! The formats pbm, pgm and ppm are fully supported. The pam decoder recognizes the tuple types //! `BLACKANDWHITE`, `GRAYSCALE` and `RGB` and explicitly recognizes but rejects their `_ALPHA` //! variants for now as alpha color types are unsupported. -#[cfg(feature = "std")] -use self::autobreak::AutoBreak; pub use self::decoder::PnmDecoder; pub use self::encoder::PnmEncoder; use self::header::HeaderRecord; @@ -13,6 +11,9 @@ pub use self::header::{ }; pub use self::header::{PnmHeader, PnmSubtype, SampleEncoding}; +#[cfg(feature = "std")] +use self::autobreak::AutoBreak; + mod autobreak; mod decoder; mod encoder; diff --git a/src/codecs/tga/decoder.rs b/src/codecs/tga/decoder.rs index 4479e03ae3..73a4e6dce1 100644 --- a/src/codecs/tga/decoder.rs +++ b/src/codecs/tga/decoder.rs @@ -13,10 +13,10 @@ use alloc::vec; use alloc::vec::Vec; #[cfg(feature = "std")] -use byteorder_lite::ReadBytesExt; - -#[cfg(feature = "std")] -use std::io::{self, Read}; +use { + byteorder_lite::ReadBytesExt, + std::io::{self, Read}, +}; struct ColorMap { /// sizes in bytes diff --git a/src/codecs/tga/header.rs b/src/codecs/tga/header.rs index c564e50066..4028e229fd 100644 --- a/src/codecs/tga/header.rs +++ b/src/codecs/tga/header.rs @@ -5,10 +5,10 @@ use crate::{ExtendedColorType, ImageError, ImageFormat, ImageResult}; use byteorder_lite::LittleEndian; #[cfg(feature = "std")] -use std::io::{Read, Write}; - -#[cfg(feature = "std")] -use byteorder_lite::{ReadBytesExt, WriteBytesExt}; +use { + byteorder_lite::{ReadBytesExt, WriteBytesExt}, + std::io::{Read, Write}, +}; pub(crate) const ALPHA_BIT_MASK: u8 = 0b1111; pub(crate) const SCREEN_ORIGIN_BIT_MASK: u8 = 0b10_0000; diff --git a/src/dynimage.rs b/src/dynimage.rs index 2106c01f77..3457c8704f 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -6,11 +6,6 @@ use crate::codecs::gif; #[cfg(feature = "png")] use crate::codecs::png; -#[cfg(feature = "std")] -use std::io::{self, Seek, Write}; -#[cfg(feature = "std")] -use std::path::Path; - use crate::buffer_::{ ConvertBuffer, Gray16Image, GrayAlpha16Image, GrayAlphaImage, GrayImage, ImageBuffer, Rgb16Image, RgbImage, Rgba16Image, RgbaImage, @@ -18,22 +13,26 @@ use crate::buffer_::{ use crate::color::{self, FromColor, IntoColor}; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::flat::FlatSamples; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::image::ImageFormat; use crate::image::{GenericImage, GenericImageView, ImageDecoder, ImageEncoder}; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::image_reader::free_functions; use crate::imageops; use crate::math::resize_dimensions; use crate::metadata::Orientation; use crate::traits::Pixel; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::ExtendedColorType; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::ImageReader; use crate::{image, Luma, LumaA}; use crate::{Rgb32FImage, Rgba32FImage}; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] +use { + crate::image::ImageFormat, crate::image_reader::free_functions, crate::ExtendedColorType, + crate::ImageReader, +}; + +#[cfg(feature = "std")] +use { + std::io::{self, Seek, Write}, + std::path::Path, +}; + /// A Dynamic Image /// /// This represents a _matrix_ of _pixels_ which are _convertible_ from and to an _RGBA_ diff --git a/src/image.rs b/src/image.rs index 10e9ca5e43..28e32fc01e 100644 --- a/src/image.rs +++ b/src/image.rs @@ -16,9 +16,7 @@ use crate::traits::Pixel; use crate::ImageBuffer; #[cfg(feature = "std")] -use std::ffi::OsStr; -#[cfg(feature = "std")] -use std::path::Path; +use {std::ffi::OsStr, std::path::Path}; /// An enumeration of supported image formats. /// Not all formats support both encoding and decoding. diff --git a/src/image_reader/free_functions.rs b/src/image_reader/free_functions.rs index cb95429dde..b49d2c9bc7 100644 --- a/src/image_reader/free_functions.rs +++ b/src/image_reader/free_functions.rs @@ -1,30 +1,24 @@ -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use alloc::format; use core::iter; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::codecs::*; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::dynimage::DynamicImage; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::error::UnsupportedError; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::error::UnsupportedErrorKind; use crate::error::{ImageError, ImageFormatHint, ImageResult}; use crate::image::ImageFormat; + #[allow(unused_imports)] // When no features are supported use crate::image::{ImageDecoder, ImageEncoder}; + #[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::ExtendedColorType; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::ImageReader; +use { + crate::codecs::*, crate::dynimage::DynamicImage, crate::error::UnsupportedError, + crate::error::UnsupportedErrorKind, crate::ExtendedColorType, crate::ImageReader, + alloc::format, +}; #[cfg(feature = "std")] -use std::fs::File; -#[cfg(feature = "std")] -use std::io::{BufRead, BufWriter, Seek}; -#[cfg(feature = "std")] -use std::path::Path; +use { + std::fs::File, + std::io::{BufRead, BufWriter, Seek}, + std::path::Path, +}; /// Create a new image from a Reader. /// diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index acf186fecb..dbcd6e59a3 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -1,23 +1,18 @@ -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use alloc::boxed::Box; - -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::dynimage::DynamicImage; use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::image::ImageFormat; -#[cfg_attr(not(feature = "std"), expect(unused_imports))] -use crate::ImageDecoder; use crate::{ImageError, ImageResult}; #[cfg_attr(not(feature = "std"), expect(unused_imports))] -use super::free_functions; +use { + super::free_functions, crate::dynimage::DynamicImage, crate::ImageDecoder, alloc::boxed::Box, +}; #[cfg(feature = "std")] -use std::fs::File; -#[cfg(feature = "std")] -use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; -#[cfg(feature = "std")] -use std::path::Path; +use { + std::fs::File, + std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}, + std::path::Path, +}; /// A multi-format image reader. /// diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index ab4ebf32e7..4c9446a4b3 100644 --- a/src/imageops/mod.rs +++ b/src/imageops/mod.rs @@ -20,18 +20,12 @@ pub use self::sample::{ thumbnail, }; -#[cfg(any(feature = "std", feature = "libm"))] -pub use self::sample::{blur, unsharpen}; - /// Color operations pub use self::colorops::{ brighten, contrast, dither, grayscale, grayscale_alpha, grayscale_with_type, grayscale_with_type_alpha, index_colors, invert, BiLevel, ColorMap, }; -#[cfg(any(feature = "std", feature = "libm"))] -pub use self::colorops::huerotate; - mod affine; // Public only because of Rust bug: // https://github.com/rust-lang/rust/issues/18241 @@ -41,7 +35,11 @@ mod fast_blur; mod sample; #[cfg(any(feature = "std", feature = "libm"))] -pub use fast_blur::fast_blur; +pub use { + self::colorops::huerotate, + self::sample::{blur, unsharpen}, + fast_blur::fast_blur, +}; /// Return a mutable view into an image /// The coordinates set the position of the top left corner of the crop. From ade419ab2b89aafdac5704efb58531e7d45dda90 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 12 May 2025 21:49:17 +1000 Subject: [PATCH 39/42] Proof of Concept for `no_std` in Qoi --- src/codecs/qoi.rs | 75 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/src/codecs/qoi.rs b/src/codecs/qoi.rs index f5724a31a8..62524eaa0a 100644 --- a/src/codecs/qoi.rs +++ b/src/codecs/qoi.rs @@ -1,7 +1,5 @@ //! Decoding and encoding of QOI images -#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] - use alloc::boxed::Box; use alloc::format; use alloc::vec::Vec; @@ -16,36 +14,72 @@ use std::io::{Read, Write}; /// QOI decoder pub struct QoiDecoder { - decoder: qoi::Decoder, + inner: R, +} + +impl QoiDecoder<(R, qoi::Header)> +where + R: AsRef<[u8]>, +{ + /// Creates a new decoder that decodes from the slice reference ```bytes``` + pub fn from_bytes(bytes: R) -> ImageResult { + let header = qoi::decode_header(&bytes).map_err(decoding_error)?; + let inner = (bytes, header); + + Ok(Self { inner }) + } +} + +impl> ImageDecoder for QoiDecoder<(R, qoi::Header)> { + fn dimensions(&self) -> (u32, u32) { + (self.inner.1.width, self.inner.1.height) + } + + fn color_type(&self) -> ColorType { + match self.inner.1.channels { + qoi::Channels::Rgb => ColorType::Rgb8, + qoi::Channels::Rgba => ColorType::Rgba8, + } + } + + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { + qoi::decode_to_buf(buf, &self.inner.0).map_err(decoding_error)?; + Ok(()) + } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } #[cfg(feature = "std")] -impl QoiDecoder +impl QoiDecoder> where R: Read, { /// Creates a new decoder that decodes from the stream ```reader``` pub fn new(reader: R) -> ImageResult { - let decoder = qoi::Decoder::from_stream(reader).map_err(decoding_error)?; - Ok(Self { decoder }) + let inner = qoi::Decoder::from_stream(reader).map_err(decoding_error)?; + + Ok(Self { inner }) } } #[cfg(feature = "std")] -impl ImageDecoder for QoiDecoder { +impl ImageDecoder for QoiDecoder> { fn dimensions(&self) -> (u32, u32) { - (self.decoder.header().width, self.decoder.header().height) + (self.inner.header().width, self.inner.header().height) } fn color_type(&self) -> ColorType { - match self.decoder.header().channels { + match self.inner.header().channels { qoi::Channels::Rgb => ColorType::Rgb8, qoi::Channels::Rgba => ColorType::Rgba8, } } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { - self.decoder.decode_to_buf(buf).map_err(decoding_error)?; + self.inner.decode_to_buf(buf).map_err(decoding_error)?; Ok(()) } @@ -112,6 +146,27 @@ impl ImageEncoder for QoiEncoder { } } +// Note that ImageEncoder should only be implemented for QoiEncoder where W would implement +// std::io::Write with the std feature enabled. +#[cfg(not(feature = "std"))] +impl ImageEncoder for QoiEncoder> { + #[track_caller] + fn write_image( + mut self, + buf: &[u8], + width: u32, + height: u32, + color_type: ExtendedColorType, + ) -> ImageResult<()> { + let mut data = write_image_to_vec(buf, width, height, color_type)?; + + // Write data to buffer + self.writer.append(&mut data); + + Ok(()) + } +} + #[track_caller] fn write_image_to_vec( buf: &[u8], From dfc76ca788c085bbf42b29d568522ca8fb4150df Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 12 May 2025 21:51:13 +1000 Subject: [PATCH 40/42] Clippy --- src/codecs/pnm/encoder.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index 3aa2a06a01..64a2b1313e 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -522,7 +522,8 @@ impl<'a> CheckedHeaderColor<'a> { } } -impl CheckedHeader<'_> { +#[cfg_attr(not(feature = "std"), expect(clippy::needless_lifetimes))] +impl<'a> CheckedHeader<'a> { #[cfg(feature = "std")] fn write_header(self, writer: &mut dyn Write) -> ImageResult> { self.header().write(writer)?; From 36213e1c7b78c3edc38bd25931d4083830c42b34 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 12 May 2025 21:57:17 +1000 Subject: [PATCH 41/42] Avoid public/private --- src/codecs/qoi.rs | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/codecs/qoi.rs b/src/codecs/qoi.rs index 62524eaa0a..ee263525f4 100644 --- a/src/codecs/qoi.rs +++ b/src/codecs/qoi.rs @@ -17,33 +17,44 @@ pub struct QoiDecoder { inner: R, } -impl QoiDecoder<(R, qoi::Header)> +/// Intermediate Qoi decoder. +pub struct ReaderDecoder { + decoder: qoi::Decoder, +} + +/// Intermediate Qoi decoder. +pub struct SliceDecoder { + bytes: R, + header: qoi::Header, +} + +impl QoiDecoder> where R: AsRef<[u8]>, { /// Creates a new decoder that decodes from the slice reference ```bytes``` pub fn from_bytes(bytes: R) -> ImageResult { let header = qoi::decode_header(&bytes).map_err(decoding_error)?; - let inner = (bytes, header); + let inner = SliceDecoder { bytes, header }; Ok(Self { inner }) } } -impl> ImageDecoder for QoiDecoder<(R, qoi::Header)> { +impl> ImageDecoder for QoiDecoder> { fn dimensions(&self) -> (u32, u32) { - (self.inner.1.width, self.inner.1.height) + (self.inner.header.width, self.inner.header.height) } fn color_type(&self) -> ColorType { - match self.inner.1.channels { + match self.inner.header.channels { qoi::Channels::Rgb => ColorType::Rgb8, qoi::Channels::Rgba => ColorType::Rgba8, } } fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { - qoi::decode_to_buf(buf, &self.inner.0).map_err(decoding_error)?; + qoi::decode_to_buf(buf, &self.inner.bytes).map_err(decoding_error)?; Ok(()) } @@ -53,33 +64,40 @@ impl> ImageDecoder for QoiDecoder<(R, qoi::Header)> { } #[cfg(feature = "std")] -impl QoiDecoder> +impl QoiDecoder> where R: Read, { /// Creates a new decoder that decodes from the stream ```reader``` pub fn new(reader: R) -> ImageResult { - let inner = qoi::Decoder::from_stream(reader).map_err(decoding_error)?; + let decoder = qoi::Decoder::from_stream(reader).map_err(decoding_error)?; + let inner = ReaderDecoder { decoder }; Ok(Self { inner }) } } #[cfg(feature = "std")] -impl ImageDecoder for QoiDecoder> { +impl ImageDecoder for QoiDecoder> { fn dimensions(&self) -> (u32, u32) { - (self.inner.header().width, self.inner.header().height) + ( + self.inner.decoder.header().width, + self.inner.decoder.header().height, + ) } fn color_type(&self) -> ColorType { - match self.inner.header().channels { + match self.inner.decoder.header().channels { qoi::Channels::Rgb => ColorType::Rgb8, qoi::Channels::Rgba => ColorType::Rgba8, } } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { - self.inner.decode_to_buf(buf).map_err(decoding_error)?; + self.inner + .decoder + .decode_to_buf(buf) + .map_err(decoding_error)?; Ok(()) } From c0f3e8d781db7534c074596b2fa938b76716b6e1 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 12 May 2025 21:59:31 +1000 Subject: [PATCH 42/42] Clippy --- src/codecs/qoi.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/codecs/qoi.rs b/src/codecs/qoi.rs index ee263525f4..dcff94adea 100644 --- a/src/codecs/qoi.rs +++ b/src/codecs/qoi.rs @@ -19,6 +19,7 @@ pub struct QoiDecoder { /// Intermediate Qoi decoder. pub struct ReaderDecoder { + #[cfg_attr(not(feature = "std"), expect(dead_code))] decoder: qoi::Decoder, }