Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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]
Expand Down
60 changes: 60 additions & 0 deletions benches/bgra_to_rgba.rs
Original file line number Diff line number Diff line change
@@ -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<u8>) -> Option<RgbaImage> {
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<u8>) -> Option<RgbaImage> {
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);
28 changes: 8 additions & 20 deletions src/images/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -1514,51 +1514,39 @@ impl<P: Pixel> ImageBuffer<P, Vec<P::Subpixel>> {
impl<S, Container> ImageBuffer<Rgb<S>, Container>
where
Rgb<S>: PixelWithColorType<Subpixel = S>,
S: Primitive,
Container: DerefMut<Target = [S]>,
{
/// Construct an image by swapping `Bgr` channels into an `Rgb` order.
pub fn from_raw_bgr(width: u32, height: u32, container: Container) -> Option<Self> {
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()
}
}

impl<S, Container> ImageBuffer<Rgba<S>, Container>
where
Rgba<S>: PixelWithColorType<Subpixel = S>,
S: Primitive,
Container: DerefMut<Target = [S]>,
{
/// Construct an image by swapping `BgrA` channels into an `RgbA` order.
pub fn from_raw_bgra(width: u32, height: u32, container: Container) -> Option<Self> {
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()
}
}
Expand Down Expand Up @@ -1825,7 +1813,7 @@ where
let transform =
options.as_transform_fn::<SelfPixel, SelfPixel>(self.color_space(), color)?;

let mut scratch = [<SelfPixel::Subpixel as crate::Primitive>::DEFAULT_MIN_VALUE; 1200];
let mut scratch = [<SelfPixel::Subpixel as Primitive>::DEFAULT_MIN_VALUE; 1200];
let chunk_len = scratch.len() / usize::from(<SelfPixel as Pixel>::CHANNEL_COUNT)
* usize::from(<SelfPixel as Pixel>::CHANNEL_COUNT);

Expand Down
29 changes: 27 additions & 2 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
Loading