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
37 changes: 32 additions & 5 deletions benches/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,46 @@ pub fn bench_cast_intra_colorspace(c: &mut Criterion) {
}

pub fn bench_cast_with_coefficient(c: &mut Criterion) {
let source =
DynamicImage::ImageRgba8(ImageBuffer::from_pixel(256, 256, Rgba([0u8, 0, 0, 255])));
let rgba8 = DynamicImage::new(256, 256, image::ColorType::Rgb8);

c.bench_function("cast_dynamic_rgba8_l8", |b| {
b.iter(|| black_box(&source).to_luma8());
b.iter(|| black_box(&rgba8).to_luma8());
});

c.bench_function("cast_dynamic_rgba8_l16", |b| {
b.iter(|| black_box(&source).to_luma16());
b.iter(|| black_box(&rgba8).to_luma16());
});

c.bench_function("cast_dynamic_rgba8_la16", |b| {
b.iter(|| black_box(&source).to_luma_alpha16());
b.iter(|| black_box(&rgba8).to_luma_alpha16());
});

let rgba16 = DynamicImage::new(256, 256, image::ColorType::Rgb16);

c.bench_function("cast_dynamic_rgba16_l8", |b| {
b.iter(|| black_box(&rgba16).to_luma8());
});

c.bench_function("cast_dynamic_rgba16_l16", |b| {
b.iter(|| black_box(&rgba16).to_luma16());
});

c.bench_function("cast_dynamic_rgba16_la16", |b| {
b.iter(|| black_box(&rgba16).to_luma_alpha16());
});

let rgba32f = DynamicImage::new(256, 256, image::ColorType::Rgba32F);

c.bench_function("cast_dynamic_rgba32f_l8", |b| {
b.iter(|| black_box(&rgba32f).to_luma8());
});

c.bench_function("cast_dynamic_rgba32f_l16", |b| {
b.iter(|| black_box(&rgba32f).to_luma16());
});

c.bench_function("cast_dynamic_rgba32f_la16", |b| {
b.iter(|| black_box(&rgba32f).to_luma_alpha16());
});
}

Expand Down
54 changes: 19 additions & 35 deletions src/color.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::ops::{Index, IndexMut};

use num_traits::{NumCast, Zero};
use num_traits::Zero;

use crate::{
error::TryFromExtendedColorError,
traits::{Enlargeable, Pixel, Primitive},
primitive_sealed::RgbToLuma,
traits::{Pixel, Primitive},
};

/// An enumeration over supported color types and bit depths
Expand Down Expand Up @@ -515,11 +516,11 @@ define_colors! {
///
/// For the purpose of color conversion, as well as blending, the implementation of `Pixel`
/// assumes an `sRGB` color space of its data.
pub struct Rgb<T: Primitive Enlargeable>([T; 3, 0]) = "RGB";
pub struct Rgb<T: Primitive>([T; 3, 0]) = "RGB";
/// Grayscale colors.
pub struct Luma<T: Primitive>([T; 1, 0]) = "Y";
/// RGB colors + alpha channel
pub struct Rgba<T: Primitive Enlargeable>([T; 4, 1]) = "RGBA";
pub struct Rgba<T: Primitive>([T; 4, 1]) = "RGBA";
/// Grayscale colors + alpha channel
pub struct LumaA<T: Primitive>([T; 2, 1]) = "YA";
}
Expand Down Expand Up @@ -637,18 +638,6 @@ where
}
}

/// Coefficients to transform from sRGB to a CIE Y (luminance) value.
const SRGB_LUMA: [u32; 3] = [2126, 7152, 722];
const SRGB_LUMA_DIV: u32 = 10000;

#[inline]
fn rgb_to_luma<T: Primitive + Enlargeable>(rgb: &[T]) -> T {
let l = <T::Larger as NumCast>::from(SRGB_LUMA[0]).unwrap() * rgb[0].to_larger()
+ <T::Larger as NumCast>::from(SRGB_LUMA[1]).unwrap() * rgb[1].to_larger()
+ <T::Larger as NumCast>::from(SRGB_LUMA[2]).unwrap() * rgb[2].to_larger();
T::clamp_from(l / <T::Larger as NumCast>::from(SRGB_LUMA_DIV).unwrap())
}

// `FromColor` for Luma
impl<S: Primitive, T: Primitive> FromColor<Luma<S>> for Luma<T>
where
Expand All @@ -670,26 +659,23 @@ where
}
}

impl<S: Primitive + Enlargeable, T: Primitive> FromColor<Rgb<S>> for Luma<T>
impl<S: Primitive, T: Primitive> FromColor<Rgb<S>> for Luma<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Rgb<S>) {
let gray = self.channels_mut();
let rgb = other.channels();
gray[0] = T::from_primitive(rgb_to_luma(rgb));
let [r, g, b] = other.0;
self.0[0] = T::from_primitive(RgbToLuma::rgb_to_luma(r, g, b));
}
}

impl<S: Primitive + Enlargeable, T: Primitive> FromColor<Rgba<S>> for Luma<T>
impl<S: Primitive, T: Primitive> FromColor<Rgba<S>> for Luma<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Rgba<S>) {
let gray = self.channels_mut();
let rgb = other.channels();
let l = rgb_to_luma(rgb);
gray[0] = T::from_primitive(l);
let [r, g, b, _a] = other.0;
self.0[0] = T::from_primitive(RgbToLuma::rgb_to_luma(r, g, b));
}
}

Expand All @@ -707,27 +693,25 @@ where
}
}

impl<S: Primitive + Enlargeable, T: Primitive> FromColor<Rgb<S>> for LumaA<T>
impl<S: Primitive, T: Primitive> FromColor<Rgb<S>> for LumaA<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Rgb<S>) {
let gray_a = self.channels_mut();
let rgb = other.channels();
gray_a[0] = T::from_primitive(rgb_to_luma(rgb));
gray_a[1] = T::DEFAULT_MAX_VALUE;
let [r, g, b] = other.0;
self.0[0] = T::from_primitive(RgbToLuma::rgb_to_luma(r, g, b));
self.0[1] = T::DEFAULT_MAX_VALUE;
}
}

impl<S: Primitive + Enlargeable, T: Primitive> FromColor<Rgba<S>> for LumaA<T>
impl<S: Primitive, T: Primitive> FromColor<Rgba<S>> for LumaA<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Rgba<S>) {
let gray_a = self.channels_mut();
let rgba = other.channels();
gray_a[0] = T::from_primitive(rgb_to_luma(rgba));
gray_a[1] = T::from_primitive(rgba[3]);
let [r, g, b, a] = other.0;
self.0[0] = T::from_primitive(RgbToLuma::rgb_to_luma(r, g, b));
self.0[1] = T::from_primitive(a);
}
}

Expand Down
52 changes: 51 additions & 1 deletion src/primitive_sealed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use crate::imageops::fast_blur::BlurAccumulator;
/// This trait is `pub` but not exported, so it cannot be implemented outside
/// this crate.
#[allow(private_bounds)]
pub trait PrimitiveSealed: Sized + NearestFrom<f32> + WithBlurAcc + BgraSwizzle {}
pub trait PrimitiveSealed:
Sized + NearestFrom<f32> + WithBlurAcc + BgraSwizzle + RgbToLuma
{
}

impl PrimitiveSealed for usize {}
impl PrimitiveSealed for u8 {}
Expand Down Expand Up @@ -179,3 +182,50 @@ macro_rules! impl_with_blur_acc_f32 {
}

impl_with_blur_acc_f32!(u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);

/// Converts sRGB to luma using standard coefficients for all primitives.
pub(crate) trait RgbToLuma: Copy + Sized + crate::traits::Enlargeable {
#[inline]
fn rgb_to_luma(r: Self, g: Self, b: Self) -> Self {
use num_traits::NumCast;

/// Coefficients to transform from sRGB to a CIE Y (luminance) value.
const SRGB_LUMA: [u32; 3] = [2126, 7152, 722];
const SRGB_LUMA_DIV: u32 = 10000;

let l = <Self::Larger as NumCast>::from(SRGB_LUMA[0]).unwrap() * r.to_larger()
+ <Self::Larger as NumCast>::from(SRGB_LUMA[1]).unwrap() * g.to_larger()
+ <Self::Larger as NumCast>::from(SRGB_LUMA[2]).unwrap() * b.to_larger();
Self::clamp_from(l / <Self::Larger as NumCast>::from(SRGB_LUMA_DIV).unwrap())
}
}
impl RgbToLuma for usize {}
impl RgbToLuma for u8 {
fn rgb_to_luma(r: u8, g: u8, b: u8) -> u8 {
// The following constants give the same results as:
// ((r as u32 * 2126 + g as u32 * 7152 + b as u32 * 722 + 5000) / 10000) as u8
// Note that results are correctly rounded to the nearest integer.
const W_R: u32 = ((1_u64 << 24) * 2126).div_ceil(10000) as u32;
const W_G: u32 = ((1_u64 << 24) * 7152).div_ceil(10000) as u32;
const W_B: u32 = ((1_u64 << 24) * 722).div_ceil(10000) as u32;
((r as u32 * W_R + g as u32 * W_G + b as u32 * W_B + 0x800000) >> 24) as u8
}
}
impl RgbToLuma for u16 {}
impl RgbToLuma for u32 {}
impl RgbToLuma for u64 {}
impl RgbToLuma for isize {}
impl RgbToLuma for i8 {}
impl RgbToLuma for i16 {}
impl RgbToLuma for i32 {}
impl RgbToLuma for i64 {}
impl RgbToLuma for f32 {
#[inline]
fn rgb_to_luma(r: f32, g: f32, b: f32) -> f32 {
const SCALE_R: f32 = 2126. / 10000.;
const SCALE_G: f32 = 7152. / 10000.;
const SCALE_B: f32 = 722. / 10000.;
SCALE_R * r + SCALE_G * g + SCALE_B * b
}
}
impl RgbToLuma for f64 {}
Loading