diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dcc67f776f..d9f0d1e4f7 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: ["", 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 }} @@ -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: @@ -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,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 74eb6094db..829326f4cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "image" -version = "0.25.6" +version = "0.26.0" 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." @@ -36,25 +36,28 @@ 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 } -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 } 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 } 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"] } + +# Patches +num-bigint = { version = "0.4.2", default-features = false, optional = true } [dev-dependencies] crc32fast = "1.2.0" @@ -64,33 +67,35 @@ 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"] +avif = ["dep:ravif", "dep:rgb", "std", "dep:num-bigint"] bmp = [] dds = [] -exr = ["dep:exr"] +exr = ["dep:exr", "std"] ff = [] # Farbfeld image format -gif = ["dep:gif", "dep:color_quant"] +gif = ["dep:gif", "dep:color_quant", "std"] hdr = [] -ico = ["bmp", "png"] -jpeg = ["dep:zune-core", "dep:zune-jpeg"] -png = ["dep:png"] +ico = ["bmp", "png", "std"] +jpeg = ["dep:zune-core", "dep:zune-jpeg", "std"] +png = ["dep:png", "std"] pnm = [] qoi = ["dep:qoi"] tga = [] -tiff = ["dep:tiff"] -webp = ["dep:image-webp"] +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) +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"] # 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) +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", "qoi?/std"] +libm = ["num-traits/libm"] [[bench]] path = "benches/decode.rs" 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/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..3d2e92a6ca 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,21 +1,30 @@ //! 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::{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}, + std::path::Path, +}; + /// Iterate over pixel refs. pub struct Pixels<'a, P: Pixel + 'a> where @@ -993,6 +1002,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 +1029,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 +1057,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 +1727,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..c9863fdbeb 100644 --- a/src/codecs/avif/decoder.rs +++ b/src/codecs/avif/decoder.rs @@ -4,14 +4,19 @@ 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. /// /// [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 +43,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 +132,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 +165,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 +179,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, } } @@ -195,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); @@ -209,13 +214,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 +228,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..313a932af4 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( @@ -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/bmp/decoder.rs b/src/codecs/bmp/decoder.rs index 109470cbf8..c893d3fb9b 100644 --- a/src/codecs/bmp/decoder.rs +++ b/src/codecs/bmp/decoder.rs @@ -1,10 +1,15 @@ -use std::cmp::{self, Ordering}; -use std::io::{self, BufRead, Seek, SeekFrom}; -use std::iter::{repeat, Rev}; -use std::slice::ChunksMut; -use std::{error, fmt}; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] -use byteorder_lite::{LittleEndian, ReadBytesExt}; +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 byteorder_lite::LittleEndian; use crate::color::ColorType; use crate::error::{ @@ -13,6 +18,12 @@ use crate::error::{ use crate::image::{self, ImageDecoder, ImageFormat}; use crate::ImageDecoderRect; +#[cfg(feature = "std")] +use { + byteorder_lite::ReadBytesExt, + std::io::{BufRead, Seek, SeekFrom}, +}; + const BITMAPCOREHEADER_SIZE: u32 = 12; const BITMAPINFOHEADER_SIZE: u32 = 40; const BITMAPV2HEADER_SIZE: u32 = 52; @@ -263,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. @@ -498,7 +509,7 @@ enum RLEInsn { PixelRun(u8, u8), } -impl BmpDecoder { +impl BmpDecoder { fn new_decoder(reader: R) -> BmpDecoder { BmpDecoder { reader, @@ -522,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); @@ -546,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(()); @@ -854,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. @@ -918,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; @@ -990,7 +1004,7 @@ impl BmpDecoder { _ => panic!(), }; } - Ok(()) + ImageResult::Ok(()) }, )?; @@ -1071,7 +1085,7 @@ impl BmpDecoder { } } } - Ok(()) + ImageResult::Ok(()) }, )?; @@ -1166,7 +1180,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]; @@ -1324,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) @@ -1349,6 +1364,7 @@ impl ImageDecoder for BmpDecoder { } } +#[cfg(feature = "std")] impl ImageDecoderRect for BmpDecoder { fn read_rect( &mut self, @@ -1369,7 +1385,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/codecs/bmp/encoder.rs b/src/codecs/bmp/encoder.rs index 457e5d9d12..4ae5ef7ac7 100644 --- a/src/codecs/bmp/encoder.rs +++ b/src/codecs/bmp/encoder.rs @@ -1,5 +1,8 @@ -use byteorder_lite::{LittleEndian, WriteBytesExt}; -use std::io::{self, Write}; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + +use alloc::format; +use alloc::string::String; +use byteorder_lite::LittleEndian; use crate::error::{ EncodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, @@ -7,6 +10,12 @@ use crate::error::{ use crate::image::ImageEncoder; use crate::{ExtendedColorType, ImageFormat}; +#[cfg(feature = "std")] +use { + byteorder_lite::WriteBytesExt, + std::io::{self, Write}, +}; + const BITMAPFILEHEADER_SIZE: u32 = 14; const BITMAPINFOHEADER_SIZE: u32 = 40; const BITMAPV4HEADER_SIZE: u32 = 108; @@ -16,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 @@ -72,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) @@ -272,6 +289,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { } } +#[cfg(feature = "std")] impl ImageEncoder for BmpEncoder<'_, W> { #[track_caller] fn write_image( @@ -290,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), @@ -307,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)] diff --git a/src/codecs/dds.rs b/src/codecs/dds.rs index e23a23beb2..57c53177fd 100644 --- a/src/codecs/dds.rs +++ b/src/codecs/dds.rs @@ -5,12 +5,15 @@ //! # Related Links //! * - Description of the DDS format. -use std::io::Read; -use std::{error, fmt}; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] -use byteorder_lite::{LittleEndian, ReadBytesExt}; +use alloc::boxed::Box; +use alloc::format; +use alloc::string::ToString; +use core::{error, fmt}; + +use byteorder_lite::LittleEndian; -#[allow(deprecated)] use crate::codecs::dxt::{DxtDecoder, DxtVariant}; use crate::color::ColorType; use crate::error::{ @@ -18,9 +21,12 @@ use crate::error::{ }; use crate::image::{ImageDecoder, ImageFormat}; +#[cfg(feature = "std")] +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)] -#[allow(clippy::enum_variant_names)] +#[expect(clippy::enum_variant_names)] enum DecoderError { /// Wrong DDS channel width PixelFormatSizeInvalid(u32), @@ -116,6 +122,7 @@ struct PixelFormat { } impl PixelFormat { + #[cfg(feature = "std")] fn from_reader(r: &mut dyn Read) -> ImageResult { let size = r.read_u32::()?; if size != 32 { @@ -139,6 +146,7 @@ impl PixelFormat { } impl Header { + #[cfg(feature = "std")] fn from_reader(r: &mut dyn Read) -> ImageResult { let size = r.read_u32::()?; if size != 124 { @@ -186,6 +194,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::()?; @@ -240,11 +249,11 @@ impl DX10Header { } /// The representation of a DDS decoder -pub struct DdsDecoder { - #[allow(deprecated)] +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 { @@ -257,7 +266,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, @@ -294,7 +302,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) @@ -310,7 +317,6 @@ impl DdsDecoder { )); } - #[allow(deprecated)] let inner = DxtDecoder::new(r, header.width, header.height, variant)?; Ok(Self { inner }) } else { @@ -325,6 +331,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 9f3ee253d3..7b1457d5d1 100644 --- a/src/codecs/dxt.rs +++ b/src/codecs/dxt.rs @@ -7,12 +7,18 @@ //! //! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds -use std::io::{self, Read}; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + +use alloc::boxed::Box; +use alloc::vec; 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 @@ -57,7 +63,7 @@ impl DxtVariant { } /// DXT decoder -pub(crate) struct DxtDecoder { +pub(crate) struct DxtDecoder { inner: R, width_blocks: u32, height_blocks: u32, @@ -65,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 @@ -101,15 +107,12 @@ 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( - #[allow(deprecated)] - 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]; @@ -126,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) @@ -138,7 +142,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)?; } @@ -224,7 +227,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/farbfeld.rs b/src/codecs/farbfeld.rs index 32fdbc9c1e..988221a666 100644 --- a/src/codecs/farbfeld.rs +++ b/src/codecs/farbfeld.rs @@ -16,7 +16,10 @@ //! # Related Links //! * - the farbfeld specification -use std::io::{self, Read, Seek, SeekFrom, Write}; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + +use alloc::boxed::Box; +use alloc::format; use crate::color::ExtendedColorType; use crate::error::{ @@ -25,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, @@ -35,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 { @@ -85,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; @@ -111,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 { @@ -166,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)?; @@ -174,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)?; @@ -182,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> { @@ -195,6 +207,7 @@ impl FarbfeldDecoder { } } +#[cfg(feature = "std")] impl ImageDecoder for FarbfeldDecoder { fn dimensions(&self) -> (u32, u32) { (self.reader.width, self.reader.height) @@ -215,6 +228,7 @@ impl ImageDecoder for FarbfeldDecoder { } } +#[cfg(feature = "std")] impl ImageDecoderRect for FarbfeldDecoder { fn read_rect( &mut self, @@ -246,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 @@ -289,6 +306,7 @@ impl FarbfeldEncoder { } } +#[cfg(feature = "std")] impl ImageEncoder for FarbfeldEncoder { #[track_caller] fn write_image( diff --git a/src/codecs/gif.rs b/src/codecs/gif.rs index f67a161306..4dcec922fb 100644 --- a/src/codecs/gif.rs +++ b/src/codecs/gif.rs @@ -25,11 +25,14 @@ //! # Ok(()) //! # } //! ``` -#![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}; @@ -68,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/hdr/decoder.rs b/src/codecs/hdr/decoder.rs index 08f8483d5f..9446955de2 100644 --- a/src/codecs/hdr/decoder.rs +++ b/src/codecs/hdr/decoder.rs @@ -1,7 +1,12 @@ -use std::io::{self, Read}; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] -use std::num::{ParseFloatError, ParseIntError}; -use std::{error, fmt}; +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 crate::color::{ColorType, Rgb}; use crate::error::{ @@ -9,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 as _; + /// Errors that can occur during decoding and parsing of a HDR image #[derive(Debug, Clone, PartialEq, Eq)] enum DecoderError { @@ -142,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 { @@ -158,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` @@ -257,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, @@ -293,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) @@ -321,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(); @@ -341,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]; @@ -349,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, @@ -397,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(); @@ -451,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[..])?; @@ -684,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 { @@ -701,7 +723,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..1198c3a352 100644 --- a/src/codecs/hdr/encoder.rs +++ b/src/codecs/hdr/encoder.rs @@ -1,15 +1,29 @@ +#![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}; 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; + +#[cfg(feature = "std")] use std::io::{Result, Write}; +#[cfg(all(not(feature = "std"), feature = "libm"))] +use num_traits::Float as _; + +#[cfg(all(not(feature = "std"), not(feature = "libm")))] +use num_traits::float::FloatCore as _; + /// Radiance HDR encoder -pub struct HdrEncoder { +pub struct HdrEncoder { w: W, } +#[cfg(feature = "std")] impl ImageEncoder for HdrEncoder { fn write_image( self, @@ -37,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<()> { @@ -270,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])); @@ -466,7 +485,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..da64fa051b 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::{ @@ -121,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, @@ -335,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/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/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/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..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, @@ -7,8 +5,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..06ad413a38 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}; @@ -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/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..397bbfbb6c 100644 --- a/src/codecs/pnm/autobreak.rs +++ b/src/codecs/pnm/autobreak.rs @@ -1,9 +1,16 @@ //! Insert line breaks between written buffers when they would overflow the line length. + +#![cfg_attr(not(feature = "std"), expect(unused_imports))] + +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. +#[cfg(feature = "std")] pub(crate) struct AutoBreak { wrapped: W, line_capacity: usize, @@ -12,6 +19,7 @@ pub(crate) struct AutoBreak { panicked: bool, // see https://github.com/rust-lang/rust/issues/30888 } +#[cfg(feature = "std")] impl AutoBreak { pub(crate) fn new(writer: W, line_capacity: usize) -> Self { AutoBreak { @@ -55,6 +63,7 @@ impl AutoBreak { } } +#[cfg(feature = "std")] impl io::Write for AutoBreak { fn write(&mut self, buffer: &[u8]) -> io::Result { if self.has_newline { @@ -79,6 +88,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 61a2544720..9dfc011f00 100644 --- a/src/codecs/pnm/decoder.rs +++ b/src/codecs/pnm/decoder.rs @@ -1,9 +1,15 @@ -use std::error; -use std::fmt::{self, Display}; -use std::io::{self, Read}; -use std::mem::size_of; -use std::num::ParseIntError; -use std::str; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + +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 super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; @@ -16,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 { @@ -43,7 +52,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, @@ -238,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<()>; } @@ -257,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> { @@ -300,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 { @@ -357,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]> { @@ -515,7 +529,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())); @@ -575,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()) @@ -621,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() { @@ -679,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' '); @@ -722,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)?; @@ -742,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)?; @@ -765,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; } @@ -772,6 +795,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(); @@ -806,6 +830,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 4b6cc18fb0..64a2b1313e 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -1,10 +1,7 @@ //! Encoding of PNM Images -use std::fmt; -use std::io; -use std::io::Write; +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] -use super::AutoBreak; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; use crate::color::ExtendedColorType; @@ -13,8 +10,19 @@ use crate::error::{ UnsupportedErrorKind, }; use crate::image::{ImageEncoder, ImageFormat}; - -use byteorder_lite::{BigEndian, WriteBytesExt}; +use alloc::borrow::ToOwned; +use alloc::format; +use alloc::vec::Vec; +use core::fmt; + +use byteorder_lite::BigEndian; + +#[cfg(feature = "std")] +use { + super::AutoBreak, + byteorder_lite::WriteBytesExt, + std::io::{self, Write}, +}; enum HeaderStrategy { Dynamic, @@ -22,6 +30,7 @@ enum HeaderStrategy { Chosen(PnmHeader), } +#[cfg_attr(not(feature = "std"), expect(unreachable_pub))] #[derive(Clone, Copy)] pub enum FlatSamples<'a> { U8(&'a [u8]), @@ -29,7 +38,7 @@ pub enum FlatSamples<'a> { } /// Encodes images to any of the `pnm` image formats. -pub struct PnmEncoder { +pub struct PnmEncoder { writer: W, header: HeaderStrategy, } @@ -78,7 +87,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 @@ -134,7 +143,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 @@ -282,6 +294,7 @@ impl PnmEncoder { } } +#[cfg(feature = "std")] impl ImageEncoder for PnmEncoder { #[track_caller] fn write_image( @@ -509,7 +522,9 @@ impl<'a> CheckedHeaderColor<'a> { } } +#[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)?; Ok(self.encoding) @@ -520,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 @@ -634,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 40e82dc165..1b380b7ae7 100644 --- a/src/codecs/pnm/header.rs +++ b/src/codecs/pnm/header.rs @@ -1,4 +1,11 @@ -use std::{fmt, io}; +#![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 #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -256,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 { diff --git a/src/codecs/pnm/mod.rs b/src/codecs/pnm/mod.rs index 1e7f324e93..eeec35a68e 100644 --- a/src/codecs/pnm/mod.rs +++ b/src/codecs/pnm/mod.rs @@ -3,7 +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. -use self::autobreak::AutoBreak; pub use self::decoder::PnmDecoder; pub use self::encoder::PnmEncoder; use self::header::HeaderRecord; @@ -12,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/qoi.rs b/src/codecs/qoi.rs index f42d21a470..dcff94adea 100644 --- a/src/codecs/qoi.rs +++ b/src/codecs/qoi.rs @@ -1,41 +1,104 @@ //! Decoding and encoding of QOI images +use alloc::boxed::Box; +use alloc::format; +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 { + inner: R, +} + +/// Intermediate Qoi decoder. +pub struct ReaderDecoder { + #[cfg_attr(not(feature = "std"), expect(dead_code))] decoder: qoi::Decoder, } -impl QoiDecoder +/// 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 = SliceDecoder { bytes, header }; + + Ok(Self { inner }) + } +} + +impl> ImageDecoder for QoiDecoder> { + fn dimensions(&self) -> (u32, u32) { + (self.inner.header.width, self.inner.header.height) + } + + fn color_type(&self) -> ColorType { + 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.bytes).map_err(decoding_error)?; + Ok(()) + } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } +} + +#[cfg(feature = "std")] +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 = ReaderDecoder { decoder }; + + Ok(Self { inner }) } } -impl ImageDecoder for QoiDecoder { +#[cfg(feature = "std")] +impl ImageDecoder for QoiDecoder> { fn dimensions(&self) -> (u32, u32) { - (self.decoder.header().width, self.decoder.header().height) + ( + self.inner.decoder.header().width, + self.inner.decoder.header().height, + ) } fn color_type(&self) -> ColorType { - match self.decoder.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.decoder.decode_to_buf(buf).map_err(decoding_error)?; + self.inner + .decoder + .decode_to_buf(buf) + .map_err(decoding_error)?; Ok(()) } @@ -44,12 +107,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 @@ -57,13 +138,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( @@ -73,26 +155,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[..])?; @@ -102,6 +165,58 @@ 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], + 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/codecs/tga/decoder.rs b/src/codecs/tga/decoder.rs index 3ad28e72a4..73a4e6dce1 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}, @@ -6,8 +8,15 @@ use crate::{ }, image::{ImageDecoder, ImageFormat}, }; -use byteorder_lite::ReadBytesExt; -use std::io::{self, Read}; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +#[cfg(feature = "std")] +use { + byteorder_lite::ReadBytesExt, + std::io::{self, Read}, +}; struct ColorMap { /// sizes in bytes @@ -17,13 +26,14 @@ struct ColorMap { } impl ColorMap { + #[cfg(feature = "std")] pub(crate) fn from_reader( r: &mut dyn Read, start_offset: u16, 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)?; @@ -59,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 + 7) / 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 @@ -166,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; @@ -201,81 +146,25 @@ 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 { - 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 @@ -336,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) @@ -382,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 2b3383fc74..25ab7adfde 100644 --- a/src/codecs/tga/encoder.rs +++ b/src/codecs/tga/encoder.rs @@ -1,9 +1,15 @@ +#![cfg_attr(not(feature = "std"), expect(dead_code, unused_imports))] + use super::header::Header; 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}; + +#[cfg(feature = "std")] +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)] @@ -33,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 @@ -48,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 { @@ -62,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) @@ -233,6 +242,7 @@ impl TgaEncoder { } } +#[cfg(feature = "std")] impl ImageEncoder for TgaEncoder { #[track_caller] fn write_image( @@ -250,7 +260,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/tga/header.rs b/src/codecs/tga/header.rs index 4dff32e777..4028e229fd 100644 --- a/src/codecs/tga/header.rs +++ b/src/codecs/tga/header.rs @@ -1,7 +1,14 @@ +#![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 std::io::{Read, Write}; +use byteorder_lite::LittleEndian; + +#[cfg(feature = "std")] +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; @@ -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)?; diff --git a/src/codecs/tiff.rs b/src/codecs/tiff.rs index 9a46a79456..4fbcbefc84 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::{ @@ -168,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/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..12fc322c68 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))] @@ -231,9 +234,11 @@ impl ExtendedColorType { } /// Returns the number of bytes required to hold a width x height image of this color type. + #[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 + 7) / 8; + let row_pitch = (width as u64 * bpp).div_ceil(8); row_pitch.saturating_mul(height as u64) } } @@ -443,7 +448,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() } @@ -501,7 +506,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); } @@ -510,7 +515,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; } @@ -518,11 +523,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 b55851b745..3457c8704f 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1,5 +1,5 @@ -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; @@ -13,16 +13,26 @@ 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_reader::free_functions; +use crate::image::{GenericImage, GenericImageView, ImageDecoder, ImageEncoder}; +use crate::imageops; use crate::math::resize_dimensions; use crate::metadata::Orientation; use crate::traits::Pixel; -use crate::ImageReader; use crate::{image, Luma, LumaA}; -use crate::{imageops, ExtendedColorType}; 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_ @@ -664,6 +674,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 +865,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 +879,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 +897,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 +938,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 +1001,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 +1043,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(); @@ -1035,7 +1051,6 @@ impl DynamicImage { // TODO do not repeat this match statement across the crate - #[allow(deprecated)] match format { #[cfg(feature = "png")] ImageFormat::Png => { @@ -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, @@ -1155,7 +1172,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? @@ -1168,7 +1184,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 { @@ -1290,6 +1306,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 +1319,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 +1334,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 +1354,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 +1381,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 +1400,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 +1414,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); @@ -1517,7 +1540,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 5201c7b8c8..4790226bca 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), } @@ -157,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, @@ -183,6 +189,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 +304,7 @@ impl LimitError { } } +#[cfg(feature = "std")] impl From for ImageError { fn from(err: io::Error) -> ImageError { ImageError::IoError(err) @@ -309,6 +317,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 +342,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 +356,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 +373,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 +502,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,9 +512,9 @@ impl fmt::Display for ImageFormatHint { #[cfg(test)] mod tests { use super::*; - use std::mem::size_of; + 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 4ba1b6eab0..8c02770cfd 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. @@ -1464,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); } @@ -1622,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 fef9a754bf..28e32fc01e 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,10 +1,10 @@ -#![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 +15,8 @@ use crate::metadata::Orientation; use crate::traits::Pixel; use crate::ImageBuffer; -use crate::animation::Frames; +#[cfg(feature = "std")] +use {std::ffi::OsStr, std::path::Path}; /// An enumeration of supported image formats. /// Not all formats support both encoding and decoding. @@ -83,14 +84,33 @@ 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 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, @@ -128,6 +148,7 @@ impl ImageFormat { /// /// # Ok::<(), image::error::ImageError>(()) /// ``` + #[cfg(feature = "std")] #[inline] pub fn from_path

(path: P) -> ImageResult where @@ -399,7 +420,6 @@ pub(crate) struct ImageReadBuffer { scanline_bytes: usize, buffer: Vec, consumed: usize, - total_bytes: u64, offset: u64, } @@ -409,7 +429,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 { @@ -421,11 +441,11 @@ impl ImageReadBuffer { } } - #[allow(dead_code)] + #[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 { @@ -469,8 +489,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 -pub(crate) fn load_rect( +#[expect(clippy::too_many_arguments)] +pub(crate) fn load_rect( x: u32, y: u32, width: u32, @@ -484,9 +504,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(); @@ -528,9 +548,12 @@ 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]; + 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 { @@ -559,9 +582,11 @@ 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]; + 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; @@ -1280,7 +1305,6 @@ where } } -#[allow(deprecated)] impl GenericImageView for SubImageInner where I: Deref, @@ -1297,13 +1321,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) } @@ -1314,6 +1338,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); } @@ -1334,7 +1359,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 57fbb72466..b49d2c9bc7 100644 --- a/src/image_reader/free_functions.rs +++ b/src/image_reader/free_functions.rs @@ -1,31 +1,40 @@ -use std::fs::File; -use std::io::{BufRead, BufWriter, Seek}; -use std::iter; -use std::path::Path; +use core::iter; -use crate::{codecs::*, ExtendedColorType, ImageReader}; - -use crate::dynimage::DynamicImage; 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}; +#[cfg_attr(not(feature = "std"), expect(unused_imports))] +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, + std::io::{BufRead, BufWriter, Seek}, + std::path::Path, +}; + /// Create a new image from a Reader. /// /// Assumes the reader is already buffered. For optimal performance, /// consider wrapping the reader with a `BufReader::new()`. /// /// Try [`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); reader.decode() } -#[allow(unused_variables)] // Most variables when no features are supported +#[cfg(feature = "std")] pub(crate) fn save_buffer_impl( path: &Path, buf: &[u8], @@ -37,8 +46,8 @@ pub(crate) fn save_buffer_impl( save_buffer_with_format_impl(path, buf, width, height, color, format) } -#[allow(unused_variables)] // Most variables when no features are supported +#[cfg(feature = "std")] pub(crate) fn save_buffer_with_format_impl( path: &Path, buf: &[u8], @@ -51,7 +60,7 @@ pub(crate) fn save_buffer_with_format_impl( write_buffer_impl(buffered_file_write, buf, width, height, color, format) } -#[allow(unused_variables)] +#[cfg(feature = "std")] // Most variables when no features are supported pub(crate) fn write_buffer_impl( buffered_write: &mut W, diff --git a/src/image_reader/image_reader_type.rs b/src/image_reader/image_reader_type.rs index 364f389588..dbcd6e59a3 100644 --- a/src/image_reader/image_reader_type.rs +++ b/src/image_reader/image_reader_type.rs @@ -1,13 +1,18 @@ -use std::fs::File; -use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; -use std::path::Path; - -use crate::dynimage::DynamicImage; use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::image::ImageFormat; -use crate::{ImageDecoder, ImageError, ImageResult}; +use crate::{ImageError, ImageResult}; + +#[cfg_attr(not(feature = "std"), expect(unused_imports))] +use { + super::free_functions, crate::dynimage::DynamicImage, crate::ImageDecoder, alloc::boxed::Box, +}; -use super::free_functions; +#[cfg(feature = "std")] +use { + std::fs::File, + std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}, + std::path::Path, +}; /// A multi-format image reader. /// @@ -58,7 +63,7 @@ use super::free_functions; /// /// [`set_format`]: #method.set_format /// [`ImageDecoder`]: ../trait.ImageDecoder.html -pub struct ImageReader { +pub struct ImageReader { /// The reader. Should be buffered. inner: R, /// The format, if one has been set or deduced. @@ -67,7 +72,7 @@ pub struct ImageReader { limits: super::Limits, } -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, @@ -131,6 +136,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 @@ -139,12 +157,10 @@ 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> { - #[allow(unused)] use crate::codecs::*; - #[allow(unreachable_patterns)] // Default is unreachable if all features are supported. Ok(match format { #[cfg(feature = "avif-native")] @@ -272,17 +288,9 @@ 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")] 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..b37f0bfeba 100644 --- a/src/image_reader/mod.rs +++ b/src/image_reader/mod.rs @@ -9,7 +9,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 {} @@ -35,7 +34,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 6e4f14581f..a3e3daefcd 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 { @@ -396,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 7289c09ac8..71160bb375 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, @@ -248,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(), @@ -266,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(), @@ -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, @@ -315,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) = ( @@ -334,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(), @@ -633,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/fast_blur.rs b/src/imageops/fast_blur.rs index dd40ba4f95..f4633b4747 100644 --- a/src/imageops/fast_blur.rs +++ b/src/imageops/fast_blur.rs @@ -1,7 +1,14 @@ +use alloc::{vec, vec::Vec}; 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 diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index 72e70e5fc8..4c9446a4b3 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,24 +16,30 @@ 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, }; /// 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, }; 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; -pub use fast_blur::fast_blur; +#[cfg(any(feature = "std", feature = "libm"))] +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. diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 05a4a43640..ad6f928b90 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) } @@ -290,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; @@ -299,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(), @@ -530,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(), @@ -545,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); @@ -574,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); @@ -668,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); } @@ -808,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; @@ -878,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, @@ -899,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) = ( @@ -917,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(), @@ -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..be8ad5ea96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,15 @@ // 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)] +#![cfg_attr(not(test), no_std)] + +#[cfg(feature = "std")] +extern crate std; + +extern crate alloc; #[cfg(all(test, feature = "benchmarks"))] extern crate test; @@ -170,12 +179,17 @@ 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; +pub use crate::image_reader::ImageReader; pub use crate::dynimage::DynamicImage; 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..9e790b639c 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,8 +1,8 @@ //! Types describing image metadata -use std::io::{Cursor, Read}; +use core::mem::offset_of; -use byteorder_lite::{BigEndian, LittleEndian, ReadBytesExt}; +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). @@ -64,44 +64,77 @@ impl Orientation { } 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, + }) } } 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)