Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/
colr.rs

1use crate::*;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[non_exhaustive]
6pub enum Colr {
7    Nclx {
8        colour_primaries: u16,
9        transfer_characteristics: u16,
10        matrix_coefficients: u16,
11        full_range_flag: bool,
12    },
13    Nclc {
14        colour_primaries: u16,
15        transfer_characteristics: u16,
16        matrix_coefficients: u16,
17    },
18    Ricc {
19        profile: Vec<u8>,
20    },
21    Prof {
22        profile: Vec<u8>,
23    },
24}
25
26impl Colr {
27    pub fn new(
28        colour_primaries: u16,
29        transfer_characteristics: u16,
30        matrix_coefficients: u16,
31        full_range_flag: bool,
32    ) -> Result<Self> {
33        Ok(Colr::Nclx {
34            colour_primaries,
35            transfer_characteristics,
36            matrix_coefficients,
37            full_range_flag,
38        })
39    }
40}
41
42impl Atom for Colr {
43    const KIND: FourCC = FourCC::new(b"colr");
44
45    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
46        const NCLX: FourCC = FourCC::new(b"nclx");
47        const NCLC: FourCC = FourCC::new(b"nclc");
48        const PROF: FourCC = FourCC::new(b"prof");
49        const RICC: FourCC = FourCC::new(b"rICC");
50
51        let colour_type = FourCC::decode(buf)?;
52        match colour_type {
53            NCLX => {
54                let colour_primaries = u16::decode(buf)?;
55                let transfer_characteristics = u16::decode(buf)?;
56                let matrix_coefficients = u16::decode(buf)?;
57                let full_range_flag = u8::decode(buf)? == 0x80;
58                Ok(Colr::Nclx {
59                    colour_primaries,
60                    transfer_characteristics,
61                    matrix_coefficients,
62                    full_range_flag,
63                })
64            }
65            NCLC => {
66                let colour_primaries = u16::decode(buf)?;
67                let transfer_characteristics = u16::decode(buf)?;
68                let matrix_coefficients = u16::decode(buf)?;
69                Ok(Colr::Nclc {
70                    colour_primaries,
71                    transfer_characteristics,
72                    matrix_coefficients,
73                })
74            }
75            PROF => {
76                let profile_len = buf.remaining();
77                let profile = buf.slice(profile_len).to_vec();
78                buf.advance(profile_len);
79                Ok(Colr::Prof { profile })
80            }
81            RICC => {
82                let profile_len = buf.remaining();
83                let profile = buf.slice(profile_len).to_vec();
84                buf.advance(profile_len);
85                Ok(Colr::Ricc { profile })
86            }
87            _ => Err(Error::UnexpectedBox(colour_type)),
88        }
89    }
90
91    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
92        match self {
93            Colr::Nclx {
94                colour_primaries,
95                transfer_characteristics,
96                matrix_coefficients,
97                full_range_flag,
98            } => {
99                b"nclx".encode(buf)?;
100                colour_primaries.encode(buf)?;
101                transfer_characteristics.encode(buf)?;
102                matrix_coefficients.encode(buf)?;
103                if *full_range_flag {
104                    0x80u8.encode(buf)?;
105                } else {
106                    0x00u8.encode(buf)?;
107                }
108            }
109            Colr::Nclc {
110                colour_primaries,
111                transfer_characteristics,
112                matrix_coefficients,
113            } => {
114                b"nclc".encode(buf)?;
115                colour_primaries.encode(buf)?;
116                transfer_characteristics.encode(buf)?;
117                matrix_coefficients.encode(buf)?;
118            }
119            Colr::Ricc { profile } => {
120                b"rICC".encode(buf)?;
121                profile.encode(buf)?;
122            }
123            Colr::Prof { profile } => {
124                b"prof".encode(buf)?;
125                profile.encode(buf)?;
126            }
127        }
128        Ok(())
129    }
130}
131
132impl Default for Colr {
133    fn default() -> Self {
134        Colr::Nclx {
135            // These match MIAF defaults (ISO/IEC 23000-22:2025 7.3.6.4), probably a reasonable set
136            colour_primaries: 1,
137            transfer_characteristics: 13,
138            matrix_coefficients: 5,
139            full_range_flag: true,
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_nclx_decode() {
150        const ENCODED: &[u8] = &[
151            0x00, 0x00, 0x00, 0x13, 0x63, 0x6f, 0x6c, 0x72, 0x6e, 0x63, 0x6c, 0x78, 0x00, 0x01,
152            0x00, 0x01, 0x00, 0x01, 0x00,
153        ];
154
155        let buf = &mut std::io::Cursor::new(&ENCODED);
156
157        let colr = Colr::decode(buf).expect("failed to decode colr");
158
159        assert_eq!(
160            colr,
161            Colr::Nclx {
162                colour_primaries: 1,
163                transfer_characteristics: 1,
164                matrix_coefficients: 1,
165                full_range_flag: false
166            }
167        );
168    }
169
170    #[test]
171    fn test_prof_decode_roundtrip() {
172        // 19-byte colr: size=0x13, kind=colr, colour_type=prof, 7 bytes profile.
173        // Pre-fix this failed with UnderDecode("colr") because the prof branch
174        // borrowed the profile via buf.slice without advancing the cursor.
175        const ENCODED: &[u8] = &[
176            0x00, 0x00, 0x00, 0x13, b'c', b'o', b'l', b'r', b'p', b'r', b'o', b'f', 0x01, 0x02,
177            0x03, 0x04, 0x05, 0x06, 0x07,
178        ];
179        let buf = &mut std::io::Cursor::new(&ENCODED);
180        let colr = Colr::decode(buf).expect("failed to decode prof colr");
181        assert_eq!(
182            colr,
183            Colr::Prof {
184                profile: vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07],
185            }
186        );
187
188        let mut out = Vec::new();
189        colr.encode(&mut out).expect("encode prof colr");
190        assert_eq!(out.as_slice(), ENCODED);
191    }
192
193    #[test]
194    fn test_prof_does_not_leave_remaining_bytes_for_parent() {
195        // Pin the strict-end-check contract: after decoding a prof colr, the
196        // parent's remaining-bytes check must see an empty buffer. Pre-fix
197        // this saw `profile_len` bytes still pending and returned
198        // UnderDecode("colr") to the parent.
199        const ENCODED: &[u8] = &[
200            0x00, 0x00, 0x00, 0x10, b'c', b'o', b'l', b'r', b'p', b'r', b'o', b'f', 0xDE, 0xAD,
201            0xBE, 0xEF,
202        ];
203        let buf = &mut std::io::Cursor::new(&ENCODED);
204        Colr::decode(buf).expect("prof colr must decode without leaving trailing bytes");
205        assert_eq!(
206            Buf::remaining(buf),
207            0,
208            "parent end-check must see an empty body buffer"
209        );
210    }
211
212    #[test]
213    fn test_ricc_decode_roundtrip() {
214        // Same shape with rICC colour_type instead of prof.
215        const ENCODED: &[u8] = &[
216            0x00, 0x00, 0x00, 0x13, b'c', b'o', b'l', b'r', b'r', b'I', b'C', b'C', 0xAA, 0xBB,
217            0xCC, 0xDD, 0xEE, 0xFF, 0x00,
218        ];
219        let buf = &mut std::io::Cursor::new(&ENCODED);
220        let colr = Colr::decode(buf).expect("failed to decode rICC colr");
221        assert_eq!(
222            colr,
223            Colr::Ricc {
224                profile: vec![0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00],
225            }
226        );
227
228        let mut out = Vec::new();
229        colr.encode(&mut out).expect("encode rICC colr");
230        assert_eq!(out.as_slice(), ENCODED);
231    }
232
233    #[test]
234    fn test_nclx_encode() {
235        const ENCODED: &[u8] = &[
236            0x00, 0x00, 0x00, 0x13, 0x63, 0x6f, 0x6c, 0x72, 0x6e, 0x63, 0x6c, 0x78, 0x00, 0x01,
237            0x00, 0x0d, 0x00, 0x06, 0x80,
238        ];
239
240        let colr = Colr::Nclx {
241            colour_primaries: 1,
242            transfer_characteristics: 13,
243            matrix_coefficients: 6,
244            full_range_flag: true,
245        };
246
247        let mut buf = Vec::new();
248        colr.encode(&mut buf).unwrap();
249
250        assert_eq!(buf.as_slice(), ENCODED);
251    }
252
253    #[test]
254    fn test_nclc_decode() {
255        const ENCODED: &[u8] = &[
256            0x00, 0x00, 0x00, 0x12, 0x63, 0x6f, 0x6c, 0x72, 0x6e, 0x63, 0x6c, 0x63, 0x00, 0x01,
257            0x00, 0x0d, 0x00, 0x06,
258        ];
259
260        let buf = &mut std::io::Cursor::new(&ENCODED);
261
262        let colr = Colr::decode(buf).expect("failed to decode colr");
263
264        assert_eq!(
265            colr,
266            Colr::Nclc {
267                colour_primaries: 1,
268                transfer_characteristics: 13,
269                matrix_coefficients: 6,
270            }
271        );
272    }
273
274    #[test]
275    fn test_nclc_encode() {
276        const ENCODED: &[u8] = &[
277            0x00, 0x00, 0x00, 0x12, 0x63, 0x6f, 0x6c, 0x72, 0x6e, 0x63, 0x6c, 0x63, 0x00, 0x01,
278            0x00, 0x0d, 0x00, 0x06,
279        ];
280
281        let colr = Colr::Nclc {
282            colour_primaries: 1,
283            transfer_characteristics: 13,
284            matrix_coefficients: 6,
285        };
286
287        let mut buf = Vec::new();
288        colr.encode(&mut buf).unwrap();
289
290        assert_eq!(buf.as_slice(), ENCODED);
291    }
292}