1use core::fmt;
12use serde::{Deserialize, Serialize};
13
14const V4L2_COLORSPACE_SMPTE170M: u32 = 1;
16const V4L2_COLORSPACE_REC709: u32 = 3;
17const V4L2_COLORSPACE_470_SYSTEM_M: u32 = 5;
18const V4L2_COLORSPACE_470_SYSTEM_BG: u32 = 6;
19const V4L2_COLORSPACE_JPEG: u32 = 7;
20const V4L2_COLORSPACE_SRGB: u32 = 8;
21const V4L2_COLORSPACE_BT2020: u32 = 10;
22const V4L2_XFER_FUNC_709: u32 = 1;
23const V4L2_XFER_FUNC_SRGB: u32 = 2;
24const V4L2_XFER_FUNC_NONE: u32 = 5;
25const V4L2_XFER_FUNC_SMPTE2084: u32 = 7;
26const V4L2_YCBCR_ENC_DEFAULT: u32 = 0;
27const V4L2_YCBCR_ENC_601: u32 = 1;
28const V4L2_YCBCR_ENC_709: u32 = 2;
29const V4L2_YCBCR_ENC_BT2020: u32 = 6;
30const V4L2_QUANTIZATION_DEFAULT: u32 = 0;
31const V4L2_QUANTIZATION_FULL_RANGE: u32 = 1;
32const V4L2_QUANTIZATION_LIM_RANGE: u32 = 2;
33
34#[non_exhaustive]
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub enum ColorSpace {
38 Bt709,
39 Bt2020,
40 Srgb,
41 Smpte170m,
42}
43
44#[non_exhaustive]
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
55pub enum ColorTransfer {
56 Bt709,
57 Srgb,
58 Pq,
59 Hlg,
63 Linear,
64}
65
66#[non_exhaustive]
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
69pub enum ColorEncoding {
70 Bt601,
71 Bt709,
72 Bt2020,
73}
74
75#[non_exhaustive]
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
78pub enum ColorRange {
79 Full,
80 Limited,
81}
82
83impl ColorSpace {
84 pub fn from_str_code(s: &str) -> Option<Self> {
91 match s {
92 "bt709" => Some(Self::Bt709),
93 "bt2020" => Some(Self::Bt2020),
94 "srgb" => Some(Self::Srgb),
95 "smpte170m" => Some(Self::Smpte170m),
96 _ => None,
97 }
98 }
99
100 pub fn as_str(self) -> &'static str {
102 match self {
103 Self::Bt709 => "bt709",
104 Self::Bt2020 => "bt2020",
105 Self::Srgb => "srgb",
106 Self::Smpte170m => "smpte170m",
107 }
108 }
109
110 pub fn from_v4l2(v: u32) -> Option<Self> {
115 match v {
116 V4L2_COLORSPACE_SMPTE170M => Some(Self::Smpte170m),
117 V4L2_COLORSPACE_REC709 => Some(Self::Bt709),
118 V4L2_COLORSPACE_470_SYSTEM_M | V4L2_COLORSPACE_470_SYSTEM_BG => Some(Self::Smpte170m),
120 V4L2_COLORSPACE_JPEG | V4L2_COLORSPACE_SRGB => Some(Self::Srgb),
121 V4L2_COLORSPACE_BT2020 => Some(Self::Bt2020),
122 _ => None,
123 }
124 }
125}
126
127impl ColorTransfer {
128 pub fn from_str_code(s: &str) -> Option<Self> {
135 match s {
136 "bt709" => Some(Self::Bt709),
137 "srgb" => Some(Self::Srgb),
138 "pq" => Some(Self::Pq),
139 "hlg" => Some(Self::Hlg),
140 "linear" => Some(Self::Linear),
141 _ => None,
142 }
143 }
144
145 pub fn as_str(self) -> &'static str {
147 match self {
148 Self::Bt709 => "bt709",
149 Self::Srgb => "srgb",
150 Self::Pq => "pq",
151 Self::Hlg => "hlg",
152 Self::Linear => "linear",
153 }
154 }
155
156 pub fn from_v4l2(v: u32) -> Option<Self> {
165 match v {
166 V4L2_XFER_FUNC_709 => Some(Self::Bt709),
167 V4L2_XFER_FUNC_SRGB => Some(Self::Srgb),
168 V4L2_XFER_FUNC_NONE => Some(Self::Linear),
169 V4L2_XFER_FUNC_SMPTE2084 => Some(Self::Pq),
170 _ => None,
171 }
172 }
173}
174
175impl ColorEncoding {
176 pub fn from_str_code(s: &str) -> Option<Self> {
183 match s {
184 "bt601" => Some(Self::Bt601),
185 "bt709" => Some(Self::Bt709),
186 "bt2020" => Some(Self::Bt2020),
187 _ => None,
188 }
189 }
190
191 pub fn as_str(self) -> &'static str {
193 match self {
194 Self::Bt601 => "bt601",
195 Self::Bt709 => "bt709",
196 Self::Bt2020 => "bt2020",
197 }
198 }
199
200 pub fn from_v4l2(v: u32) -> Option<Self> {
205 match v {
206 V4L2_YCBCR_ENC_601 => Some(Self::Bt601),
207 V4L2_YCBCR_ENC_709 => Some(Self::Bt709),
208 V4L2_YCBCR_ENC_BT2020 => Some(Self::Bt2020),
209 _ => None,
210 }
211 }
212}
213
214impl ColorRange {
215 pub fn from_str_code(s: &str) -> Option<Self> {
222 match s {
223 "full" => Some(Self::Full),
224 "limited" => Some(Self::Limited),
225 _ => None,
226 }
227 }
228
229 pub fn as_str(self) -> &'static str {
231 match self {
232 Self::Full => "full",
233 Self::Limited => "limited",
234 }
235 }
236
237 pub fn from_v4l2(v: u32) -> Option<Self> {
242 match v {
243 V4L2_QUANTIZATION_FULL_RANGE => Some(Self::Full),
244 V4L2_QUANTIZATION_LIM_RANGE => Some(Self::Limited),
245 _ => None,
246 }
247 }
248}
249
250macro_rules! display_via_as_str {
251 ($($t:ty),*) => {$(
252 impl fmt::Display for $t {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 f.write_str(self.as_str())
255 }
256 }
257 )*};
258}
259display_via_as_str!(ColorSpace, ColorTransfer, ColorEncoding, ColorRange);
260
261#[derive(Debug, Clone, Copy, PartialEq)]
268pub struct MatrixWeights {
269 pub kr: f64,
270 pub kb: f64,
271}
272
273impl MatrixWeights {
274 pub fn kg(self) -> f64 {
276 1.0 - self.kr - self.kb
277 }
278}
279
280impl ColorEncoding {
281 pub fn luma_weights(self) -> MatrixWeights {
284 match self {
285 Self::Bt601 => MatrixWeights {
286 kr: 0.299,
287 kb: 0.114,
288 },
289 Self::Bt709 => MatrixWeights {
290 kr: 0.2126,
291 kb: 0.0722,
292 },
293 Self::Bt2020 => MatrixWeights {
294 kr: 0.2627,
295 kb: 0.0593,
296 },
297 }
298 }
299}
300
301#[derive(Debug, Clone, Copy, PartialEq)]
305pub struct RangeScaling {
306 pub y_offset: f64,
308 pub y_swing: f64,
310 pub c_swing: f64,
312}
313
314impl ColorRange {
315 pub fn scaling(self) -> RangeScaling {
319 match self {
320 Self::Full => RangeScaling {
321 y_offset: 0.0,
322 y_swing: 255.0,
323 c_swing: 255.0,
324 },
325 Self::Limited => RangeScaling {
326 y_offset: 16.0,
327 y_swing: 219.0,
328 c_swing: 224.0,
329 },
330 }
331 }
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
338pub struct Colorimetry {
339 pub space: Option<ColorSpace>,
340 pub transfer: Option<ColorTransfer>,
341 pub encoding: Option<ColorEncoding>,
342 pub range: Option<ColorRange>,
343}
344
345impl Colorimetry {
346 pub fn jfif() -> Self {
349 Self {
350 space: Some(ColorSpace::Srgb),
351 transfer: Some(ColorTransfer::Srgb),
352 encoding: Some(ColorEncoding::Bt601),
353 range: Some(ColorRange::Full),
354 }
355 }
356
357 pub fn from_v4l2(colorspace: u32, xfer: u32, ycbcr_enc: u32, quant: u32) -> Self {
369 let encoding = ColorEncoding::from_v4l2(ycbcr_enc).or_else(|| {
370 if ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT {
371 Self::default_encoding_for_colorspace(colorspace)
372 } else {
373 None
374 }
375 });
376 let range = ColorRange::from_v4l2(quant).or_else(|| {
377 if quant == V4L2_QUANTIZATION_DEFAULT {
378 Self::default_range_for_colorspace(colorspace)
379 } else {
380 None
381 }
382 });
383 Self {
384 space: ColorSpace::from_v4l2(colorspace),
385 transfer: ColorTransfer::from_v4l2(xfer),
386 encoding,
387 range,
388 }
389 }
390
391 fn default_encoding_for_colorspace(colorspace: u32) -> Option<ColorEncoding> {
394 match colorspace {
395 V4L2_COLORSPACE_REC709 => Some(ColorEncoding::Bt709),
396 V4L2_COLORSPACE_BT2020 => Some(ColorEncoding::Bt2020),
397 V4L2_COLORSPACE_SMPTE170M
398 | V4L2_COLORSPACE_470_SYSTEM_M
399 | V4L2_COLORSPACE_470_SYSTEM_BG
400 | V4L2_COLORSPACE_JPEG
401 | V4L2_COLORSPACE_SRGB => Some(ColorEncoding::Bt601),
402 _ => None,
403 }
404 }
405
406 fn default_range_for_colorspace(colorspace: u32) -> Option<ColorRange> {
411 match colorspace {
412 V4L2_COLORSPACE_JPEG => Some(ColorRange::Full),
413 V4L2_COLORSPACE_SMPTE170M
414 | V4L2_COLORSPACE_REC709
415 | V4L2_COLORSPACE_BT2020
416 | V4L2_COLORSPACE_470_SYSTEM_M
417 | V4L2_COLORSPACE_470_SYSTEM_BG
418 | V4L2_COLORSPACE_SRGB => Some(ColorRange::Limited),
419 _ => None,
420 }
421 }
422
423 pub fn with_space(mut self, s: ColorSpace) -> Self {
425 self.space = Some(s);
426 self
427 }
428
429 pub fn with_transfer(mut self, t: ColorTransfer) -> Self {
431 self.transfer = Some(t);
432 self
433 }
434
435 pub fn with_encoding(mut self, e: ColorEncoding) -> Self {
437 self.encoding = Some(e);
438 self
439 }
440
441 pub fn with_range(mut self, r: ColorRange) -> Self {
443 self.range = Some(r);
444 self
445 }
446
447 pub fn pack(&self) -> u32 {
460 let space: u32 = match self.space {
461 None => 0,
462 Some(ColorSpace::Bt709) => 1,
463 Some(ColorSpace::Bt2020) => 2,
464 Some(ColorSpace::Srgb) => 3,
465 Some(ColorSpace::Smpte170m) => 4,
466 };
467 let transfer: u32 = match self.transfer {
468 None => 0,
469 Some(ColorTransfer::Bt709) => 1,
470 Some(ColorTransfer::Srgb) => 2,
471 Some(ColorTransfer::Pq) => 3,
472 Some(ColorTransfer::Hlg) => 4,
473 Some(ColorTransfer::Linear) => 5,
474 };
475 let encoding: u32 = match self.encoding {
476 None => 0,
477 Some(ColorEncoding::Bt601) => 1,
478 Some(ColorEncoding::Bt709) => 2,
479 Some(ColorEncoding::Bt2020) => 3,
480 };
481 let range: u32 = match self.range {
482 None => 0,
483 Some(ColorRange::Full) => 1,
484 Some(ColorRange::Limited) => 2,
485 };
486 space | (transfer << 8) | (encoding << 16) | (range << 24)
487 }
488
489 pub fn unpack(bits: u32) -> Self {
498 let space = match bits & 0xFF {
499 1 => Some(ColorSpace::Bt709),
500 2 => Some(ColorSpace::Bt2020),
501 3 => Some(ColorSpace::Srgb),
502 4 => Some(ColorSpace::Smpte170m),
503 _ => None,
504 };
505 let transfer = match (bits >> 8) & 0xFF {
506 1 => Some(ColorTransfer::Bt709),
507 2 => Some(ColorTransfer::Srgb),
508 3 => Some(ColorTransfer::Pq),
509 4 => Some(ColorTransfer::Hlg),
510 5 => Some(ColorTransfer::Linear),
511 _ => None,
512 };
513 let encoding = match (bits >> 16) & 0xFF {
514 1 => Some(ColorEncoding::Bt601),
515 2 => Some(ColorEncoding::Bt709),
516 3 => Some(ColorEncoding::Bt2020),
517 _ => None,
518 };
519 let range = match (bits >> 24) & 0xFF {
520 1 => Some(ColorRange::Full),
521 2 => Some(ColorRange::Limited),
522 _ => None,
523 };
524 Colorimetry {
525 space,
526 transfer,
527 encoding,
528 range,
529 }
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn from_v4l2_known_and_default() {
539 assert_eq!(ColorEncoding::from_v4l2(1), Some(ColorEncoding::Bt601));
540 assert_eq!(ColorEncoding::from_v4l2(2), Some(ColorEncoding::Bt709));
541 assert_eq!(ColorEncoding::from_v4l2(6), Some(ColorEncoding::Bt2020));
542 assert_eq!(ColorEncoding::from_v4l2(0), None); assert_eq!(ColorEncoding::from_v4l2(3), None); assert_eq!(ColorRange::from_v4l2(1), Some(ColorRange::Full));
545 assert_eq!(ColorRange::from_v4l2(2), Some(ColorRange::Limited));
546 assert_eq!(ColorSpace::from_v4l2(7), Some(ColorSpace::Srgb)); assert_eq!(ColorTransfer::from_v4l2(5), Some(ColorTransfer::Linear)); assert_eq!(ColorSpace::from_v4l2(1), Some(ColorSpace::Smpte170m)); assert_eq!(ColorSpace::from_v4l2(3), Some(ColorSpace::Bt709)); assert_eq!(ColorSpace::from_v4l2(10), Some(ColorSpace::Bt2020)); assert_eq!(ColorSpace::from_v4l2(5), Some(ColorSpace::Smpte170m)); assert_eq!(ColorSpace::from_v4l2(6), Some(ColorSpace::Smpte170m)); assert_eq!(ColorTransfer::from_v4l2(1), Some(ColorTransfer::Bt709)); assert_eq!(ColorTransfer::from_v4l2(2), Some(ColorTransfer::Srgb)); assert_eq!(ColorTransfer::from_v4l2(7), Some(ColorTransfer::Pq)); assert_eq!(ColorTransfer::from_v4l2(3), None); assert_eq!(ColorTransfer::from_v4l2(6), None); for v in 0u32..=10 {
565 assert_ne!(ColorTransfer::from_v4l2(v), Some(ColorTransfer::Hlg));
566 }
567 }
568
569 #[test]
570 fn jfif_is_bt601_full_srgb() {
571 let c = Colorimetry::jfif();
572 assert_eq!(c.space, Some(ColorSpace::Srgb));
573 assert_eq!(c.transfer, Some(ColorTransfer::Srgb));
574 assert_eq!(c.encoding, Some(ColorEncoding::Bt601));
575 assert_eq!(c.range, Some(ColorRange::Full));
576 }
577
578 #[test]
579 fn from_v4l2_struct_maps_all_axes_and_unknown_to_none() {
580 let c = Colorimetry::from_v4l2(3, 1, 2, 1); assert_eq!(c.space, Some(ColorSpace::Bt709));
582 assert_eq!(c.transfer, Some(ColorTransfer::Bt709));
583 assert_eq!(c.encoding, Some(ColorEncoding::Bt709));
584 assert_eq!(c.range, Some(ColorRange::Full));
585 let d = Colorimetry::from_v4l2(0, 0, 0, 0); assert_eq!(d, Colorimetry::default()); }
588
589 #[test]
590 fn from_v4l2_default_enc_quant_derive_from_colorspace() {
591 let jpeg = Colorimetry::from_v4l2(7, 0, 0, 0);
595 assert_eq!(jpeg.encoding, Some(ColorEncoding::Bt601));
596 assert_eq!(jpeg.range, Some(ColorRange::Full));
597
598 let rec709 = Colorimetry::from_v4l2(3, 0, 0, 0);
600 assert_eq!(rec709.encoding, Some(ColorEncoding::Bt709));
601 assert_eq!(rec709.range, Some(ColorRange::Limited));
602
603 let explicit = Colorimetry::from_v4l2(7, 0, 2, 2); assert_eq!(explicit.encoding, Some(ColorEncoding::Bt709));
606 assert_eq!(explicit.range, Some(ColorRange::Limited));
607
608 let unknown_enc = Colorimetry::from_v4l2(7, 0, 99, 0);
610 assert_eq!(unknown_enc.encoding, None);
611 assert_eq!(unknown_enc.range, Some(ColorRange::Full)); }
613
614 #[test]
615 fn luma_weights_are_the_canonical_bt_constants() {
616 assert_eq!(
617 ColorEncoding::Bt601.luma_weights(),
618 MatrixWeights {
619 kr: 0.299,
620 kb: 0.114
621 }
622 );
623 assert_eq!(
624 ColorEncoding::Bt709.luma_weights(),
625 MatrixWeights {
626 kr: 0.2126,
627 kb: 0.0722
628 }
629 );
630 assert_eq!(
631 ColorEncoding::Bt2020.luma_weights(),
632 MatrixWeights {
633 kr: 0.2627,
634 kb: 0.0593
635 }
636 );
637 assert!((ColorEncoding::Bt709.luma_weights().kg() - 0.7152).abs() < 1e-9);
639 }
640
641 #[test]
642 fn range_scaling_is_full_or_studio_swing() {
643 let full = ColorRange::Full.scaling();
644 assert_eq!(
645 full,
646 RangeScaling {
647 y_offset: 0.0,
648 y_swing: 255.0,
649 c_swing: 255.0
650 }
651 );
652 let limited = ColorRange::Limited.scaling();
653 assert_eq!(
654 limited,
655 RangeScaling {
656 y_offset: 16.0,
657 y_swing: 219.0,
658 c_swing: 224.0
659 }
660 );
661 }
662
663 #[test]
664 fn as_str_and_from_str_code_are_inverses_for_every_variant() {
665 for v in [
668 ColorSpace::Bt709,
669 ColorSpace::Bt2020,
670 ColorSpace::Srgb,
671 ColorSpace::Smpte170m,
672 ] {
673 assert_eq!(ColorSpace::from_str_code(v.as_str()), Some(v), "{v:?}");
674 }
675 for v in [
676 ColorTransfer::Bt709,
677 ColorTransfer::Srgb,
678 ColorTransfer::Pq,
679 ColorTransfer::Hlg,
680 ColorTransfer::Linear,
681 ] {
682 assert_eq!(ColorTransfer::from_str_code(v.as_str()), Some(v), "{v:?}");
683 }
684 for v in [
685 ColorEncoding::Bt601,
686 ColorEncoding::Bt709,
687 ColorEncoding::Bt2020,
688 ] {
689 assert_eq!(ColorEncoding::from_str_code(v.as_str()), Some(v), "{v:?}");
690 }
691 for v in [ColorRange::Full, ColorRange::Limited] {
692 assert_eq!(ColorRange::from_str_code(v.as_str()), Some(v), "{v:?}");
693 }
694 }
695
696 #[test]
697 fn an_unknown_or_empty_label_is_unspecified_not_an_error() {
698 assert_eq!(ColorSpace::from_str_code(""), None);
699 assert_eq!(ColorSpace::from_str_code("not-a-colour-space"), None);
700 assert_eq!(
701 ColorRange::from_str_code("FULL"),
702 None,
703 "labels are case-sensitive"
704 );
705 }
706
707 #[test]
708 fn as_str_matches_schema() {
709 assert_eq!(ColorEncoding::Bt601.as_str(), "bt601");
710 assert_eq!(ColorRange::Full.as_str(), "full");
711 assert_eq!(ColorSpace::Smpte170m.as_str(), "smpte170m");
712 assert_eq!(ColorTransfer::Pq.as_str(), "pq");
713 assert_eq!(ColorTransfer::Hlg.as_str(), "hlg");
714 }
715
716 #[test]
717 fn pack_unpack_round_trips() {
718 assert_eq!(
723 Colorimetry::unpack(Colorimetry::default().pack()),
724 Colorimetry::default()
725 );
726 assert_eq!(Colorimetry::default().pack(), 0);
727
728 let jfif = Colorimetry::jfif();
729 assert_eq!(Colorimetry::unpack(jfif.pack()), jfif);
730
731 let full = Colorimetry {
732 space: Some(ColorSpace::Bt2020),
733 transfer: Some(ColorTransfer::Hlg),
734 encoding: Some(ColorEncoding::Bt2020),
735 range: Some(ColorRange::Limited),
736 };
737 assert_eq!(Colorimetry::unpack(full.pack()), full);
738 }
739
740 #[test]
741 fn unpack_maps_unrecognised_codes_to_none() {
742 let bits = 0xFFu32 | (0xFFu32 << 8) | (0xFFu32 << 16) | (0xFFu32 << 24);
746 assert_eq!(Colorimetry::unpack(bits), Colorimetry::default());
747 }
748
749 const SPACES: [(ColorSpace, u32); 4] = [
750 (ColorSpace::Bt709, 1),
751 (ColorSpace::Bt2020, 2),
752 (ColorSpace::Srgb, 3),
753 (ColorSpace::Smpte170m, 4),
754 ];
755 const TRANSFERS: [(ColorTransfer, u32); 5] = [
756 (ColorTransfer::Bt709, 1),
757 (ColorTransfer::Srgb, 2),
758 (ColorTransfer::Pq, 3),
759 (ColorTransfer::Hlg, 4),
760 (ColorTransfer::Linear, 5),
761 ];
762 const ENCODINGS: [(ColorEncoding, u32); 3] = [
763 (ColorEncoding::Bt601, 1),
764 (ColorEncoding::Bt709, 2),
765 (ColorEncoding::Bt2020, 3),
766 ];
767 const RANGES: [(ColorRange, u32); 2] = [(ColorRange::Full, 1), (ColorRange::Limited, 2)];
768
769 #[test]
770 fn pack_places_each_axis_code_in_its_own_byte() {
771 for (s, code) in SPACES {
773 let c = Colorimetry::default().with_space(s);
774 assert_eq!(c.pack(), code, "{s:?}");
775 assert_eq!(Colorimetry::unpack(code), c, "{s:?}");
776 }
777 for (t, code) in TRANSFERS {
778 let c = Colorimetry::default().with_transfer(t);
779 assert_eq!(c.pack(), code << 8, "{t:?}");
780 assert_eq!(Colorimetry::unpack(code << 8), c, "{t:?}");
781 }
782 for (e, code) in ENCODINGS {
783 let c = Colorimetry::default().with_encoding(e);
784 assert_eq!(c.pack(), code << 16, "{e:?}");
785 assert_eq!(Colorimetry::unpack(code << 16), c, "{e:?}");
786 }
787 for (r, code) in RANGES {
788 let c = Colorimetry::default().with_range(r);
789 assert_eq!(c.pack(), code << 24, "{r:?}");
790 assert_eq!(Colorimetry::unpack(code << 24), c, "{r:?}");
791 }
792 }
793
794 #[test]
795 fn unpack_decodes_a_fully_populated_word() {
796 assert_eq!(
797 Colorimetry::unpack(0x02_02_05_04),
798 Colorimetry {
799 space: Some(ColorSpace::Smpte170m),
800 transfer: Some(ColorTransfer::Linear),
801 encoding: Some(ColorEncoding::Bt709),
802 range: Some(ColorRange::Limited),
803 }
804 );
805 }
806
807 #[test]
808 fn every_axis_combination_round_trips_through_pack() {
809 for (s, _) in SPACES {
810 for (t, _) in TRANSFERS {
811 for (e, _) in ENCODINGS {
812 for (r, _) in RANGES {
813 let c = Colorimetry {
814 space: Some(s),
815 transfer: Some(t),
816 encoding: Some(e),
817 range: Some(r),
818 };
819 assert_eq!(Colorimetry::unpack(c.pack()), c);
820 }
821 }
822 }
823 }
824 }
825
826 #[test]
827 fn builders_set_exactly_their_own_axis() {
828 let c = Colorimetry::default()
829 .with_space(ColorSpace::Bt2020)
830 .with_transfer(ColorTransfer::Pq)
831 .with_encoding(ColorEncoding::Bt2020)
832 .with_range(ColorRange::Full);
833 assert_eq!(
834 c,
835 Colorimetry {
836 space: Some(ColorSpace::Bt2020),
837 transfer: Some(ColorTransfer::Pq),
838 encoding: Some(ColorEncoding::Bt2020),
839 range: Some(ColorRange::Full),
840 }
841 );
842 let base = Colorimetry::jfif();
844 assert_eq!(
845 base.with_space(ColorSpace::Bt709),
846 Colorimetry {
847 space: Some(ColorSpace::Bt709),
848 ..base
849 }
850 );
851 assert_eq!(
852 base.with_transfer(ColorTransfer::Hlg),
853 Colorimetry {
854 transfer: Some(ColorTransfer::Hlg),
855 ..base
856 }
857 );
858 assert_eq!(
859 base.with_encoding(ColorEncoding::Bt709),
860 Colorimetry {
861 encoding: Some(ColorEncoding::Bt709),
862 ..base
863 }
864 );
865 assert_eq!(
866 base.with_range(ColorRange::Limited),
867 Colorimetry {
868 range: Some(ColorRange::Limited),
869 ..base
870 }
871 );
872 }
873
874 #[test]
875 fn from_v4l2_derives_encoding_and_range_for_every_recognised_colorspace() {
876 use ColorEncoding as E;
877 use ColorRange as R;
878 let table = [
879 (V4L2_COLORSPACE_SMPTE170M, E::Bt601, R::Limited),
880 (V4L2_COLORSPACE_REC709, E::Bt709, R::Limited),
881 (V4L2_COLORSPACE_470_SYSTEM_M, E::Bt601, R::Limited),
882 (V4L2_COLORSPACE_470_SYSTEM_BG, E::Bt601, R::Limited),
883 (V4L2_COLORSPACE_JPEG, E::Bt601, R::Full),
884 (V4L2_COLORSPACE_SRGB, E::Bt601, R::Limited),
885 (V4L2_COLORSPACE_BT2020, E::Bt2020, R::Limited),
886 ];
887 for (colorspace, encoding, range) in table {
888 let c = Colorimetry::from_v4l2(colorspace, 0, 0, 0);
889 assert_eq!(c.encoding, Some(encoding), "colorspace {colorspace}");
890 assert_eq!(c.range, Some(range), "colorspace {colorspace}");
891 }
892 for colorspace in [0, 2, 4, 9, 11, 99] {
894 let c = Colorimetry::from_v4l2(colorspace, 0, 0, 0);
895 assert_eq!(
896 (c.encoding, c.range),
897 (None, None),
898 "colorspace {colorspace}"
899 );
900 }
901 }
902}