Skip to content

Commit

Permalink
Merge #188
Browse files Browse the repository at this point in the history
188: Allow HSV, HSL and HWB to represent nonlinear RGB r=Ogeon a=Ogeon

This removes the old restriction that meant HSV, HSL and HWB can only represent linear RGB. They can now be converted to and from RGB (and each other) as long as the RGB standard is the same. This restriction is there to still allow type inference and reduce implementation complexity. So converting `Hsl<Srgb>` (not linear) to `Rgb<Srgb>` is still a single step process, while converting to `Rgb<Linear<Srgb>>` is a two step process. There are still shortcuts in place to only change the RGB standard, meaning it's possible to write `Hsl::<Linear<Srgb>>::from_color(Hsl::<Srgb>::new(...))`, similar to how it worked before.

This will mean that existing code may have a different output, adding more breaking changes to the pile.

Fixes #160, fixes #187 

Co-authored-by: Erik Hedvall <erikwhedvall@gmail.com>
  • Loading branch information
bors[bot] and Ogeon authored May 10, 2020
2 parents 71b1434 + ae930c2 commit 48453b6
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 408 deletions.
82 changes: 70 additions & 12 deletions palette/benches/rgb.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
use palette::convert::FromColorUnclamped;
use palette::encoding;
use palette::{Hsl, Hsv, Hwb, LinSrgb, Srgb};
use palette::{Hsl, Hsv, Hwb, IntoColor, LinSrgb, Srgb};

type SrgbHsv = Hsv<encoding::Srgb>;
type SrgbHsl = Hsl<encoding::Srgb>;
type SrgbHwb = Hwb<encoding::Srgb>;
type LinHsv = Hsv<encoding::Linear<encoding::Srgb>>;
type LinHsl = Hsl<encoding::Linear<encoding::Srgb>>;
type LinHwb = Hwb<encoding::Linear<encoding::Srgb>>;

#[path = "../tests/convert/data_color_mine.rs"]
#[allow(dead_code)]
Expand All @@ -19,6 +26,12 @@ use data_color_mine::{load_data, ColorMine};
- xyz to rgb
- hsl to rgb
- hsv to rgb
- hsv to linear hsv
- linear hsv to hsv
- hsl to linear hsl
- linear hsl to hsl
- hwb to linear hwb
- linear hwb to hwb
- linsrgb to rgb
- rgb_u8 to linsrgb_f32
- linsrgb_f32 to rgb_u8
Expand All @@ -27,22 +40,25 @@ use data_color_mine::{load_data, ColorMine};
fn rgb_conversion(c: &mut Criterion) {
let mut group = c.benchmark_group("Rgb family");
let colormine: Vec<ColorMine> = load_data();
let rgb: Vec<Srgb> = colormine.iter().map(|x| Srgb::from(x.linear_rgb)).collect();
let rgb_u8: Vec<Srgb<u8>> = rgb.iter().map(|x| x.into_format().into()).collect();
let rgb_u8: Vec<Srgb<u8>> = colormine.iter().map(|x| x.rgb.into_format()).collect();

let linear_hsv: Vec<LinHsv> = colormine.iter().map(|x| x.hsv.into_color()).collect();
let linear_hsl: Vec<LinHsl> = colormine.iter().map(|x| x.hsl.into_color()).collect();
let linear_hwb: Vec<LinHwb> = colormine.iter().map(|x| x.hwb.into_color()).collect();

group.throughput(Throughput::Elements(colormine.len() as u64));

group.bench_with_input("rgb to linsrgb", &rgb, |b, rgb| {
group.bench_with_input("rgb to linsrgb", &colormine, |b, colormine| {
b.iter(|| {
for c in rgb {
black_box(c.into_linear());
for c in colormine {
black_box(c.rgb.into_linear());
}
})
});
group.bench_with_input("rgb to hsl", &rgb, |b, rgb| {
group.bench_with_input("rgb to hsl", &colormine, |b, colormine| {
b.iter(|| {
for c in rgb {
black_box(Hsl::from_color_unclamped(*c));
for c in colormine {
black_box(Hsl::from_color_unclamped(c.rgb));
}
})
});
Expand All @@ -53,10 +69,10 @@ fn rgb_conversion(c: &mut Criterion) {
}
})
});
group.bench_with_input("rgb to hsv", &rgb, |b, rgb| {
group.bench_with_input("rgb to hsv", &colormine, |b, colormine| {
b.iter(|| {
for c in rgb {
black_box(Hsv::from_color_unclamped(*c));
for c in colormine {
black_box(Hsv::from_color_unclamped(c.rgb));
}
})
});
Expand Down Expand Up @@ -102,6 +118,48 @@ fn rgb_conversion(c: &mut Criterion) {
}
})
});
group.bench_with_input("hsv to linear hsv", &colormine, |b, colormine| {
b.iter(|| {
for c in colormine {
black_box(LinHsv::from_color_unclamped(c.hsv));
}
})
});
group.bench_with_input("linear hsv to hsv", &linear_hsv, |b, linear_hsv| {
b.iter(|| {
for &c in linear_hsv {
black_box(SrgbHsv::from_color_unclamped(c));
}
})
});
group.bench_with_input("hsl to linear hsl", &colormine, |b, colormine| {
b.iter(|| {
for c in colormine {
black_box(LinHsl::from_color_unclamped(c.hsl));
}
})
});
group.bench_with_input("linear hsl to hsl", &linear_hsl, |b, linear_hsl| {
b.iter(|| {
for &c in linear_hsl {
black_box(SrgbHsl::from_color_unclamped(c));
}
})
});
group.bench_with_input("hwb to linear hwb", &colormine, |b, colormine| {
b.iter(|| {
for c in colormine {
black_box(LinHwb::from_color_unclamped(c.hwb));
}
})
});
group.bench_with_input("linear hwb to hwb", &linear_hwb, |b, linear_hwb| {
b.iter(|| {
for &c in linear_hwb {
black_box(SrgbHwb::from_color_unclamped(c));
}
})
});
group.bench_with_input("linsrgb to rgb", &colormine, |b, colormine| {
b.iter(|| {
for c in colormine {
Expand Down
14 changes: 5 additions & 9 deletions palette/examples/hue.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use palette::{FromColor, Hsl, Hue, IntoColor, Lch, Pixel, Srgb};
use palette::{FromColor, Hsl, Hue, Lch, Pixel, Srgb};

fn main() {
let mut image = image::open("res/fruits.png")
Expand All @@ -12,15 +12,11 @@ fn main() {
let color = Srgb::from_raw(&pixel.0).into_format();

pixel.0 = if x < y {
let saturated = Hsl::from_color(color).shift_hue(180.0);
Srgb::from_linear(saturated.into_color())
.into_format()
.into_raw()
let hue_shifted = Hsl::from_color(color).shift_hue(180.0);
Srgb::from_color(hue_shifted).into_format().into_raw()
} else {
let saturated = Lch::from_color(color).shift_hue(180.0);
Srgb::from_linear(saturated.into_color())
.into_format()
.into_raw()
let hue_shifted = Lch::from_color(color).shift_hue(180.0);
Srgb::from_color(hue_shifted).into_format().into_raw()
};
}

Expand Down
16 changes: 3 additions & 13 deletions palette/examples/saturate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use palette::{Hsl, IntoColor, Lch, Pixel, Saturate, Srgb};
use palette::{FromColor, Hsl, IntoColor, Lch, Pixel, Saturate, Srgb};

use image::{GenericImage, GenericImageView};

Expand All @@ -19,18 +19,13 @@ fn main() {
for y in 0..height {
let color: Hsl = Srgb::from_raw(&sub_image.get_pixel(x, y).0)
.into_format()
.into_linear()
.into_color();

let saturated = color.saturate(0.8);
sub_image.put_pixel(
x,
y,
image::Rgb(
Srgb::from_linear(saturated.into_color())
.into_format()
.into_raw(),
),
image::Rgb(Srgb::from_color(saturated).into_format().into_raw()),
);
}
}
Expand All @@ -43,18 +38,13 @@ fn main() {
for y in 0..height {
let color: Lch = Srgb::from_raw(&sub_image.get_pixel(x, y).0)
.into_format()
.into_linear()
.into_color();

let saturated = color.saturate(0.8);
sub_image.put_pixel(
x,
y,
image::Rgb(
Srgb::from_linear(saturated.into_color())
.into_format()
.into_raw(),
),
image::Rgb(Srgb::from_color(saturated).into_format().into_raw()),
);
}
}
Expand Down
63 changes: 35 additions & 28 deletions palette/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,39 @@
//! They can be attached to either the item itself, or to the fields.
//!
//! ```
//! # use palette::rgb::RgbSpace;
//! # use palette::rgb::{RgbStandard, RgbSpace};
//! # use palette::convert::FromColorUnclamped;
//! # use palette::{Xyz, Component, FloatComponent};
//! #
//! #[palette(
//! component = "T",
//! rgb_space = "S",
//! white_point = "S::WhitePoint",
//! rgb_standard = "S",
//! white_point = "<S::Space as RgbSpace>::WhitePoint",
//! )]
//! #[derive(FromColorUnclamped)]
//! #[repr(C)]
//! struct ExampleType<S: RgbSpace, T: Component> {
//! struct ExampleType<S: RgbStandard, T: Component> {
//! // ...
//! #[palette(alpha)]
//! alpha: T,
//! space: std::marker::PhantomData<S>,
//! standard: std::marker::PhantomData<S>,
//! }
//!
//! # impl<S: RgbSpace, T: FloatComponent> FromColorUnclamped<Xyz<S::WhitePoint, T>> for ExampleType<S, T> {
//! # fn from_color_unclamped(color: Xyz<S::WhitePoint, T>) -> Self {
//! # ExampleType {alpha: T::max_intensity(), space: std::marker::PhantomData}
//! # impl<S, T> FromColorUnclamped<Xyz<<S::Space as RgbSpace>::WhitePoint, T>> for ExampleType<S, T>
//! # where
//! # S: RgbStandard,
//! # T: FloatComponent
//! # {
//! # fn from_color_unclamped(color: Xyz<<S::Space as RgbSpace>::WhitePoint, T>) -> Self {
//! # ExampleType {alpha: T::max_intensity(), standard: std::marker::PhantomData}
//! # }
//! # }
//! #
//! # impl<S: RgbSpace, T: FloatComponent> FromColorUnclamped<ExampleType<S, T>> for Xyz<S::WhitePoint, T> {
//! # impl<S, T> FromColorUnclamped<ExampleType<S, T>> for Xyz<<S::Space as RgbSpace>::WhitePoint, T>
//! # where
//! # S: RgbStandard,
//! # T: FloatComponent
//! # {
//! # fn from_color_unclamped(color: ExampleType<S, T>) -> Self {
//! # Xyz::default()
//! # }
Expand All @@ -60,9 +68,9 @@
//! component type that should be used when deriving. The default is `f32`, but
//! it may be any other type, including type parameters.
//!
//! * `rgb_space = "some::rgb_space::Type"`: Sets the RGB space
//! * `rgb_standard = "some::rgb_standard::Type"`: Sets the RGB standard
//! type that should be used when deriving. The default is to either use `Srgb`
//! or a best effort to convert between spaces, so sometimes it has to be set
//! or a best effort to convert between standards, but sometimes it has to be set
//! to a specific type. This also accepts type parameters.
//!
//! ## Field Attributes
Expand Down Expand Up @@ -138,7 +146,7 @@
//! #[palette(
//! skip_derives(Rgb),
//! component = "T",
//! rgb_space = "palette::encoding::Srgb"
//! rgb_standard = "palette::encoding::Srgb"
//! )]
//! #[derive(Copy, Clone, Pixel, FromColorUnclamped)]
//! #[repr(C)] // Makes sure the memory layout is as we want it.
Expand Down Expand Up @@ -185,8 +193,8 @@
//! 0.0,
//! 0.0,
//! 0.0,
//! 0.7353569830524495,
//! 0.5370987304831942,
//! 0.5,
//! 0.25,
//! ];
//! let hsv: Hsv<_, f64> = Bgr::from_raw_slice(&buffer)[1].into_color();
//!
Expand All @@ -209,7 +217,7 @@
//! /// CSS style sRGB.
//! #[palette(
//! skip_derives(Rgb),
//! rgb_space = "palette::encoding::Srgb"
//! rgb_standard = "palette::encoding::Srgb"
//! )]
//! #[derive(PartialEq, Debug, FromColorUnclamped, WithAlpha)]
//! struct CssRgb {
Expand Down Expand Up @@ -528,7 +536,7 @@ mod tests {
skip_derives(Xyz, Luma),
white_point = "S::WhitePoint",
component = "f64",
rgb_space = "S",
rgb_standard = "Linear<S>",
palette_internal,
palette_internal_not_base_type
)]
Expand Down Expand Up @@ -597,7 +605,7 @@ mod tests {
skip_derives(Lch, Luma),
white_point = "crate::white_point::E",
component = "T",
rgb_space = "(crate::encoding::Srgb, crate::white_point::E)",
rgb_standard = "Linear<(crate::encoding::Srgb, crate::white_point::E)>",
palette_internal,
palette_internal_not_base_type
)]
Expand Down Expand Up @@ -667,7 +675,7 @@ mod tests {
let lch: Lch<_, f64> = Default::default();
WithXyz::<crate::encoding::Srgb>::from_color(lch);

let rgb: Rgb<crate::encoding::Srgb, f64> = Default::default();
let rgb: Rgb<_, f64> = Default::default();
WithXyz::<crate::encoding::Srgb>::from_color(rgb);

let hsl: Hsl<_, f64> = Default::default();
Expand Down Expand Up @@ -701,8 +709,7 @@ mod tests {
let lch: Alpha<Lch<_, f64>, u8> = Alpha::from(Lch::default());
WithXyz::<crate::encoding::Srgb>::from_color(lch);

let rgb: Alpha<Rgb<crate::encoding::Srgb, f64>, u8> =
Alpha::from(Rgb::<crate::encoding::Srgb, f64>::default());
let rgb: Alpha<Rgb<_, f64>, u8> = Alpha::from(Rgb::default());
WithXyz::<crate::encoding::Srgb>::from_color(rgb);

let hsl: Alpha<Hsl<_, f64>, u8> = Alpha::from(Hsl::default());
Expand Down Expand Up @@ -736,7 +743,7 @@ mod tests {
let lch: Lch<_, f64> = Default::default();
Alpha::<WithXyz<crate::encoding::Srgb>, u8>::from_color(lch);

let rgb: Rgb<crate::encoding::Srgb, f64> = Default::default();
let rgb: Rgb<_, f64> = Default::default();
Alpha::<WithXyz<crate::encoding::Srgb>, u8>::from_color(rgb);

let hsl: Hsl<_, f64> = Default::default();
Expand Down Expand Up @@ -770,7 +777,7 @@ mod tests {
let lch: Lch<_, f64> = Default::default();
Alpha::<WithXyz<crate::encoding::Srgb>, u8>::from_color(lch);

let rgb: Rgb<crate::encoding::Srgb, f64> = Default::default();
let rgb: Rgb<_, f64> = Default::default();
Alpha::<WithXyz<crate::encoding::Srgb>, u8>::from_color(rgb);

let hsl: Hsl<_, f64> = Default::default();
Expand All @@ -795,7 +802,7 @@ mod tests {
let _yxy: Yxy<_, f64> = color.into_color();
let _lab: Lab<_, f64> = color.into_color();
let _lch: Lch<_, f64> = color.into_color();
let _rgb: Rgb<crate::encoding::Srgb, f64> = color.into_color();
let _rgb: Rgb<_, f64> = color.into_color();
let _hsl: Hsl<_, f64> = color.into_color();
let _hsv: Hsv<_, f64> = color.into_color();
let _hwb: Hwb<_, f64> = color.into_color();
Expand All @@ -812,7 +819,7 @@ mod tests {
let _yxy: Yxy<_, f64> = color.into_color();
let _lab: Lab<_, f64> = color.into_color();
let _lch: Lch<_, f64> = color.into_color();
let _rgb: Rgb<crate::encoding::Srgb, f64> = color.into_color();
let _rgb: Rgb<_, f64> = color.into_color();
let _hsl: Hsl<_, f64> = color.into_color();
let _hsv: Hsv<_, f64> = color.into_color();
let _hwb: Hwb<_, f64> = color.into_color();
Expand All @@ -828,7 +835,7 @@ mod tests {
let _yxy: Alpha<Yxy<_, f64>, u8> = color.into_color();
let _lab: Alpha<Lab<_, f64>, u8> = color.into_color();
let _lch: Alpha<Lch<_, f64>, u8> = color.into_color();
let _rgb: Alpha<Rgb<crate::encoding::Srgb, f64>, u8> = color.into_color();
let _rgb: Alpha<Rgb<_, f64>, u8> = color.into_color();
let _hsl: Alpha<Hsl<_, f64>, u8> = color.into_color();
let _hsv: Alpha<Hsv<_, f64>, u8> = color.into_color();
let _hwb: Alpha<Hwb<_, f64>, u8> = color.into_color();
Expand All @@ -845,7 +852,7 @@ mod tests {
let _yxy: Alpha<Yxy<_, f64>, u8> = color.into_color();
let _lab: Alpha<Lab<_, f64>, u8> = color.into_color();
let _lch: Alpha<Lch<_, f64>, u8> = color.into_color();
let _rgb: Alpha<Rgb<crate::encoding::Srgb, f64>, u8> = color.into_color();
let _rgb: Alpha<Rgb<_, f64>, u8> = color.into_color();
let _hsl: Alpha<Hsl<_, f64>, u8> = color.into_color();
let _hsv: Alpha<Hsv<_, f64>, u8> = color.into_color();
let _hwb: Alpha<Hwb<_, f64>, u8> = color.into_color();
Expand All @@ -869,7 +876,7 @@ mod tests {
let lch: Lch<crate::white_point::E, f64> = Default::default();
WithoutXyz::<f64>::from_color(lch);

let rgb: Rgb<(_, crate::encoding::Srgb), f64> = Default::default();
let rgb: Rgb<_, f64> = Default::default();
WithoutXyz::<f64>::from_color(rgb);

let hsl: Hsl<_, f64> = Default::default();
Expand All @@ -894,7 +901,7 @@ mod tests {
let _yxy: Yxy<crate::white_point::E, f64> = color.into_color();
let _lab: Lab<crate::white_point::E, f64> = color.into_color();
let _lch: Lch<crate::white_point::E, f64> = color.into_color();
let _rgb: Rgb<(_, crate::encoding::Srgb), f64> = color.into_color();
let _rgb: Rgb<_, f64> = color.into_color();
let _hsl: Hsl<_, f64> = color.into_color();
let _hsv: Hsv<_, f64> = color.into_color();
let _hwb: Hwb<_, f64> = color.into_color();
Expand Down
Loading

0 comments on commit 48453b6

Please sign in to comment.