1use core::marker::PhantomData;
28
29use crate::adaptation::Bradford;
30use crate::illuminant::Illuminant;
31use crate::math::{DefaultMath, Mat3};
32use crate::primaries::{
33 AcesAp0Primaries, AcesAp1Primaries, DciP3Primaries, P3Primaries, Primaries, PrimariesToXyz, ProPhotoPrimaries,
34 Rec2020Primaries, SrgbPrimaries,
35};
36use crate::transfer::{
37 AcesCcTf, AcesCctTf, DciP3Tf, HlgTf, IsDisplayReferred, IsLinearEncoding, IsSceneReferred, LinearTf, PqTf,
38 ProPhotoTf, Rec709Tf, SrgbTf, TransferFunction,
39};
40use crate::xyz::Xyz;
41use crate::{
42 Asserts, Color, ColorSpace, DisplayReferred, LinearLight, OutOfGamut, SceneReferred, Transform, TryTransform,
43};
44
45pub trait ChannelMap<const N: usize>: 'static {
51 const INDICES: [usize; N];
53 const ALPHA: Option<usize>;
55}
56
57macro_rules! define_layout {
58 ($name:ident, [$r:expr, $g:expr, $b:expr, $a:expr]) => {
59 #[doc = concat!("Four-channel layout. R=`", stringify!($r), "` G=`", stringify!($g), "` B=`", stringify!($b), "` A=`", stringify!($a), "`.")]
60 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
61 pub struct $name;
62 impl Asserts<[f32; 4]> for $name {}
63 #[cfg(feature = "glam")]
64 impl Asserts<glam::Vec4> for $name {}
65 impl ChannelMap<4> for $name {
66 const INDICES: [usize; 4] = [$r, $g, $b, $a];
67 const ALPHA: Option<usize> = Some(3);
68 }
69 };
70 ($name:ident, [$r:expr, $g:expr, $b:expr]) => {
71 #[doc = concat!("Three-channel layout. R=`", stringify!($r), "` G=`", stringify!($g), "` B=`", stringify!($b), "`. No alpha.")]
72 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
73 pub struct $name;
74 impl Asserts<[f32; 3]> for $name {}
75 #[cfg(feature = "glam")]
76 impl Asserts<glam::Vec3> for $name {}
77 #[cfg(feature = "glam")]
78 impl Asserts<glam::Vec3A> for $name {}
79 impl ChannelMap<3> for $name {
80 const INDICES: [usize; 3] = [$r, $g, $b];
81 const ALPHA: Option<usize> = None;
82 }
83 };
84 ($name:ident, [$a:expr, $b:expr]) => {
85 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
87 pub struct $name;
88 impl Asserts<[f32; 2]> for $name {}
89 #[cfg(feature = "glam")]
90 impl Asserts<glam::Vec2> for $name {}
91 impl ChannelMap<2> for $name {
92 const INDICES: [usize; 2] = [$a, $b];
93 const ALPHA: Option<usize> = None;
94 }
95 };
96 ($name:ident, [$a:expr], alpha) => {
97 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
99 pub struct $name;
100 impl Asserts<[f32; 1]> for $name {}
101 impl Asserts<f32> for $name {}
102 impl ChannelMap<1> for $name {
103 const INDICES: [usize; 1] = [$a];
104 const ALPHA: Option<usize> = Some(0);
105 }
106 };
107 ($name:ident, [$a:expr]) => {
108 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
110 pub struct $name;
111 impl Asserts<[f32; 1]> for $name {}
112 impl Asserts<f32> for $name {}
113 impl ChannelMap<1> for $name {
114 const INDICES: [usize; 1] = [$a];
115 const ALPHA: Option<usize> = None;
116 }
117 };
118}
119
120define_layout!(Rgba, [0, 1, 2, 3]);
121define_layout!(Bgra, [2, 1, 0, 3]);
122define_layout!(Argb, [1, 2, 3, 0]);
123define_layout!(Abgr, [3, 2, 1, 0]);
124define_layout!(Rgb, [0, 1, 2]);
125define_layout!(Bgr, [2, 1, 0]);
126define_layout!(Rg, [0, 1]);
127define_layout!(Ra, [0, 1]);
128define_layout!(R, [0]);
129define_layout!(G, [0]);
130define_layout!(B, [0]);
131define_layout!(A, [0], alpha);
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
136pub struct RgbSpace<P: Primaries, TF: TransferFunction, L = Rgba>(PhantomData<(P, TF, L)>);
137
138pub trait RgbColorSpace: ColorSpace {}
140
141impl<P, TF, L> RgbColorSpace for RgbSpace<P, TF, L>
142where
143 P: Primaries,
144 TF: TransferFunction,
145 L: 'static,
146{
147}
148
149impl<P, TF, L> ColorSpace for RgbSpace<P, TF, L>
150where
151 P: Primaries,
152 TF: TransferFunction,
153 L: 'static,
154{
155 const CHANNELS: usize = 3;
156 const LUMINANCE_WEIGHTS: Option<[f32; 3]> = Some(P::TO_XYZ_NATIVE.luminance_weights());
157}
158
159impl<P, TF, L, S> Asserts<S> for RgbSpace<P, TF, L>
160where
161 P: Primaries,
162 TF: TransferFunction,
163 L: Asserts<S> + 'static,
164 S: 'static,
165{
166}
167
168impl<P, TF, L> LinearLight for RgbSpace<P, TF, L>
169where
170 P: Primaries,
171 TF: TransferFunction + IsLinearEncoding,
172 L: 'static,
173{
174}
175
176impl<P, TF, L> SceneReferred for RgbSpace<P, TF, L>
177where
178 P: Primaries,
179 TF: TransferFunction + IsSceneReferred,
180 L: 'static,
181{
182}
183
184impl<P, TF, L> DisplayReferred for RgbSpace<P, TF, L>
185where
186 P: Primaries,
187 TF: TransferFunction + IsDisplayReferred,
188 L: 'static,
189{
190}
191
192const fn direct_matrix<P1: Primaries, P2: Primaries>() -> Mat3 {
194 use crate::adaptation::adapt;
195 let a = adapt::<Bradford>(P1::Native::WHITE_POINT_XYZ, P2::Native::WHITE_POINT_XYZ);
196 let adapted = Mat3::mul(&a, &P1::TO_XYZ_NATIVE);
197 Mat3::mul(&P2::FROM_XYZ_NATIVE, &adapted)
198}
199
200impl<P, TF, L1, L2> Transform<Color<[f32; 4], RgbSpace<P, TF, L1>>> for Color<[f32; 4], RgbSpace<P, TF, L2>>
202where
203 P: Primaries,
204 TF: TransferFunction,
205 L1: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
206 L2: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
207{
208 fn transform_from(src: Color<[f32; 4], RgbSpace<P, TF, L1>>, _: &()) -> Self {
209 let s = src.inner();
210 let [r1, g1, b1, a1] = L1::INDICES;
211 let [r2, g2, b2, a2] = L2::INDICES;
212 let mut out = [0.0f32; 4];
213 out[r2] = s[r1];
214 out[g2] = s[g1];
215 out[b2] = s[b1];
216 out[a2] = s[a1];
217 Color::new_unchecked(out)
218 }
219}
220
221impl<P, TF, L1, L2> Transform<Color<[f32; 3], RgbSpace<P, TF, L1>>> for Color<[f32; 3], RgbSpace<P, TF, L2>>
223where
224 P: Primaries,
225 TF: TransferFunction,
226 L1: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
227 L2: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
228{
229 fn transform_from(src: Color<[f32; 3], RgbSpace<P, TF, L1>>, _: &()) -> Self {
230 let s = src.inner();
231 let [r1, g1, b1] = L1::INDICES;
232 let [r2, g2, b2] = L2::INDICES;
233 let mut out = [0.0f32; 3];
234 out[r2] = s[r1];
235 out[g2] = s[g1];
236 out[b2] = s[b1];
237 Color::new_unchecked(out)
238 }
239}
240
241impl<P, TF, W, L> Transform<Color<[f32; 4], RgbSpace<P, TF, L>>> for Color<[f32; 4], Xyz<W>>
243where
244 P: Primaries + PrimariesToXyz<W, Bradford>,
245 TF: TransferFunction,
246 W: Illuminant,
247 L: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
248 Xyz<W>: Asserts<[f32; 4]>,
249{
250 fn transform_from(src: Color<[f32; 4], RgbSpace<P, TF, L>>, _: &()) -> Self {
251 let s = src.inner();
252 let [ri, gi, bi, ai] = L::INDICES;
253 let rgb = TF::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
254 let xyz = <P as PrimariesToXyz<W, Bradford>>::TO_XYZ.apply(rgb);
255 Color::new_unchecked([xyz[0], xyz[1], xyz[2], s[ai]])
256 }
257}
258
259impl<P, TF, W, L> TryTransform<Color<[f32; 4], Xyz<W>>> for Color<[f32; 4], RgbSpace<P, TF, L>>
261where
262 P: Primaries + PrimariesToXyz<W, Bradford>,
263 TF: TransferFunction,
264 W: Illuminant,
265 L: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
266 Xyz<W>: Asserts<[f32; 4]>,
267{
268 fn try_transform_from(src: Color<[f32; 4], Xyz<W>>, _: &()) -> Result<Self, OutOfGamut<Self>> {
269 let x = src.inner();
270 let rgb = <P as PrimariesToXyz<W, Bradford>>::FROM_XYZ.apply([x[0], x[1], x[2]]);
271 let enc = TF::encode::<DefaultMath>(rgb);
272 let [ri, gi, bi, ai] = L::INDICES;
273 let mut s = [0.0f32; 4];
274 s[ri] = enc[0];
275 s[gi] = enc[1];
276 s[bi] = enc[2];
277 s[ai] = x[3];
278 let min = TF::ENCODED_MIN;
279 let max = TF::ENCODED_MAX;
280 let clipped = enc[0] < min[0]
281 || enc[0] > max[0]
282 || enc[1] < min[1]
283 || enc[1] > max[1]
284 || enc[2] < min[2]
285 || enc[2] > max[2]
286 || enc[0].is_nan()
287 || enc[1].is_nan()
288 || enc[2].is_nan();
289 if clipped {
290 let c = |v: f32, lo: f32| if v.is_nan() { lo } else { v };
291 s[ri] = c(enc[0], min[0]).clamp(min[0], max[0]);
292 s[gi] = c(enc[1], min[1]).clamp(min[1], max[1]);
293 s[bi] = c(enc[2], min[2]).clamp(min[2], max[2]);
294 Err(OutOfGamut {
295 clamped: Color::new_unchecked(s),
296 })
297 } else {
298 Ok(Color::new_unchecked(s))
299 }
300 }
301}
302
303impl<P, TF, W, L> Transform<Color<[f32; 3], RgbSpace<P, TF, L>>> for Color<[f32; 3], Xyz<W>>
305where
306 P: Primaries + PrimariesToXyz<W, Bradford>,
307 TF: TransferFunction,
308 W: Illuminant,
309 L: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
310 Xyz<W>: Asserts<[f32; 3]>,
311{
312 fn transform_from(src: Color<[f32; 3], RgbSpace<P, TF, L>>, _: &()) -> Self {
313 let s = src.inner();
314 let [ri, gi, bi] = L::INDICES;
315 let rgb = TF::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
316 let xyz = <P as PrimariesToXyz<W, Bradford>>::TO_XYZ.apply(rgb);
317 Color::new_unchecked([xyz[0], xyz[1], xyz[2]])
318 }
319}
320
321impl<P, TF, W, L> TryTransform<Color<[f32; 3], Xyz<W>>> for Color<[f32; 3], RgbSpace<P, TF, L>>
323where
324 P: Primaries + PrimariesToXyz<W, Bradford>,
325 TF: TransferFunction,
326 W: Illuminant,
327 L: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
328 Xyz<W>: Asserts<[f32; 3]>,
329{
330 fn try_transform_from(src: Color<[f32; 3], Xyz<W>>, _: &()) -> Result<Self, OutOfGamut<Self>> {
331 let x = src.inner();
332 let rgb = <P as PrimariesToXyz<W, Bradford>>::FROM_XYZ.apply([x[0], x[1], x[2]]);
333 let enc = TF::encode::<DefaultMath>(rgb);
334 let [ri, gi, bi] = L::INDICES;
335 let mut s = [0.0f32; 3];
336 s[ri] = enc[0];
337 s[gi] = enc[1];
338 s[bi] = enc[2];
339 let min = TF::ENCODED_MIN;
340 let max = TF::ENCODED_MAX;
341 let clipped = enc[0] < min[0]
342 || enc[0] > max[0]
343 || enc[1] < min[1]
344 || enc[1] > max[1]
345 || enc[2] < min[2]
346 || enc[2] > max[2]
347 || enc[0].is_nan()
348 || enc[1].is_nan()
349 || enc[2].is_nan();
350 if clipped {
351 let c = |v: f32, lo: f32| if v.is_nan() { lo } else { v };
352 s[ri] = c(enc[0], min[0]).clamp(min[0], max[0]);
353 s[gi] = c(enc[1], min[1]).clamp(min[1], max[1]);
354 s[bi] = c(enc[2], min[2]).clamp(min[2], max[2]);
355 Err(OutOfGamut {
356 clamped: Color::new_unchecked(s),
357 })
358 } else {
359 Ok(Color::new_unchecked(s))
360 }
361 }
362}
363
364#[cfg(feature = "glam")]
365impl<P, TF, W, L> Transform<Color<glam::Vec4, RgbSpace<P, TF, L>>> for Color<glam::Vec4, Xyz<W>>
366where
367 P: Primaries + PrimariesToXyz<W, Bradford>,
368 TF: TransferFunction,
369 W: Illuminant,
370 L: Asserts<glam::Vec4> + ChannelMap<4> + 'static,
371 Xyz<W>: Asserts<glam::Vec4>,
372{
373 fn transform_from(src: Color<glam::Vec4, RgbSpace<P, TF, L>>, _: &()) -> Self {
374 let s = src.inner().to_array();
375 let [ri, gi, bi, ai] = L::INDICES;
376 let rgb = TF::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
377 let xyz = <P as PrimariesToXyz<W, Bradford>>::TO_XYZ.apply(rgb);
378 Color::new_unchecked(glam::Vec4::new(xyz[0], xyz[1], xyz[2], s[ai]))
379 }
380}
381
382#[cfg(feature = "glam")]
383impl<P, TF, W, L> TryTransform<Color<glam::Vec4, Xyz<W>>> for Color<glam::Vec4, RgbSpace<P, TF, L>>
384where
385 P: Primaries + PrimariesToXyz<W, Bradford>,
386 TF: TransferFunction,
387 W: Illuminant,
388 L: Asserts<glam::Vec4> + ChannelMap<4> + 'static,
389 Xyz<W>: Asserts<glam::Vec4>,
390{
391 fn try_transform_from(src: Color<glam::Vec4, Xyz<W>>, _: &()) -> Result<Self, OutOfGamut<Self>> {
392 let x = src.inner().to_array();
393 let rgb = <P as PrimariesToXyz<W, Bradford>>::FROM_XYZ.apply([x[0], x[1], x[2]]);
394 let enc = TF::encode::<DefaultMath>(rgb);
395 let [ri, gi, bi, ai] = L::INDICES;
396 let mut s = [0.0f32; 4];
397 s[ri] = enc[0];
398 s[gi] = enc[1];
399 s[bi] = enc[2];
400 s[ai] = x[3];
401 let min = TF::ENCODED_MIN;
402 let max = TF::ENCODED_MAX;
403 let clipped = enc[0] < min[0]
404 || enc[0] > max[0]
405 || enc[1] < min[1]
406 || enc[1] > max[1]
407 || enc[2] < min[2]
408 || enc[2] > max[2]
409 || enc[0].is_nan()
410 || enc[1].is_nan()
411 || enc[2].is_nan();
412 if clipped {
413 let c = |v: f32, lo: f32| if v.is_nan() { lo } else { v };
414 s[ri] = c(enc[0], min[0]).clamp(min[0], max[0]);
415 s[gi] = c(enc[1], min[1]).clamp(min[1], max[1]);
416 s[bi] = c(enc[2], min[2]).clamp(min[2], max[2]);
417 Err(OutOfGamut {
418 clamped: Color::new_unchecked(glam::Vec4::from_array(s)),
419 })
420 } else {
421 Ok(Color::new_unchecked(glam::Vec4::from_array(s)))
422 }
423 }
424}
425
426#[cfg(feature = "glam")]
427impl<P, TF, W, L> Transform<Color<glam::Vec3A, RgbSpace<P, TF, L>>> for Color<glam::Vec3A, Xyz<W>>
428where
429 P: Primaries + PrimariesToXyz<W, Bradford>,
430 TF: TransferFunction,
431 W: Illuminant,
432 L: Asserts<glam::Vec3A> + ChannelMap<3> + 'static,
433 Xyz<W>: Asserts<glam::Vec3A>,
434{
435 fn transform_from(src: Color<glam::Vec3A, RgbSpace<P, TF, L>>, _: &()) -> Self {
436 let s = src.inner().to_array();
437 let [ri, gi, bi] = L::INDICES;
438 let rgb = TF::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
439 let xyz = <P as PrimariesToXyz<W, Bradford>>::TO_XYZ.apply(rgb);
440 Color::new_unchecked(glam::Vec3A::new(xyz[0], xyz[1], xyz[2]))
441 }
442}
443
444#[cfg(feature = "glam")]
445impl<P, TF, W, L> TryTransform<Color<glam::Vec3A, Xyz<W>>> for Color<glam::Vec3A, RgbSpace<P, TF, L>>
446where
447 P: Primaries + PrimariesToXyz<W, Bradford>,
448 TF: TransferFunction,
449 W: Illuminant,
450 L: Asserts<glam::Vec3A> + ChannelMap<3> + 'static,
451 Xyz<W>: Asserts<glam::Vec3A>,
452{
453 fn try_transform_from(src: Color<glam::Vec3A, Xyz<W>>, _: &()) -> Result<Self, OutOfGamut<Self>> {
454 let x = src.inner().to_array();
455 let rgb = <P as PrimariesToXyz<W, Bradford>>::FROM_XYZ.apply([x[0], x[1], x[2]]);
456 let enc = TF::encode::<DefaultMath>(rgb);
457 let [ri, gi, bi] = L::INDICES;
458 let mut s = [0.0f32; 3];
459 s[ri] = enc[0];
460 s[gi] = enc[1];
461 s[bi] = enc[2];
462 let min = TF::ENCODED_MIN;
463 let max = TF::ENCODED_MAX;
464 let clipped = enc[0] < min[0]
465 || enc[0] > max[0]
466 || enc[1] < min[1]
467 || enc[1] > max[1]
468 || enc[2] < min[2]
469 || enc[2] > max[2]
470 || enc[0].is_nan()
471 || enc[1].is_nan()
472 || enc[2].is_nan();
473 if clipped {
474 let c = |v: f32, lo: f32| if v.is_nan() { lo } else { v };
475 s[ri] = c(enc[0], min[0]).clamp(min[0], max[0]);
476 s[gi] = c(enc[1], min[1]).clamp(min[1], max[1]);
477 s[bi] = c(enc[2], min[2]).clamp(min[2], max[2]);
478 Err(OutOfGamut {
479 clamped: Color::new_unchecked(glam::Vec3A::from_array(s)),
480 })
481 } else {
482 Ok(Color::new_unchecked(glam::Vec3A::from_array(s)))
483 }
484 }
485}
486
487impl<L> Transform<Color<[f32; 4], Srgb<L>>> for Color<[f32; 4], DisplayP3<L>>
489where
490 L: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
491{
492 fn transform_from(src: Color<[f32; 4], Srgb<L>>, _: &()) -> Self {
493 let s = src.inner();
494 let [ri, gi, bi, _ai] = L::INDICES;
495 let lin = SrgbTf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
496 let out = const { direct_matrix::<SrgbPrimaries, P3Primaries>() }.apply(lin);
497 let enc = SrgbTf::encode::<DefaultMath>(out);
498 let mut r = s;
499 r[ri] = enc[0];
500 r[gi] = enc[1];
501 r[bi] = enc[2];
502 Color::new_unchecked(r)
503 }
504}
505
506impl<L> Transform<Color<[f32; 3], Srgb<L>>> for Color<[f32; 3], DisplayP3<L>>
507where
508 L: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
509{
510 fn transform_from(src: Color<[f32; 3], Srgb<L>>, _: &()) -> Self {
511 let s = src.inner();
512 let [ri, gi, bi] = L::INDICES;
513 let lin = SrgbTf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
514 let out = const { direct_matrix::<SrgbPrimaries, P3Primaries>() }.apply(lin);
515 let enc = SrgbTf::encode::<DefaultMath>(out);
516 let mut r = s;
517 r[ri] = enc[0];
518 r[gi] = enc[1];
519 r[bi] = enc[2];
520 Color::new_unchecked(r)
521 }
522}
523
524impl<L> Transform<Color<[f32; 4], DisplayP3<L>>> for Color<[f32; 4], Srgb<L>>
525where
526 L: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
527{
528 fn transform_from(src: Color<[f32; 4], DisplayP3<L>>, _: &()) -> Self {
529 let s = src.inner();
530 let [ri, gi, bi, _ai] = L::INDICES;
531 let lin = SrgbTf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
532 let out = const { direct_matrix::<P3Primaries, SrgbPrimaries>() }.apply(lin);
533 let enc = SrgbTf::encode::<DefaultMath>(out);
534 let mut r = s;
535 r[ri] = enc[0];
536 r[gi] = enc[1];
537 r[bi] = enc[2];
538 Color::new_unchecked(r)
539 }
540}
541
542impl<L> Transform<Color<[f32; 3], DisplayP3<L>>> for Color<[f32; 3], Srgb<L>>
543where
544 L: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
545{
546 fn transform_from(src: Color<[f32; 3], DisplayP3<L>>, _: &()) -> Self {
547 let s = src.inner();
548 let [ri, gi, bi] = L::INDICES;
549 let lin = SrgbTf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
550 let out = const { direct_matrix::<P3Primaries, SrgbPrimaries>() }.apply(lin);
551 let enc = SrgbTf::encode::<DefaultMath>(out);
552 let mut r = s;
553 r[ri] = enc[0];
554 r[gi] = enc[1];
555 r[bi] = enc[2];
556 Color::new_unchecked(r)
557 }
558}
559
560impl<L> Transform<Color<[f32; 4], Srgb<L>>> for Color<[f32; 4], Rec709<L>>
562where
563 L: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
564{
565 fn transform_from(src: Color<[f32; 4], Srgb<L>>, _: &()) -> Self {
566 let s = src.inner();
567 let [ri, gi, bi, _ai] = L::INDICES;
568 let lin = SrgbTf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
569 let enc = Rec709Tf::encode::<DefaultMath>(lin);
570 let mut r = s;
571 r[ri] = enc[0];
572 r[gi] = enc[1];
573 r[bi] = enc[2];
574 Color::new_unchecked(r)
575 }
576}
577
578impl<L> Transform<Color<[f32; 3], Srgb<L>>> for Color<[f32; 3], Rec709<L>>
579where
580 L: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
581{
582 fn transform_from(src: Color<[f32; 3], Srgb<L>>, _: &()) -> Self {
583 let s = src.inner();
584 let [ri, gi, bi] = L::INDICES;
585 let lin = SrgbTf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
586 let enc = Rec709Tf::encode::<DefaultMath>(lin);
587 let mut r = s;
588 r[ri] = enc[0];
589 r[gi] = enc[1];
590 r[bi] = enc[2];
591 Color::new_unchecked(r)
592 }
593}
594
595impl<L> Transform<Color<[f32; 4], Rec709<L>>> for Color<[f32; 4], Srgb<L>>
596where
597 L: Asserts<[f32; 4]> + ChannelMap<4> + 'static,
598{
599 fn transform_from(src: Color<[f32; 4], Rec709<L>>, _: &()) -> Self {
600 let s = src.inner();
601 let [ri, gi, bi, _ai] = L::INDICES;
602 let lin = Rec709Tf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
603 let enc = SrgbTf::encode::<DefaultMath>(lin);
604 let mut r = s;
605 r[ri] = enc[0];
606 r[gi] = enc[1];
607 r[bi] = enc[2];
608 Color::new_unchecked(r)
609 }
610}
611
612impl<L> Transform<Color<[f32; 3], Rec709<L>>> for Color<[f32; 3], Srgb<L>>
613where
614 L: Asserts<[f32; 3]> + ChannelMap<3> + 'static,
615{
616 fn transform_from(src: Color<[f32; 3], Rec709<L>>, _: &()) -> Self {
617 let s = src.inner();
618 let [ri, gi, bi] = L::INDICES;
619 let lin = Rec709Tf::decode::<DefaultMath>([s[ri], s[gi], s[bi]]);
620 let enc = SrgbTf::encode::<DefaultMath>(lin);
621 let mut r = s;
622 r[ri] = enc[0];
623 r[gi] = enc[1];
624 r[bi] = enc[2];
625 Color::new_unchecked(r)
626 }
627}
628
629pub type Srgb<L = Rgba> = RgbSpace<SrgbPrimaries, SrgbTf, L>;
632pub type LinearSrgb<L = Rgba> = RgbSpace<SrgbPrimaries, LinearTf, L>;
634pub type Rec709<L = Rgba> = RgbSpace<SrgbPrimaries, Rec709Tf, L>;
636pub type DisplayP3<L = Rgba> = RgbSpace<P3Primaries, SrgbTf, L>;
639pub type LinearP3<L = Rgba> = RgbSpace<P3Primaries, LinearTf, L>;
641pub type Hdr10<L = Rgba> = RgbSpace<Rec2020Primaries, PqTf, L>;
644pub type Hlg<L = Rgba> = RgbSpace<Rec2020Primaries, HlgTf, L>;
647pub type LinearRec2020<L = Rgba> = RgbSpace<Rec2020Primaries, LinearTf, L>;
649pub type AcesCg<L = Rgba> = RgbSpace<AcesAp1Primaries, LinearTf, L>;
652pub type Aces2065<L = Rgba> = RgbSpace<AcesAp0Primaries, LinearTf, L>;
655pub type AcesCc<L = Rgba> = RgbSpace<AcesAp1Primaries, AcesCcTf, L>;
657pub type AcesCct<L = Rgba> = RgbSpace<AcesAp1Primaries, AcesCctTf, L>;
659pub type ProPhoto<L = Rgba> = RgbSpace<ProPhotoPrimaries, ProPhotoTf, L>;
662pub type LinearProPhoto<L = Rgba> = RgbSpace<ProPhotoPrimaries, LinearTf, L>;
664pub type DciP3<L = Rgba> = RgbSpace<DciP3Primaries, DciP3Tf, L>;
667pub type P3D65Gamma26<L = Rgba> = RgbSpace<P3Primaries, DciP3Tf, L>;