diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 60f3d8e534..30823834a9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,14 +39,14 @@ jobs: strategy: fail-fast: false matrix: - rust: ["1.85.0", nightly, beta] + rust: ["1.88.0", nightly, beta] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - if: ${{ matrix.rust == '1.85.0' }} + if: ${{ matrix.rust == '1.88.0' }} - name: Generate Cargo.lock with minimal-version dependencies - if: ${{ matrix.rust == '1.85.0' }} + if: ${{ matrix.rust == '1.88.0' }} run: | cargo -Zminimal-versions generate-lockfile cargo update --offline num-bigint --precise 0.4.2 @@ -61,7 +61,7 @@ jobs: - name: build run: cargo build -v - name: test - if: ${{ matrix.rust != '1.85.0' }} + if: ${{ matrix.rust != '1.88.0' }} run: cargo test -v && cargo doc -v test_other_archs: diff --git a/CHANGES.md b/CHANGES.md index a9688dba2d..ef493f6adc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,13 @@ Breaking Changes: - Trait `ImageDecoderRect` has been removed (#2355, #2681) +Features: + - Added `RbgImage::from_raw_bgr` and `RbgaImage::from_raw_bgra` constructors, which convert + from `BGR(A)` with an optimized specialization for bytes of `BGRA` + +Structural changes: + - Increased MSRV to 1.88.0 (from 1.85.0) + ### Version 0.25.9 Features: diff --git a/Cargo.toml b/Cargo.toml index 821c3813d9..e7d7363cc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ resolver = "2" publish = false # note: when changed, also update test runner in `.github/workflows/rust.yml` -rust-version = "1.85.0" +rust-version = "1.88.0" license = "MIT OR Apache-2.0" description = "Imaging library. Provides basic image processing and encoders/decoders for common image formats." @@ -126,6 +126,11 @@ path = "benches/blur.rs" name = "blur" harness = false +[[bench]] +path = "benches/bgra_to_rgba.rs" +name = "bgra_to_rgba" +harness = false + # because of https://github.com/image-rs/image/pull/2583 # TODO: remove when shipping the next major release after 0.25 [package.metadata.cargo-semver-checks.lints] diff --git a/benches/bgra_to_rgba.rs b/benches/bgra_to_rgba.rs new file mode 100644 index 0000000000..96bd82b8c8 --- /dev/null +++ b/benches/bgra_to_rgba.rs @@ -0,0 +1,60 @@ +use std::time::Duration; + +use criterion::{criterion_group, criterion_main, Criterion}; +use image::{ImageBuffer, Rgb, RgbImage, Rgba, RgbaImage}; + +pub fn from_raw_bgra_old(width: u32, height: u32, container: Vec) -> Option { + let mut img = RgbaImage::from_raw(width, height, container)?; + for pix in img.pixels_mut() { + pix.0[..3].reverse(); + } + Some(img) +} +pub fn from_raw_bgra_newdefault(width: u32, height: u32, container: Vec) -> Option { + let mut img = RgbaImage::from_raw(width, height, container)?; + for pix in img.as_chunks_mut::<4>().0 { + pix.swap(0, 2); + } + Some(img) +} + +pub fn bench_bgra_to_rgba(c: &mut Criterion) { + let width = 2048; + let height = 2048; + // Create src as a Rgba image, but later treat it as a Bgra buffer + let src = ImageBuffer::from_pixel(width, height, Rgba([255u8, 0, 0, 255])).to_vec(); + let src_s = ImageBuffer::from_pixel(width, height, Rgb([255u8, 0, 0])).to_vec(); + let mut group = c.benchmark_group("bgra_to_rgba"); + group.measurement_time(Duration::from_secs(15)); + group.bench_function("from_raw_bgra (new+optimized)", |b| { + b.iter_batched( + || src.clone(), + |input| RgbaImage::from_raw_bgra(width, height, input), + criterion::BatchSize::LargeInput, + ); + }); + group.bench_function("from_raw_bgra (old)", |b| { + b.iter_batched( + || src.clone(), + |input| from_raw_bgra_old(width, height, input), + criterion::BatchSize::LargeInput, + ); + }); + group.bench_function("from_raw_bgra (new)", |b| { + b.iter_batched( + || src.clone(), + |input| from_raw_bgra_newdefault(width, height, input), + criterion::BatchSize::LargeInput, + ); + }); + group.bench_function("from_raw_bgr (new)", |b| { + b.iter_batched( + || src_s.clone(), + |input| RgbImage::from_raw_bgr(width, height, input), + criterion::BatchSize::LargeInput, + ); + }); +} + +criterion_group!(benches, bench_bgra_to_rgba); +criterion_main!(benches); diff --git a/src/images/buffer.rs b/src/images/buffer.rs index 634cf37643..9f44dac162 100644 --- a/src/images/buffer.rs +++ b/src/images/buffer.rs @@ -19,7 +19,7 @@ use crate::{ metadata::{Cicp, CicpColorPrimaries, CicpTransferCharacteristics, CicpTransform}, save_buffer, save_buffer_with_format, write_buffer_with_format, ImageError, }; -use crate::{DynamicImage, GenericImage, GenericImageView, ImageEncoder, ImageFormat}; +use crate::{DynamicImage, GenericImage, GenericImageView, ImageEncoder, ImageFormat, Primitive}; /// Iterate over pixel refs. pub struct Pixels<'a, P: Pixel + 'a> @@ -1514,25 +1514,19 @@ impl ImageBuffer> { impl ImageBuffer, Container> where Rgb: PixelWithColorType, + S: Primitive, Container: DerefMut, { /// Construct an image by swapping `Bgr` channels into an `Rgb` order. pub fn from_raw_bgr(width: u32, height: u32, container: Container) -> Option { let mut img = Self::from_raw(width, height, container)?; - - for pix in img.pixels_mut() { - pix.0.reverse(); - } - + S::swizzle_rgb_bgr(img.as_mut()); Some(img) } /// Return the underlying raw buffer after converting it into `Bgr` channel order. pub fn into_raw_bgr(mut self) -> Container { - for pix in self.pixels_mut() { - pix.0.reverse(); - } - + S::swizzle_rgb_bgr(self.as_mut()); self.into_raw() } } @@ -1540,25 +1534,19 @@ where impl ImageBuffer, Container> where Rgba: PixelWithColorType, + S: Primitive, Container: DerefMut, { /// Construct an image by swapping `BgrA` channels into an `RgbA` order. pub fn from_raw_bgra(width: u32, height: u32, container: Container) -> Option { let mut img = Self::from_raw(width, height, container)?; - - for pix in img.pixels_mut() { - pix.0[..3].reverse(); - } - + S::swizzle_rgba_bgra(img.as_mut()); Some(img) } /// Return the underlying raw buffer after converting it into `BgrA` channel order. pub fn into_raw_bgra(mut self) -> Container { - for pix in self.pixels_mut() { - pix.0[..3].reverse(); - } - + S::swizzle_rgba_bgra(self.as_mut()); self.into_raw() } } @@ -1825,7 +1813,7 @@ where let transform = options.as_transform_fn::(self.color_space(), color)?; - let mut scratch = [::DEFAULT_MIN_VALUE; 1200]; + let mut scratch = [::DEFAULT_MIN_VALUE; 1200]; let chunk_len = scratch.len() / usize::from(::CHANNEL_COUNT) * usize::from(::CHANNEL_COUNT); diff --git a/src/traits.rs b/src/traits.rs index 407df9ee9e..5bb96dd270 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -34,11 +34,36 @@ impl EncodableLayout for [f32] { } mod sealed { - pub trait PrimitiveSealed: Sized {} + pub trait PrimitiveSealed: Sized { + fn swizzle_rgb_bgr(pixels: &mut [Self]) { + for pixel in pixels.as_chunks_mut::<3>().0 { + pixel.swap(0, 2); + } + } + fn swizzle_rgba_bgra(pixels: &mut [Self]) { + for pix in pixels.as_chunks_mut::<4>().0 { + pix.swap(0, 2); + } + } + } } impl sealed::PrimitiveSealed for usize {} -impl sealed::PrimitiveSealed for u8 {} +impl sealed::PrimitiveSealed for u8 { + fn swizzle_rgb_bgr(pixels: &mut [Self]) { + for pixel in pixels.as_chunks_mut::<3>().0 { + pixel.reverse(); + } + } + fn swizzle_rgba_bgra(pixels: &mut [Self]) { + for pix in pixels.as_chunks_mut::<4>().0 { + let bgra = u32::from_be_bytes(*pix); + let argb = bgra.swap_bytes(); + let rgba = argb.rotate_left(8); + *pix = rgba.to_be_bytes(); + } + } +} impl sealed::PrimitiveSealed for u16 {} impl sealed::PrimitiveSealed for u32 {} impl sealed::PrimitiveSealed for u64 {}