1use crate::TransferFunction;
12use crate::cicp::Cicp;
13use alloc::sync::Arc;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum ColorProfileSource<'a> {
23 Icc(&'a [u8]),
25 Cicp(Cicp),
27 Named(NamedProfile),
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
33#[non_exhaustive]
34pub enum NamedProfile {
35 #[default]
37 Srgb,
38 DisplayP3,
40 Bt2020,
42 Bt2020Pq,
44 Bt2020Hlg,
46 AdobeRgb,
48 LinearSrgb,
50}
51
52impl NamedProfile {
53 pub const fn from_cicp(cicp: Cicp) -> Option<Self> {
58 match (
61 cicp.color_primaries,
62 cicp.transfer_characteristics,
63 cicp.matrix_coefficients,
64 ) {
65 (1, 13, 0) => Some(Self::Srgb),
66 (12, 13, 0) => Some(Self::DisplayP3),
67 (9, 1, 0) => Some(Self::Bt2020),
68 (9, 16, _) => Some(Self::Bt2020Pq), (9, 18, _) => Some(Self::Bt2020Hlg), (1, 8, 0) => Some(Self::LinearSrgb),
71 _ => None,
72 }
73 }
74
75 pub const fn to_cicp(self) -> Option<Cicp> {
77 match self {
78 Self::Srgb => Some(Cicp::SRGB),
79 Self::DisplayP3 => Some(Cicp::DISPLAY_P3),
80 Self::Bt2020 => Some(Cicp {
81 color_primaries: 9,
82 transfer_characteristics: 1,
83 matrix_coefficients: 0,
84 full_range: true,
85 }),
86 Self::Bt2020Pq => Some(Cicp::BT2100_PQ),
87 Self::Bt2020Hlg => Some(Cicp::BT2100_HLG),
88 Self::LinearSrgb => Some(Cicp {
89 color_primaries: 1,
90 transfer_characteristics: 8,
91 matrix_coefficients: 0,
92 full_range: true,
93 }),
94 Self::AdobeRgb => None,
95 }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
106pub struct ColorContext {
107 pub icc: Option<Arc<[u8]>>,
109 pub cicp: Option<Cicp>,
111}
112
113impl ColorContext {
114 pub fn from_icc(icc: impl Into<Arc<[u8]>>) -> Self {
116 Self {
117 icc: Some(icc.into()),
118 cicp: None,
119 }
120 }
121
122 pub fn from_cicp(cicp: Cicp) -> Self {
124 Self {
125 icc: None,
126 cicp: Some(cicp),
127 }
128 }
129
130 pub fn from_icc_and_cicp(icc: impl Into<Arc<[u8]>>, cicp: Cicp) -> Self {
132 Self {
133 icc: Some(icc.into()),
134 cicp: Some(cicp),
135 }
136 }
137
138 pub fn as_profile_source(&self) -> Option<ColorProfileSource<'_>> {
143 if let Some(cicp) = self.cicp {
144 Some(ColorProfileSource::Cicp(cicp))
145 } else {
146 self.icc.as_deref().map(ColorProfileSource::Icc)
147 }
148 }
149
150 pub fn transfer_function(&self) -> TransferFunction {
152 self.cicp
153 .and_then(|c| TransferFunction::from_cicp(c.transfer_characteristics))
154 .unwrap_or(TransferFunction::Unknown)
155 }
156
157 pub fn is_srgb(&self) -> bool {
159 self.cicp == Some(Cicp::SRGB)
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
171#[non_exhaustive]
172pub enum ColorProvenance {
173 Icc,
175 Cicp,
177 GamaChrm,
179 Assumed,
181}
182
183#[derive(Clone, Debug, PartialEq, Eq)]
194#[non_exhaustive]
195pub struct ColorOrigin {
196 pub icc: Option<Arc<[u8]>>,
198 pub cicp: Option<Cicp>,
200 pub provenance: ColorProvenance,
202}
203
204impl ColorOrigin {
205 pub fn from_icc(icc: impl Into<Arc<[u8]>>) -> Self {
207 Self {
208 icc: Some(icc.into()),
209 cicp: None,
210 provenance: ColorProvenance::Icc,
211 }
212 }
213
214 pub fn from_cicp(cicp: Cicp) -> Self {
216 Self {
217 icc: None,
218 cicp: Some(cicp),
219 provenance: ColorProvenance::Cicp,
220 }
221 }
222
223 pub fn from_icc_and_cicp(icc: impl Into<Arc<[u8]>>, cicp: Cicp) -> Self {
225 Self {
226 icc: Some(icc.into()),
227 cicp: Some(cicp),
228 provenance: ColorProvenance::Cicp,
229 }
230 }
231
232 pub fn from_gama_chrm() -> Self {
234 Self {
235 icc: None,
236 cicp: None,
237 provenance: ColorProvenance::GamaChrm,
238 }
239 }
240
241 pub fn assumed() -> Self {
243 Self {
244 icc: None,
245 cicp: None,
246 provenance: ColorProvenance::Assumed,
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use alloc::vec;
255
256 #[test]
257 fn named_profile_default_is_srgb() {
258 assert_eq!(NamedProfile::default(), NamedProfile::Srgb);
259 }
260
261 #[test]
262 fn named_profile_to_cicp() {
263 assert_eq!(NamedProfile::Srgb.to_cicp(), Some(Cicp::SRGB));
264 assert_eq!(NamedProfile::Bt2020Pq.to_cicp(), Some(Cicp::BT2100_PQ));
265 assert!(NamedProfile::AdobeRgb.to_cicp().is_none());
266 }
267
268 #[test]
269 fn named_profile_from_cicp() {
270 assert_eq!(
271 NamedProfile::from_cicp(Cicp::SRGB),
272 Some(NamedProfile::Srgb)
273 );
274 assert_eq!(
275 NamedProfile::from_cicp(Cicp::DISPLAY_P3),
276 Some(NamedProfile::DisplayP3)
277 );
278 assert_eq!(
279 NamedProfile::from_cicp(Cicp::BT2100_PQ),
280 Some(NamedProfile::Bt2020Pq)
281 );
282 assert_eq!(
283 NamedProfile::from_cicp(Cicp::BT2100_HLG),
284 Some(NamedProfile::Bt2020Hlg)
285 );
286 assert_eq!(
288 NamedProfile::from_cicp(Cicp::new(1, 8, 0, true)),
289 Some(NamedProfile::LinearSrgb)
290 );
291 assert_eq!(
293 NamedProfile::from_cicp(Cicp::new(9, 1, 0, true)),
294 Some(NamedProfile::Bt2020)
295 );
296 assert_eq!(NamedProfile::from_cicp(Cicp::new(99, 99, 0, true)), None);
298 }
299
300 #[test]
301 fn named_profile_cicp_roundtrip() {
302 for profile in [
303 NamedProfile::Srgb,
304 NamedProfile::DisplayP3,
305 NamedProfile::Bt2020,
306 NamedProfile::Bt2020Pq,
307 NamedProfile::Bt2020Hlg,
308 NamedProfile::LinearSrgb,
309 ] {
310 let cicp = profile.to_cicp().unwrap();
311 assert_eq!(
312 NamedProfile::from_cicp(cicp),
313 Some(profile),
314 "roundtrip failed for {profile:?}"
315 );
316 }
317 }
318
319 #[test]
320 fn color_context_from_cicp() {
321 let ctx = ColorContext::from_cicp(Cicp::SRGB);
322 assert!(ctx.icc.is_none());
323 assert_eq!(ctx.cicp, Some(Cicp::SRGB));
324 }
325
326 #[test]
327 fn color_context_profile_source_prefers_cicp() {
328 let ctx = ColorContext::from_icc_and_cicp(vec![1, 2, 3], Cicp::SRGB);
329 let src = ctx.as_profile_source().unwrap();
330 assert_eq!(src, ColorProfileSource::Cicp(Cicp::SRGB));
331 }
332
333 #[test]
334 fn color_context_is_srgb() {
335 assert!(ColorContext::from_cicp(Cicp::SRGB).is_srgb());
336 assert!(!ColorContext::from_cicp(Cicp::BT2100_PQ).is_srgb());
337 }
338
339 #[test]
340 fn color_context_transfer_function() {
341 assert_eq!(
342 ColorContext::from_cicp(Cicp::SRGB).transfer_function(),
343 TransferFunction::Srgb
344 );
345 assert_eq!(
346 ColorContext::from_icc(vec![1]).transfer_function(),
347 TransferFunction::Unknown
348 );
349 }
350
351 #[test]
354 fn color_context_from_icc() {
355 let ctx = ColorContext::from_icc(vec![10, 20, 30]);
356 assert!(ctx.icc.is_some());
357 assert_eq!(ctx.icc.as_deref(), Some(&[10u8, 20, 30][..]));
358 assert!(ctx.cicp.is_none());
359 }
360
361 #[test]
362 fn color_context_from_icc_and_cicp() {
363 let ctx = ColorContext::from_icc_and_cicp(vec![1, 2], Cicp::BT2100_PQ);
364 assert!(ctx.icc.is_some());
365 assert_eq!(ctx.cicp, Some(Cicp::BT2100_PQ));
366 }
367
368 #[test]
369 fn color_context_profile_source_icc_only() {
370 let ctx = ColorContext::from_icc(vec![42]);
371 let src = ctx.as_profile_source().unwrap();
372 assert_eq!(src, ColorProfileSource::Icc(&[42]));
373 }
374
375 #[test]
376 fn color_context_profile_source_none() {
377 let ctx = ColorContext {
378 icc: None,
379 cicp: None,
380 };
381 assert!(ctx.as_profile_source().is_none());
382 }
383
384 #[test]
385 fn color_context_pq_transfer() {
386 assert_eq!(
387 ColorContext::from_cicp(Cicp::BT2100_PQ).transfer_function(),
388 TransferFunction::Pq
389 );
390 }
391
392 #[test]
393 fn color_context_hlg_transfer() {
394 assert_eq!(
395 ColorContext::from_cicp(Cicp::BT2100_HLG).transfer_function(),
396 TransferFunction::Hlg
397 );
398 }
399
400 #[test]
401 fn color_context_eq_and_clone() {
402 let a = ColorContext::from_cicp(Cicp::SRGB);
403 let b = a.clone();
404 assert_eq!(a, b);
405 let c = ColorContext::from_icc(vec![1]);
406 assert_ne!(a, c);
407 }
408
409 #[test]
410 fn color_context_debug() {
411 let ctx = ColorContext::from_cicp(Cicp::SRGB);
412 let s = alloc::format!("{ctx:?}");
413 assert!(s.contains("ColorContext"));
414 }
415
416 #[test]
419 fn color_profile_source_named() {
420 let src = ColorProfileSource::Named(NamedProfile::DisplayP3);
421 assert_eq!(src, ColorProfileSource::Named(NamedProfile::DisplayP3));
422 assert_ne!(src, ColorProfileSource::Named(NamedProfile::Srgb));
423 }
424
425 #[test]
426 fn color_profile_source_cicp() {
427 let src = ColorProfileSource::Cicp(Cicp::BT2100_PQ);
428 assert_eq!(src, ColorProfileSource::Cicp(Cicp::BT2100_PQ));
429 }
430
431 #[test]
432 fn color_profile_source_icc() {
433 let data: &[u8] = &[1, 2, 3];
434 let src = ColorProfileSource::Icc(data);
435 assert_eq!(src, ColorProfileSource::Icc(&[1, 2, 3]));
436 }
437
438 #[test]
439 fn color_profile_source_debug_clone() {
440 let src = ColorProfileSource::Named(NamedProfile::Srgb);
441 let s = alloc::format!("{src:?}");
442 assert!(s.contains("Named"));
443 let src2 = src.clone();
444 assert_eq!(src, src2);
445 }
446
447 #[test]
450 fn named_profile_all_variants_to_cicp() {
451 assert!(NamedProfile::Srgb.to_cicp().is_some());
452 assert!(NamedProfile::DisplayP3.to_cicp().is_some());
453 assert!(NamedProfile::Bt2020.to_cicp().is_some());
454 assert!(NamedProfile::Bt2020Pq.to_cicp().is_some());
455 assert!(NamedProfile::Bt2020Hlg.to_cicp().is_some());
456 assert!(NamedProfile::LinearSrgb.to_cicp().is_some());
457 assert!(NamedProfile::AdobeRgb.to_cicp().is_none());
458 }
459
460 #[test]
461 fn named_profile_debug_clone_eq() {
462 let p = NamedProfile::DisplayP3;
463 let _ = alloc::format!("{p:?}");
464 let p2 = p;
465 assert_eq!(p, p2);
466 }
467
468 #[test]
469 #[cfg(feature = "std")]
470 fn named_profile_hash() {
471 use core::hash::{Hash, Hasher};
472 let p = NamedProfile::DisplayP3;
473 let mut h = std::hash::DefaultHasher::new();
474 p.hash(&mut h);
475 let _ = h.finish();
476 }
477
478 #[test]
481 fn color_provenance_variants() {
482 assert_ne!(ColorProvenance::Icc, ColorProvenance::Cicp);
483 assert_ne!(ColorProvenance::GamaChrm, ColorProvenance::Assumed);
484 let a = ColorProvenance::Icc;
485 let b = a;
486 assert_eq!(a, b);
487 }
488
489 #[test]
490 fn color_provenance_debug() {
491 let p = ColorProvenance::Cicp;
492 let _ = alloc::format!("{p:?}");
493 }
494
495 #[test]
496 #[cfg(feature = "std")]
497 fn color_provenance_hash() {
498 use core::hash::{Hash, Hasher};
499 let p = ColorProvenance::Cicp;
500 let mut h = std::hash::DefaultHasher::new();
501 p.hash(&mut h);
502 let _ = h.finish();
503 }
504
505 #[test]
508 fn color_origin_from_icc() {
509 let o = ColorOrigin::from_icc(vec![1, 2, 3]);
510 assert!(o.icc.is_some());
511 assert!(o.cicp.is_none());
512 assert_eq!(o.provenance, ColorProvenance::Icc);
513 }
514
515 #[test]
516 fn color_origin_from_cicp() {
517 let o = ColorOrigin::from_cicp(Cicp::SRGB);
518 assert!(o.icc.is_none());
519 assert_eq!(o.cicp, Some(Cicp::SRGB));
520 assert_eq!(o.provenance, ColorProvenance::Cicp);
521 }
522
523 #[test]
524 fn color_origin_from_icc_and_cicp() {
525 let o = ColorOrigin::from_icc_and_cicp(vec![10], Cicp::BT2100_PQ);
526 assert!(o.icc.is_some());
527 assert_eq!(o.cicp, Some(Cicp::BT2100_PQ));
528 assert_eq!(o.provenance, ColorProvenance::Cicp);
529 }
530
531 #[test]
532 fn color_origin_from_gama_chrm() {
533 let o = ColorOrigin::from_gama_chrm();
534 assert!(o.icc.is_none());
535 assert!(o.cicp.is_none());
536 assert_eq!(o.provenance, ColorProvenance::GamaChrm);
537 }
538
539 #[test]
540 fn color_origin_assumed() {
541 let o = ColorOrigin::assumed();
542 assert!(o.icc.is_none());
543 assert!(o.cicp.is_none());
544 assert_eq!(o.provenance, ColorProvenance::Assumed);
545 }
546
547 #[test]
548 fn color_origin_eq_clone_debug() {
549 let a = ColorOrigin::from_cicp(Cicp::SRGB);
550 let b = a.clone();
551 assert_eq!(a, b);
552 let _ = alloc::format!("{a:?}");
553 let c = ColorOrigin::assumed();
554 assert_ne!(a, c);
555 }
556}