Skip to content

Commit 404ac2e

Browse files
committed
Remove default conversion between XYZ and CAM16
1 parent ceb19cd commit 404ac2e

16 files changed

+1158
-865
lines changed

CONTRIBUTING.md

+3-6
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,9 @@ The central trait for color conversion is `FromColorUnclamped`. This should be d
232232

233233
#### In The `palette_derive` Crate
234234

235-
Add the type's name to `COLOR_TYPES` in `lib.rs`. This includes it in the list of possible derived conversions.
235+
Add the type's name to its color group (typically `BASE_COLORS`) in `color_types.rs`. This includes it in the list of possible derived conversions. The `preferred_source` tells the library how to find a path to the color type. Each color space has a "parent" space that all connects to `Xyz`. For example, `Hwb` connects to `Hsv`, which connects to `Rgb`, which connects to `Xyz`. The derived conversion code will backtrack as far towards `Xyz` as it needs, until it finds a way to convert to your type.
236236

237-
Add the type's name and another type's name to `PREFERRED_CONVERSION_SOURCE` in `lib.rs`. This tells the library how to find a path to the color type. Each color space has a "parent" space that all connects to `Xyz`. For example, `Hwb` connects to `Hsv`, which connects to `Rgb`, which connects to `Xyz`. The derived conversion code will backtrack as far towards `Xyz` as it needs, until it finds a way to convert to your type.
238-
239-
Add the type's name and a Cargo feature to `REQUIRED_COLOR_FEATURES` in `lib.rs` if it's optional. Optional color spaces should have a `#[cfg(feature = "feature_name")]` attribute on each of the above additions.
240-
241-
Other special casing may be needed in other parts of the code, depending on the type.
237+
Other special casing may be needed in other parts of the code, depending on the type. This part can be confusing, so feel free to ask!
242238

243239
#### In The `palette` Crate
244240

@@ -248,6 +244,7 @@ In addition to that, add the following in the attribute:
248244

249245
* `component = "T"` to point out the component type.
250246
* `white_point = "Wp"` and other meta info to point out the white point type, if necessary. See the list in the documentation or follow the error hints.
247+
* `color_group = "group"` if it's not part of the bas group, such as `"cam16"` if it's a CIE CAM16 derivative.
251248
* `skip_derives(Xyz, Hsv, Hsl)` with all color types you want to convert from with manual implementations.
252249

253250
Add manual conversions for at least one color type and list it in `skip_derives`. `Xyz` is assumed if `skip_derives` is omitted. These are the minimum requirements:

palette/src/cam16.rs

-74
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
//! Types for the CIE CAM16 color appearance model.
22
3-
use crate::{
4-
angle::{RealAngle, SignedAngle},
5-
bool_mask::LazySelect,
6-
num::{Abs, Arithmetics, One, PartialCmp, Powf, Real, Signum, Sqrt, Trigonometry, Zero},
7-
white_point::{self},
8-
Xyz,
9-
};
10-
113
pub use full::*;
124
pub use parameters::*;
135
pub use partial::*;
@@ -25,69 +17,3 @@ mod parameters;
2517
mod partial;
2618
mod ucs_jab;
2719
mod ucs_jmh;
28-
29-
/// Converts a color to CAM16, using a set of parameters.
30-
pub trait IntoCam16<WpParam, T> {
31-
/// Convert `self` into CAM16, with `parameters` that describe the viewing
32-
/// conditions.
33-
fn into_cam16(self, parameters: BakedParameters<WpParam, T>) -> Cam16<T>;
34-
}
35-
36-
impl<WpParam, T> IntoCam16<WpParam, T> for Xyz<WpParam::StaticWp, T>
37-
where
38-
WpParam: WhitePointParameter<T>,
39-
T: Real + Arithmetics + Powf + Sqrt + Abs + Signum + Trigonometry + RealAngle + Clone,
40-
{
41-
fn into_cam16(self, parameters: BakedParameters<WpParam, T>) -> Cam16<T> {
42-
math::xyz_to_cam16(self.with_white_point(), parameters.inner)
43-
}
44-
}
45-
46-
/// Converts CAM16 to a color, using a set of parameters.
47-
pub trait FromCam16<WpParam, T>: Sized {
48-
/// Convert `cam16` into `Self`, with `parameters` that describe the viewing
49-
/// conditions.
50-
fn from_cam16(cam16: Cam16<T>, parameters: BakedParameters<WpParam, T>) -> Self {
51-
Self::from_partial_cam16(DynPartialCam16::from(cam16), parameters)
52-
}
53-
54-
/// Convert the partially specified `cam16` into `Self`, with `parameters`
55-
/// that describe the viewing conditions.
56-
fn from_partial_cam16<L, C>(
57-
cam16: PartialCam16<T, L, C>,
58-
parameters: BakedParameters<WpParam, T>,
59-
) -> Self
60-
where
61-
L: Cam16Luminance<T>,
62-
C: Cam16Chromaticity<T>;
63-
}
64-
65-
impl<WpParam, T> FromCam16<WpParam, T> for Xyz<WpParam::StaticWp, T>
66-
where
67-
WpParam: WhitePointParameter<T>,
68-
T: Real
69-
+ One
70-
+ Zero
71-
+ Sqrt
72-
+ Powf
73-
+ Abs
74-
+ Signum
75-
+ Arithmetics
76-
+ Trigonometry
77-
+ RealAngle
78-
+ SignedAngle
79-
+ PartialCmp
80-
+ Clone,
81-
T::Mask: LazySelect<Xyz<white_point::Any, T>>,
82-
{
83-
fn from_partial_cam16<L, C>(
84-
cam16: PartialCam16<T, L, C>,
85-
parameters: BakedParameters<WpParam, T>,
86-
) -> Self
87-
where
88-
L: Cam16Luminance<T>,
89-
C: Cam16Chromaticity<T>,
90-
{
91-
math::cam16_to_xyz(cam16.into_dynamic(), parameters.inner).with_white_point()
92-
}
93-
}

palette/src/cam16/full.rs

+117-43
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
use crate::{
2-
convert::FromColorUnclamped,
2+
angle::{RealAngle, SignedAngle},
3+
bool_mask::LazySelect,
34
hues::Cam16Hue,
4-
num::{Clamp, ClampAssign, Zero},
5-
white_point::WhitePoint,
6-
Xyz,
5+
num::{
6+
Abs, Arithmetics, Clamp, ClampAssign, One, PartialCmp, Powf, Real, Signum, Sqrt,
7+
Trigonometry, Zero,
8+
},
9+
white_point, Xyz,
710
};
811

9-
use super::{BakedParameters, IntoCam16, StaticWp};
12+
use super::{
13+
BakedParameters, Cam16Chromaticity, Cam16Luminance, PartialCam16, WhitePointParameter,
14+
};
1015

1116
/// The CIE CAM16 color appearance model.
1217
///
1318
/// It's a set of six technically defined attributes that describe the
1419
/// appearance of a color under certain viewing conditions, and it's a successor
1520
/// of [CIECAM02](https://en.wikipedia.org/wiki/CIECAM02). The viewing
16-
/// conditions are defined using [`Parameters`][super::Parameters] and two set
17-
/// of `Cam16` attributes are only really comparable if they were calculated
18-
/// from the same set of viewing condition parameters. The implementations of
19-
/// [`FromColor`][crate::FromColor], [`IntoColor`][crate::IntoColor], etc. use
20-
/// `Parameters::default()` as their viewing conditions. See
21-
/// [`FromCam16`][super::FromCam16] and [`IntoCam16`] for options with more
22-
/// control over the parameters.
21+
/// conditions are defined using [`Parameters`][super::Parameters], and two sets
22+
/// of parameters can be used to translate the appearance of a color from one
23+
/// set of viewing conditions to another.
24+
///
25+
/// The use of the viewing conditions parameters sets `Cam16` and its derived
26+
/// types apart from most other color types in this library. It's, for example,
27+
/// not possible to use [`FromColor`][crate::FromColor] and friends to convert
28+
/// to and from other types, since that would require default viewing conditions
29+
/// to exist. Instead, the explicit [`Cam16::from_xyz`] and [`Cam16::into_xyz`]
30+
/// are there to bridge the gap.
2331
///
24-
/// Not all attributes are needed to be known to convert _from_ CAM16, since
25-
/// they are correlated and derived from each other. This library provides a
26-
/// separate [`PartialCam16`][super::PartialCam16] to make it easier to specify
32+
/// Not all attributes are used when converting _from_ CAM16, since they are
33+
/// correlated and derived from each other. This library provides a separate
34+
/// [`PartialCam16`][super::PartialCam16] to make it easier to correctly specify
2735
/// a minimum attribute set.
28-
#[derive(Clone, Copy, Debug, WithAlpha, FromColorUnclamped)]
29-
#[palette(palette_internal, component = "T", skip_derives(Xyz, Cam16))]
36+
#[derive(Clone, Copy, Debug, WithAlpha)]
37+
#[palette(palette_internal, component = "T")]
3038
pub struct Cam16<T> {
3139
/// The [lightness](https://en.wikipedia.org/wiki/Lightness) (J) of the color.
3240
#[doc(alias = "J")]
@@ -53,6 +61,82 @@ pub struct Cam16<T> {
5361
pub saturation: T,
5462
}
5563

64+
impl<T> Cam16<T> {
65+
/// Derive CIE CAM16 attributes for the provided color, under the provided
66+
/// viewing conditions.
67+
#[inline]
68+
pub fn from_xyz<WpParam>(
69+
color: Xyz<WpParam::StaticWp, T>,
70+
parameters: impl Into<BakedParameters<WpParam, T>>,
71+
) -> Self
72+
where
73+
WpParam: WhitePointParameter<T>,
74+
T: Real + Arithmetics + Powf + Sqrt + Abs + Signum + Trigonometry + RealAngle + Clone,
75+
{
76+
super::math::xyz_to_cam16(color.with_white_point(), parameters.into().inner)
77+
}
78+
79+
/// Construct an XYZ color that matches these CIE CAM16 attributes, under
80+
/// the provided viewing conditions.
81+
///
82+
/// This assumes that all of the correlated attributes are consistent, as
83+
/// only some of them are actually used. You may want to use
84+
/// [`PartialCam16`] for more control over which set of attributes that
85+
/// should be.
86+
#[inline]
87+
pub fn into_xyz<WpParam>(
88+
self,
89+
parameters: impl Into<BakedParameters<WpParam, T>>,
90+
) -> Xyz<WpParam::StaticWp, T>
91+
where
92+
WpParam: WhitePointParameter<T>,
93+
T: Real
94+
+ One
95+
+ Zero
96+
+ Sqrt
97+
+ Powf
98+
+ Abs
99+
+ Signum
100+
+ Arithmetics
101+
+ Trigonometry
102+
+ RealAngle
103+
+ SignedAngle
104+
+ PartialCmp
105+
+ Clone,
106+
T::Mask: LazySelect<Xyz<white_point::Any, T>>,
107+
{
108+
super::math::cam16_to_xyz(self.into(), parameters.into().inner).with_white_point()
109+
}
110+
111+
/// Reconstruct a full set of CIE CAM16 attributes, using the original viewing conditions.
112+
#[inline]
113+
pub fn from_partial<WpParam, L, C>(
114+
partial: PartialCam16<T, L, C>,
115+
parameters: impl Into<BakedParameters<WpParam, T>>,
116+
) -> Self
117+
where
118+
WpParam: WhitePointParameter<T>,
119+
T: Real + Zero + Arithmetics + Sqrt + PartialCmp + Clone,
120+
T::Mask: LazySelect<T> + Clone,
121+
L: Cam16Luminance<T>,
122+
C: Cam16Chromaticity<T>,
123+
{
124+
partial.into_full(parameters)
125+
}
126+
127+
/// Create a partial set of CIE CAM16 attributes.
128+
///
129+
/// It's also possible to use `PartialCam16::from` or `Cam16::into`.
130+
#[inline]
131+
pub fn into_partial<L, C>(self) -> PartialCam16<T, L, C>
132+
where
133+
L: Cam16Luminance<T>,
134+
C: Cam16Chromaticity<T>,
135+
{
136+
PartialCam16::from_full(self)
137+
}
138+
}
139+
56140
impl<T> crate::Clamp for Cam16<T>
57141
where
58142
T: Clamp + Zero,
@@ -88,27 +172,10 @@ impl_eq_hue!(
88172
[lightness, chroma, brightness, colorfulness, saturation]
89173
);
90174

91-
impl<T> FromColorUnclamped<Cam16<T>> for Cam16<T> {
92-
fn from_color_unclamped(val: Cam16<T>) -> Self {
93-
val
94-
}
95-
}
96-
97-
impl<Wp, T> FromColorUnclamped<Xyz<Wp, T>> for Cam16<T>
98-
where
99-
Wp: WhitePoint<T>,
100-
Xyz<Wp, T>: IntoCam16<StaticWp<Wp>, T>,
101-
BakedParameters<StaticWp<Wp>, T>: Default,
102-
{
103-
fn from_color_unclamped(val: Xyz<Wp, T>) -> Self {
104-
val.into_cam16(BakedParameters::default())
105-
}
106-
}
107-
108175
#[cfg(test)]
109176
mod test {
110177
use crate::{
111-
cam16::{ChromaticityType, LuminanceType, PartialCam16},
178+
cam16::{ChromaticityType, LuminanceType, Parameters, PartialCam16},
112179
convert::{FromColorUnclamped, IntoColorUnclamped},
113180
Srgb,
114181
};
@@ -118,8 +185,9 @@ mod test {
118185
macro_rules! assert_cam16_to_rgb {
119186
($cam16:expr, $rgb:expr, $($params:tt)*) => {
120187
let cam16 = $cam16;
188+
let parameters = Parameters::TEST_DEFAULTS;
121189

122-
let rgb: Srgb<f64> = cam16.into_color_unclamped();
190+
let rgb: Srgb<f64> = cam16.into_xyz(parameters).into_color_unclamped();
123191
assert_relative_eq!(rgb, $rgb, $($params)*);
124192

125193
let chromaticities = [
@@ -140,7 +208,7 @@ mod test {
140208
luminance,
141209
};
142210
assert_relative_eq!(
143-
Srgb::<f64>::from_color_unclamped(dbg!(partial)),
211+
Srgb::<f64>::from_color_unclamped(dbg!(partial).into_xyz(parameters)),
144212
$rgb,
145213
$($params)*
146214
);
@@ -152,7 +220,8 @@ mod test {
152220
#[test]
153221
fn example_blue() {
154222
// Uses the example color from https://observablehq.com/@jrus/cam16
155-
let mut cam16: Cam16<f64> = Srgb::from(0x5588cc).into_linear().into_color_unclamped();
223+
let xyz = Srgb::from(0x5588cc).into_linear().into_color_unclamped();
224+
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
156225
cam16.hue = cam16.hue.into_positive_degrees().into();
157226

158227
assert_relative_eq!(
@@ -178,7 +247,8 @@ mod test {
178247
#[test]
179248
fn black() {
180249
// Checks against the output from https://observablehq.com/@jrus/cam16
181-
let mut cam16: Cam16<f64> = Srgb::from(0x000000).into_linear().into_color_unclamped();
250+
let xyz = Srgb::from(0x000000).into_linear().into_color_unclamped();
251+
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
182252
cam16.hue = cam16.hue.into_positive_degrees().into();
183253

184254
assert_relative_eq!(
@@ -204,7 +274,8 @@ mod test {
204274
#[test]
205275
fn white() {
206276
// Checks against the output from https://observablehq.com/@jrus/cam16
207-
let mut cam16: Cam16<f64> = Srgb::from(0xffffff).into_linear().into_color_unclamped();
277+
let xyz = Srgb::from(0xffffff).into_linear().into_color_unclamped();
278+
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
208279
cam16.hue = cam16.hue.into_positive_degrees().into();
209280

210281
assert_relative_eq!(
@@ -230,7 +301,8 @@ mod test {
230301
#[test]
231302
fn red() {
232303
// Checks against the output from https://observablehq.com/@jrus/cam16
233-
let mut cam16: Cam16<f64> = Srgb::from(0xff0000).into_linear().into_color_unclamped();
304+
let xyz = Srgb::from(0xff0000).into_linear().into_color_unclamped();
305+
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
234306
cam16.hue = cam16.hue.into_positive_degrees().into();
235307

236308
assert_relative_eq!(
@@ -252,7 +324,8 @@ mod test {
252324
#[test]
253325
fn green() {
254326
// Checks against the output from https://observablehq.com/@jrus/cam16
255-
let mut cam16: Cam16<f64> = Srgb::from(0x00ff00).into_linear().into_color_unclamped();
327+
let xyz = Srgb::from(0x00ff00).into_linear().into_color_unclamped();
328+
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
256329
cam16.hue = cam16.hue.into_positive_degrees().into();
257330

258331
assert_relative_eq!(
@@ -278,7 +351,8 @@ mod test {
278351
#[test]
279352
fn blue() {
280353
// Checks against the output from https://observablehq.com/@jrus/cam16
281-
let mut cam16: Cam16<f64> = Srgb::from(0x0000ff).into_linear().into_color_unclamped();
354+
let xyz = Srgb::from(0x0000ff).into_linear().into_color_unclamped();
355+
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
282356
cam16.hue = cam16.hue.into_positive_degrees().into();
283357

284358
assert_relative_eq!(

0 commit comments

Comments
 (0)