1use core::fmt;
12use serde::{Deserialize, Serialize};
13
14const V4L2_COLORSPACE_SMPTE170M: u32 = 1;
19const V4L2_COLORSPACE_REC709: u32 = 3;
20const V4L2_COLORSPACE_470_SYSTEM_M: u32 = 5;
21const V4L2_COLORSPACE_470_SYSTEM_BG: u32 = 6;
22const V4L2_COLORSPACE_JPEG: u32 = 7;
23const V4L2_COLORSPACE_SRGB: u32 = 8;
24const V4L2_COLORSPACE_BT2020: u32 = 10;
25const V4L2_XFER_FUNC_709: u32 = 1;
26const V4L2_XFER_FUNC_SRGB: u32 = 2;
27const V4L2_XFER_FUNC_NONE: u32 = 5;
28const V4L2_XFER_FUNC_SMPTE2084: u32 = 7;
29const V4L2_YCBCR_ENC_DEFAULT: u32 = 0;
30const V4L2_YCBCR_ENC_601: u32 = 1;
31const V4L2_YCBCR_ENC_709: u32 = 2;
32const V4L2_YCBCR_ENC_BT2020: u32 = 6;
33const V4L2_QUANTIZATION_DEFAULT: u32 = 0;
34const V4L2_QUANTIZATION_FULL_RANGE: u32 = 1;
35const V4L2_QUANTIZATION_LIM_RANGE: u32 = 2;
36
37crate::ef_vocabulary! {
44 #[non_exhaustive]
46 #[derive(Serialize, Deserialize)]
47 pub enum ColorSpace {
48 Bt709 = 1, "bt709", BT709,
49 Bt2020 = 2, "bt2020", BT2020,
50 Srgb = 3, "srgb", SRGB,
51 Smpte170m = 4, "smpte170m", SMPTE170M,
52 }
53 #[doc(hidden)]
54 pub mod color_space_wire;
55}
56
57crate::ef_vocabulary! {
58 #[non_exhaustive]
68 #[derive(Serialize, Deserialize)]
69 pub enum ColorTransfer {
70 Bt709 = 1, "bt709", BT709,
71 Srgb = 2, "srgb", SRGB,
72 Pq = 3, "pq", PQ,
73 Hlg = 4, "hlg", HLG,
77 Linear = 5, "linear", LINEAR,
78 }
79 #[doc(hidden)]
80 pub mod color_transfer_wire;
81}
82
83crate::ef_vocabulary! {
84 #[non_exhaustive]
86 #[derive(Serialize, Deserialize)]
87 pub enum ColorEncoding {
88 Bt601 = 1, "bt601", BT601,
89 Bt709 = 2, "bt709", BT709,
90 Bt2020 = 3, "bt2020", BT2020,
91 }
92 #[doc(hidden)]
93 pub mod color_encoding_wire;
94}
95
96crate::ef_vocabulary! {
97 #[non_exhaustive]
99 #[derive(Serialize, Deserialize)]
100 pub enum ColorRange {
101 Full = 1, "full", FULL,
102 Limited = 2, "limited", LIMITED,
103 }
104 #[doc(hidden)]
105 pub mod color_range_wire;
106}
107
108impl ColorSpace {
109 pub fn from_v4l2(v: u32) -> Option<Self> {
114 match v {
115 V4L2_COLORSPACE_SMPTE170M => Some(Self::Smpte170m),
116 V4L2_COLORSPACE_REC709 => Some(Self::Bt709),
117 V4L2_COLORSPACE_470_SYSTEM_M | V4L2_COLORSPACE_470_SYSTEM_BG => Some(Self::Smpte170m),
119 V4L2_COLORSPACE_JPEG | V4L2_COLORSPACE_SRGB => Some(Self::Srgb),
120 V4L2_COLORSPACE_BT2020 => Some(Self::Bt2020),
121 _ => None,
122 }
123 }
124}
125
126impl ColorTransfer {
127 pub fn from_v4l2(v: u32) -> Option<Self> {
136 match v {
137 V4L2_XFER_FUNC_709 => Some(Self::Bt709),
138 V4L2_XFER_FUNC_SRGB => Some(Self::Srgb),
139 V4L2_XFER_FUNC_NONE => Some(Self::Linear),
140 V4L2_XFER_FUNC_SMPTE2084 => Some(Self::Pq),
141 _ => None,
142 }
143 }
144}
145
146impl ColorEncoding {
147 pub fn from_v4l2(v: u32) -> Option<Self> {
152 match v {
153 V4L2_YCBCR_ENC_601 => Some(Self::Bt601),
154 V4L2_YCBCR_ENC_709 => Some(Self::Bt709),
155 V4L2_YCBCR_ENC_BT2020 => Some(Self::Bt2020),
156 _ => None,
157 }
158 }
159}
160
161impl ColorRange {
162 pub fn from_v4l2(v: u32) -> Option<Self> {
167 match v {
168 V4L2_QUANTIZATION_FULL_RANGE => Some(Self::Full),
169 V4L2_QUANTIZATION_LIM_RANGE => Some(Self::Limited),
170 _ => None,
171 }
172 }
173}
174
175macro_rules! display_via_as_str {
176 ($($t:ty),*) => {$(
177 impl fmt::Display for $t {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 f.write_str(self.as_str())
180 }
181 }
182 )*};
183}
184display_via_as_str!(ColorSpace, ColorTransfer, ColorEncoding, ColorRange);
185
186#[derive(Debug, Clone, Copy, PartialEq)]
193pub struct MatrixWeights {
194 pub kr: f64,
195 pub kb: f64,
196}
197
198impl MatrixWeights {
199 pub fn kg(self) -> f64 {
201 1.0 - self.kr - self.kb
202 }
203}
204
205impl ColorEncoding {
206 pub fn luma_weights(self) -> MatrixWeights {
209 match self {
210 Self::Bt601 => MatrixWeights {
211 kr: 0.299,
212 kb: 0.114,
213 },
214 Self::Bt709 => MatrixWeights {
215 kr: 0.2126,
216 kb: 0.0722,
217 },
218 Self::Bt2020 => MatrixWeights {
219 kr: 0.2627,
220 kb: 0.0593,
221 },
222 }
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq)]
230pub struct RangeScaling {
231 pub y_offset: f64,
233 pub y_swing: f64,
235 pub c_swing: f64,
237}
238
239impl ColorRange {
240 pub fn scaling(self) -> RangeScaling {
244 match self {
245 Self::Full => RangeScaling {
246 y_offset: 0.0,
247 y_swing: 255.0,
248 c_swing: 255.0,
249 },
250 Self::Limited => RangeScaling {
251 y_offset: 16.0,
252 y_swing: 219.0,
253 c_swing: 224.0,
254 },
255 }
256 }
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
263pub struct Colorimetry {
264 pub space: Option<ColorSpace>,
265 pub transfer: Option<ColorTransfer>,
266 pub encoding: Option<ColorEncoding>,
267 pub range: Option<ColorRange>,
268}
269
270impl Colorimetry {
271 pub fn jfif() -> Self {
274 Self {
275 space: Some(ColorSpace::Srgb),
276 transfer: Some(ColorTransfer::Srgb),
277 encoding: Some(ColorEncoding::Bt601),
278 range: Some(ColorRange::Full),
279 }
280 }
281
282 pub fn from_v4l2(
303 colorspace: u32,
304 xfer: u32,
305 ycbcr_enc: u32,
306 quant: u32,
307 is_rgb_or_hsv: bool,
308 ) -> Self {
309 let encoding = ColorEncoding::from_v4l2(ycbcr_enc).or_else(|| {
310 if ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT {
311 Self::default_encoding_for_colorspace(colorspace)
312 } else {
313 None
314 }
315 });
316 let range = ColorRange::from_v4l2(quant).or_else(|| {
317 if quant == V4L2_QUANTIZATION_DEFAULT {
318 Self::default_range(is_rgb_or_hsv, colorspace)
319 } else {
320 None
321 }
322 });
323 Self {
324 space: ColorSpace::from_v4l2(colorspace),
325 transfer: ColorTransfer::from_v4l2(xfer),
326 encoding,
327 range,
328 }
329 }
330
331 fn default_encoding_for_colorspace(colorspace: u32) -> Option<ColorEncoding> {
336 match colorspace {
337 V4L2_COLORSPACE_REC709 => Some(ColorEncoding::Bt709),
338 V4L2_COLORSPACE_BT2020 => Some(ColorEncoding::Bt2020),
339 V4L2_COLORSPACE_SMPTE170M
340 | V4L2_COLORSPACE_470_SYSTEM_M
341 | V4L2_COLORSPACE_470_SYSTEM_BG
342 | V4L2_COLORSPACE_JPEG
343 | V4L2_COLORSPACE_SRGB => Some(ColorEncoding::Bt601),
344 _ => None,
345 }
346 }
347
348 fn default_range(is_rgb_or_hsv: bool, colorspace: u32) -> Option<ColorRange> {
354 if is_rgb_or_hsv {
355 return Some(ColorRange::Full);
356 }
357 match colorspace {
358 V4L2_COLORSPACE_JPEG => Some(ColorRange::Full),
359 V4L2_COLORSPACE_SMPTE170M
360 | V4L2_COLORSPACE_REC709
361 | V4L2_COLORSPACE_BT2020
362 | V4L2_COLORSPACE_470_SYSTEM_M
363 | V4L2_COLORSPACE_470_SYSTEM_BG
364 | V4L2_COLORSPACE_SRGB => Some(ColorRange::Limited),
365 _ => None,
366 }
367 }
368
369 pub fn with_space(mut self, s: ColorSpace) -> Self {
371 self.space = Some(s);
372 self
373 }
374
375 pub fn with_transfer(mut self, t: ColorTransfer) -> Self {
377 self.transfer = Some(t);
378 self
379 }
380
381 pub fn with_encoding(mut self, e: ColorEncoding) -> Self {
383 self.encoding = Some(e);
384 self
385 }
386
387 pub fn with_range(mut self, r: ColorRange) -> Self {
389 self.range = Some(r);
390 self
391 }
392
393 pub fn pack(&self) -> u32 {
403 let space = self.space.map_or(0, ColorSpace::code);
404 let transfer = self.transfer.map_or(0, ColorTransfer::code);
405 let encoding = self.encoding.map_or(0, ColorEncoding::code);
406 let range = self.range.map_or(0, ColorRange::code);
407 space | (transfer << 8) | (encoding << 16) | (range << 24)
408 }
409
410 pub fn unpack(bits: u32) -> Self {
419 let space = ColorSpace::from_code(bits & 0xFF);
420 let transfer = ColorTransfer::from_code((bits >> 8) & 0xFF);
421 let encoding = ColorEncoding::from_code((bits >> 16) & 0xFF);
422 let range = ColorRange::from_code((bits >> 24) & 0xFF);
423 Colorimetry {
424 space,
425 transfer,
426 encoding,
427 range,
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn from_v4l2_known_and_default() {
438 assert_eq!(ColorEncoding::from_v4l2(1), Some(ColorEncoding::Bt601));
439 assert_eq!(ColorEncoding::from_v4l2(2), Some(ColorEncoding::Bt709));
440 assert_eq!(ColorEncoding::from_v4l2(6), Some(ColorEncoding::Bt2020));
441 assert_eq!(ColorEncoding::from_v4l2(0), None); assert_eq!(ColorEncoding::from_v4l2(3), None); assert_eq!(ColorRange::from_v4l2(1), Some(ColorRange::Full));
444 assert_eq!(ColorRange::from_v4l2(2), Some(ColorRange::Limited));
445 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 {
464 assert_ne!(ColorTransfer::from_v4l2(v), Some(ColorTransfer::Hlg));
465 }
466 }
467
468 #[test]
469 fn jfif_is_bt601_full_srgb() {
470 let c = Colorimetry::jfif();
471 assert_eq!(c.space, Some(ColorSpace::Srgb));
472 assert_eq!(c.transfer, Some(ColorTransfer::Srgb));
473 assert_eq!(c.encoding, Some(ColorEncoding::Bt601));
474 assert_eq!(c.range, Some(ColorRange::Full));
475 }
476
477 #[test]
478 fn from_v4l2_struct_maps_all_axes_and_unknown_to_none() {
479 let c = Colorimetry::from_v4l2(3, 1, 2, 1, false); assert_eq!(c.space, Some(ColorSpace::Bt709));
481 assert_eq!(c.transfer, Some(ColorTransfer::Bt709));
482 assert_eq!(c.encoding, Some(ColorEncoding::Bt709));
483 assert_eq!(c.range, Some(ColorRange::Full));
484 let d = Colorimetry::from_v4l2(0, 0, 0, 0, false); assert_eq!(d, Colorimetry::default()); }
487
488 #[test]
489 fn from_v4l2_default_enc_quant_derive_from_colorspace() {
490 let jpeg = Colorimetry::from_v4l2(7, 0, 0, 0, false);
494 assert_eq!(jpeg.encoding, Some(ColorEncoding::Bt601));
495 assert_eq!(jpeg.range, Some(ColorRange::Full));
496
497 let rec709 = Colorimetry::from_v4l2(3, 0, 0, 0, false);
499 assert_eq!(rec709.encoding, Some(ColorEncoding::Bt709));
500 assert_eq!(rec709.range, Some(ColorRange::Limited));
501
502 let explicit = Colorimetry::from_v4l2(7, 0, 2, 2, false); assert_eq!(explicit.encoding, Some(ColorEncoding::Bt709));
505 assert_eq!(explicit.range, Some(ColorRange::Limited));
506
507 let unknown_enc = Colorimetry::from_v4l2(7, 0, 99, 0, false);
509 assert_eq!(unknown_enc.encoding, None);
510 assert_eq!(unknown_enc.range, Some(ColorRange::Full)); }
512
513 #[test]
514 fn luma_weights_are_the_canonical_bt_constants() {
515 assert_eq!(
516 ColorEncoding::Bt601.luma_weights(),
517 MatrixWeights {
518 kr: 0.299,
519 kb: 0.114
520 }
521 );
522 assert_eq!(
523 ColorEncoding::Bt709.luma_weights(),
524 MatrixWeights {
525 kr: 0.2126,
526 kb: 0.0722
527 }
528 );
529 assert_eq!(
530 ColorEncoding::Bt2020.luma_weights(),
531 MatrixWeights {
532 kr: 0.2627,
533 kb: 0.0593
534 }
535 );
536 assert!((ColorEncoding::Bt709.luma_weights().kg() - 0.7152).abs() < 1e-9);
538 }
539
540 #[test]
541 fn range_scaling_is_full_or_studio_swing() {
542 let full = ColorRange::Full.scaling();
543 assert_eq!(
544 full,
545 RangeScaling {
546 y_offset: 0.0,
547 y_swing: 255.0,
548 c_swing: 255.0
549 }
550 );
551 let limited = ColorRange::Limited.scaling();
552 assert_eq!(
553 limited,
554 RangeScaling {
555 y_offset: 16.0,
556 y_swing: 219.0,
557 c_swing: 224.0
558 }
559 );
560 }
561
562 #[test]
563 fn as_str_and_from_str_code_are_inverses_for_every_variant() {
564 for v in [
567 ColorSpace::Bt709,
568 ColorSpace::Bt2020,
569 ColorSpace::Srgb,
570 ColorSpace::Smpte170m,
571 ] {
572 assert_eq!(ColorSpace::from_str_code(v.as_str()), Some(v), "{v:?}");
573 }
574 for v in [
575 ColorTransfer::Bt709,
576 ColorTransfer::Srgb,
577 ColorTransfer::Pq,
578 ColorTransfer::Hlg,
579 ColorTransfer::Linear,
580 ] {
581 assert_eq!(ColorTransfer::from_str_code(v.as_str()), Some(v), "{v:?}");
582 }
583 for v in [
584 ColorEncoding::Bt601,
585 ColorEncoding::Bt709,
586 ColorEncoding::Bt2020,
587 ] {
588 assert_eq!(ColorEncoding::from_str_code(v.as_str()), Some(v), "{v:?}");
589 }
590 for v in [ColorRange::Full, ColorRange::Limited] {
591 assert_eq!(ColorRange::from_str_code(v.as_str()), Some(v), "{v:?}");
592 }
593 }
594
595 #[test]
596 fn an_unknown_or_empty_label_is_unspecified_not_an_error() {
597 assert_eq!(ColorSpace::from_str_code(""), None);
598 assert_eq!(ColorSpace::from_str_code("not-a-colour-space"), None);
599 assert_eq!(
600 ColorRange::from_str_code("FULL"),
601 None,
602 "labels are case-sensitive"
603 );
604 }
605
606 #[test]
607 fn as_str_matches_schema() {
608 assert_eq!(ColorEncoding::Bt601.as_str(), "bt601");
609 assert_eq!(ColorRange::Full.as_str(), "full");
610 assert_eq!(ColorSpace::Smpte170m.as_str(), "smpte170m");
611 assert_eq!(ColorTransfer::Pq.as_str(), "pq");
612 assert_eq!(ColorTransfer::Hlg.as_str(), "hlg");
613 }
614
615 #[test]
616 fn pack_unpack_round_trips() {
617 assert_eq!(
622 Colorimetry::unpack(Colorimetry::default().pack()),
623 Colorimetry::default()
624 );
625 assert_eq!(Colorimetry::default().pack(), 0);
626
627 let jfif = Colorimetry::jfif();
628 assert_eq!(Colorimetry::unpack(jfif.pack()), jfif);
629
630 let full = Colorimetry {
631 space: Some(ColorSpace::Bt2020),
632 transfer: Some(ColorTransfer::Hlg),
633 encoding: Some(ColorEncoding::Bt2020),
634 range: Some(ColorRange::Limited),
635 };
636 assert_eq!(Colorimetry::unpack(full.pack()), full);
637 }
638
639 #[test]
640 fn unpack_maps_unrecognised_codes_to_none() {
641 let bits = 0xFFu32 | (0xFFu32 << 8) | (0xFFu32 << 16) | (0xFFu32 << 24);
645 assert_eq!(Colorimetry::unpack(bits), Colorimetry::default());
646 }
647
648 const SPACES: [(ColorSpace, u32); 4] = [
649 (ColorSpace::Bt709, 1),
650 (ColorSpace::Bt2020, 2),
651 (ColorSpace::Srgb, 3),
652 (ColorSpace::Smpte170m, 4),
653 ];
654 const TRANSFERS: [(ColorTransfer, u32); 5] = [
655 (ColorTransfer::Bt709, 1),
656 (ColorTransfer::Srgb, 2),
657 (ColorTransfer::Pq, 3),
658 (ColorTransfer::Hlg, 4),
659 (ColorTransfer::Linear, 5),
660 ];
661 const ENCODINGS: [(ColorEncoding, u32); 3] = [
662 (ColorEncoding::Bt601, 1),
663 (ColorEncoding::Bt709, 2),
664 (ColorEncoding::Bt2020, 3),
665 ];
666 const RANGES: [(ColorRange, u32); 2] = [(ColorRange::Full, 1), (ColorRange::Limited, 2)];
667
668 #[test]
669 fn pack_places_each_axis_code_in_its_own_byte() {
670 assert_eq!(SPACES.len(), ColorSpace::all().len());
673 assert_eq!(TRANSFERS.len(), ColorTransfer::all().len());
674 assert_eq!(ENCODINGS.len(), ColorEncoding::all().len());
675 assert_eq!(RANGES.len(), ColorRange::all().len());
676 for (s, code) in SPACES {
677 let c = Colorimetry::default().with_space(s);
678 assert_eq!(c.pack(), code, "{s:?}");
679 assert_eq!(Colorimetry::unpack(code), c, "{s:?}");
680 }
681 for (t, code) in TRANSFERS {
682 let c = Colorimetry::default().with_transfer(t);
683 assert_eq!(c.pack(), code << 8, "{t:?}");
684 assert_eq!(Colorimetry::unpack(code << 8), c, "{t:?}");
685 }
686 for (e, code) in ENCODINGS {
687 let c = Colorimetry::default().with_encoding(e);
688 assert_eq!(c.pack(), code << 16, "{e:?}");
689 assert_eq!(Colorimetry::unpack(code << 16), c, "{e:?}");
690 }
691 for (r, code) in RANGES {
692 let c = Colorimetry::default().with_range(r);
693 assert_eq!(c.pack(), code << 24, "{r:?}");
694 assert_eq!(Colorimetry::unpack(code << 24), c, "{r:?}");
695 }
696 }
697
698 #[test]
699 fn unpack_decodes_a_fully_populated_word() {
700 assert_eq!(
701 Colorimetry::unpack(0x02_02_05_04),
702 Colorimetry {
703 space: Some(ColorSpace::Smpte170m),
704 transfer: Some(ColorTransfer::Linear),
705 encoding: Some(ColorEncoding::Bt709),
706 range: Some(ColorRange::Limited),
707 }
708 );
709 }
710
711 #[test]
712 fn every_axis_combination_round_trips_through_pack() {
713 for (s, _) in SPACES {
714 for (t, _) in TRANSFERS {
715 for (e, _) in ENCODINGS {
716 for (r, _) in RANGES {
717 let c = Colorimetry {
718 space: Some(s),
719 transfer: Some(t),
720 encoding: Some(e),
721 range: Some(r),
722 };
723 assert_eq!(Colorimetry::unpack(c.pack()), c);
724 }
725 }
726 }
727 }
728 }
729
730 #[test]
731 fn builders_set_exactly_their_own_axis() {
732 let c = Colorimetry::default()
733 .with_space(ColorSpace::Bt2020)
734 .with_transfer(ColorTransfer::Pq)
735 .with_encoding(ColorEncoding::Bt2020)
736 .with_range(ColorRange::Full);
737 assert_eq!(
738 c,
739 Colorimetry {
740 space: Some(ColorSpace::Bt2020),
741 transfer: Some(ColorTransfer::Pq),
742 encoding: Some(ColorEncoding::Bt2020),
743 range: Some(ColorRange::Full),
744 }
745 );
746 let base = Colorimetry::jfif();
748 assert_eq!(
749 base.with_space(ColorSpace::Bt709),
750 Colorimetry {
751 space: Some(ColorSpace::Bt709),
752 ..base
753 }
754 );
755 assert_eq!(
756 base.with_transfer(ColorTransfer::Hlg),
757 Colorimetry {
758 transfer: Some(ColorTransfer::Hlg),
759 ..base
760 }
761 );
762 assert_eq!(
763 base.with_encoding(ColorEncoding::Bt709),
764 Colorimetry {
765 encoding: Some(ColorEncoding::Bt709),
766 ..base
767 }
768 );
769 assert_eq!(
770 base.with_range(ColorRange::Limited),
771 Colorimetry {
772 range: Some(ColorRange::Limited),
773 ..base
774 }
775 );
776 }
777
778 #[test]
779 fn from_v4l2_derives_encoding_and_range_for_every_recognised_yuv_colorspace() {
780 use ColorEncoding as E;
781 use ColorRange as R;
782 let table = [
783 (V4L2_COLORSPACE_SMPTE170M, E::Bt601, R::Limited),
784 (V4L2_COLORSPACE_REC709, E::Bt709, R::Limited),
785 (V4L2_COLORSPACE_470_SYSTEM_M, E::Bt601, R::Limited),
786 (V4L2_COLORSPACE_470_SYSTEM_BG, E::Bt601, R::Limited),
787 (V4L2_COLORSPACE_JPEG, E::Bt601, R::Full),
788 (V4L2_COLORSPACE_SRGB, E::Bt601, R::Limited),
789 (V4L2_COLORSPACE_BT2020, E::Bt2020, R::Limited),
790 ];
791 for (colorspace, encoding, range) in table {
792 let c = Colorimetry::from_v4l2(colorspace, 0, 0, 0, false);
793 assert_eq!(c.encoding, Some(encoding), "colorspace {colorspace}");
794 assert_eq!(c.range, Some(range), "colorspace {colorspace}");
795 }
796 for colorspace in [0, 2, 4, 9, 11, 99] {
798 let c = Colorimetry::from_v4l2(colorspace, 0, 0, 0, false);
799 assert_eq!(
800 (c.encoding, c.range),
801 (None, None),
802 "colorspace {colorspace}"
803 );
804 }
805 }
806
807 const ALL_V4L2_COLORSPACES: [u32; 14] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99];
810
811 fn kernel_default_range(is_rgb_or_hsv: bool, colorspace: u32) -> ColorRange {
814 if is_rgb_or_hsv || colorspace == V4L2_COLORSPACE_JPEG {
815 ColorRange::Full
816 } else {
817 ColorRange::Limited
818 }
819 }
820
821 #[test]
822 fn from_v4l2_quantization_matches_the_kernel_for_every_colorspace_format_and_quant() {
823 for colorspace in ALL_V4L2_COLORSPACES {
824 let recognised = ColorSpace::from_v4l2(colorspace).is_some();
825 for is_rgb_or_hsv in [false, true] {
826 for quant in [
827 V4L2_QUANTIZATION_DEFAULT,
828 V4L2_QUANTIZATION_FULL_RANGE,
829 V4L2_QUANTIZATION_LIM_RANGE,
830 99,
831 ] {
832 let range =
833 Colorimetry::from_v4l2(colorspace, 0, 0, quant, is_rgb_or_hsv).range;
834 let expected = match quant {
835 V4L2_QUANTIZATION_FULL_RANGE => Some(ColorRange::Full),
836 V4L2_QUANTIZATION_LIM_RANGE => Some(ColorRange::Limited),
837 V4L2_QUANTIZATION_DEFAULT if is_rgb_or_hsv || recognised => {
838 Some(kernel_default_range(is_rgb_or_hsv, colorspace))
839 }
840 _ => None,
841 };
842 assert_eq!(
843 range, expected,
844 "colorspace {colorspace}, rgb/hsv {is_rgb_or_hsv}, quant {quant}"
845 );
846 }
847 }
848 }
849 }
850
851 #[test]
852 fn rgb_default_quantization_is_full_and_yuv_follows_the_colorspace() {
853 let range = |colorspace, is_rgb_or_hsv| {
854 Colorimetry::from_v4l2(colorspace, 0, 0, V4L2_QUANTIZATION_DEFAULT, is_rgb_or_hsv).range
855 };
856 assert_eq!(range(V4L2_COLORSPACE_SRGB, true), Some(ColorRange::Full));
857 assert_eq!(
858 range(V4L2_COLORSPACE_SRGB, false),
859 Some(ColorRange::Limited)
860 );
861 assert_eq!(range(V4L2_COLORSPACE_JPEG, false), Some(ColorRange::Full));
862 assert_eq!(range(0, true), Some(ColorRange::Full));
865 assert_eq!(range(V4L2_COLORSPACE_REC709, true), Some(ColorRange::Full));
866 }
867
868 #[test]
869 fn from_v4l2_encoding_matches_the_kernel_for_every_recognised_colorspace() {
870 for colorspace in ALL_V4L2_COLORSPACES {
874 for is_rgb_or_hsv in [false, true] {
875 let encoding = Colorimetry::from_v4l2(colorspace, 0, 0, 0, is_rgb_or_hsv).encoding;
876 let expected = ColorSpace::from_v4l2(colorspace).map(|_| match colorspace {
877 V4L2_COLORSPACE_REC709 => ColorEncoding::Bt709,
878 V4L2_COLORSPACE_BT2020 => ColorEncoding::Bt2020,
879 _ => ColorEncoding::Bt601,
880 });
881 assert_eq!(encoding, expected, "colorspace {colorspace}");
882 }
883 }
884 }
885}
886
887#[cfg(all(test, target_os = "linux"))]
888mod v4l2_uapi_tests {
889 use edgefirst_v4l2::uapi;
890
891 #[test]
892 fn colorimetry_constants_match_edgefirst_v4l2() {
893 assert_eq!(
894 super::V4L2_COLORSPACE_SMPTE170M,
895 uapi::V4L2_COLORSPACE_SMPTE170M
896 );
897 assert_eq!(super::V4L2_COLORSPACE_REC709, uapi::V4L2_COLORSPACE_REC709);
898 assert_eq!(
899 super::V4L2_COLORSPACE_470_SYSTEM_M,
900 uapi::V4L2_COLORSPACE_470_SYSTEM_M
901 );
902 assert_eq!(
903 super::V4L2_COLORSPACE_470_SYSTEM_BG,
904 uapi::V4L2_COLORSPACE_470_SYSTEM_BG
905 );
906 assert_eq!(super::V4L2_COLORSPACE_JPEG, uapi::V4L2_COLORSPACE_JPEG);
907 assert_eq!(super::V4L2_COLORSPACE_SRGB, uapi::V4L2_COLORSPACE_SRGB);
908 assert_eq!(super::V4L2_COLORSPACE_BT2020, uapi::V4L2_COLORSPACE_BT2020);
909
910 assert_eq!(
911 super::V4L2_XFER_FUNC_709,
912 u32::from(uapi::V4L2_XFER_FUNC_709)
913 );
914 assert_eq!(
915 super::V4L2_XFER_FUNC_SRGB,
916 u32::from(uapi::V4L2_XFER_FUNC_SRGB)
917 );
918 assert_eq!(
919 super::V4L2_XFER_FUNC_NONE,
920 u32::from(uapi::V4L2_XFER_FUNC_NONE)
921 );
922 assert_eq!(
923 super::V4L2_XFER_FUNC_SMPTE2084,
924 u32::from(uapi::V4L2_XFER_FUNC_SMPTE2084)
925 );
926
927 assert_eq!(
928 super::V4L2_YCBCR_ENC_DEFAULT,
929 u32::from(uapi::V4L2_YCBCR_ENC_DEFAULT)
930 );
931 assert_eq!(
932 super::V4L2_YCBCR_ENC_601,
933 u32::from(uapi::V4L2_YCBCR_ENC_601)
934 );
935 assert_eq!(
936 super::V4L2_YCBCR_ENC_709,
937 u32::from(uapi::V4L2_YCBCR_ENC_709)
938 );
939 assert_eq!(
940 super::V4L2_YCBCR_ENC_BT2020,
941 u32::from(uapi::V4L2_YCBCR_ENC_BT2020)
942 );
943
944 assert_eq!(
945 super::V4L2_QUANTIZATION_DEFAULT,
946 u32::from(uapi::V4L2_QUANTIZATION_DEFAULT)
947 );
948 assert_eq!(
949 super::V4L2_QUANTIZATION_FULL_RANGE,
950 u32::from(uapi::V4L2_QUANTIZATION_FULL_RANGE)
951 );
952 assert_eq!(
953 super::V4L2_QUANTIZATION_LIM_RANGE,
954 u32::from(uapi::V4L2_QUANTIZATION_LIM_RANGE)
955 );
956 }
957}