Skip to main content

dvb_subtitle/segments/
alternative_clut.rs

1//! Alternative CLUT Segment — ETSI EN 300 743 §7.2.8, Table 31 (segment_type 0x16).
2//!
3//! Permits a CLUT to be defined in colour systems other than ITU-R BT.601.
4
5use crate::error::{Error, Result};
6use broadcast_common::{Parse, Serialize};
7
8/// The alternative_CLUT_segment segment_type.
9pub const SEGMENT_TYPE: u8 = 0x16;
10/// Header: 6 bytes.
11pub const HEADER_LEN: usize = 6;
12/// Fixed body: CLUT_id(1) + CLUT_version_number(4b)+reserved(4b) + CLUT_parameters(2) = 4 bytes.
13pub const FIXED_LEN: usize = 4;
14/// Entry for 8-bit output: 4 bytes.
15pub const ENTRY_8BIT_LEN: usize = 4;
16/// Entry for 10-bit output: 5 bytes.
17pub const ENTRY_10BIT_LEN: usize = 5;
18
19/// CLUT parameters as defined in Table 32.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22pub struct ClutParameters {
23    /// Maximum number of CLUT entries (0 = 256).
24    pub entry_max_number: u8,
25    /// Colour component type (0 = YCbCr).
26    pub colour_component_type: u8,
27    /// Output bit depth (0 = 8-bit, 1 = 10-bit).
28    pub output_bit_depth: u8,
29    /// Reserved bit in CLUT_parameters byte 0 (bit 0).
30    pub reserved: u8,
31    /// SDR/HDR dynamic range and colour gamut as per Table 34.
32    pub dynamic_range_and_colour_gamut: u8,
33}
34
35impl ClutParameters {
36    /// Parse CLUT_parameters from 2 raw bytes.
37    pub fn parse(bytes: &[u8]) -> Result<Self> {
38        if bytes.len() < 2 {
39            return Err(Error::BufferTooShort {
40                need: 2,
41                have: bytes.len(),
42                what: "CLUT_parameters",
43            });
44        }
45        let entry_max_number = bytes[0] >> 6;
46        let colour_component_type = (bytes[0] >> 4) & 0x03;
47        let output_bit_depth = (bytes[0] >> 1) & 0x07;
48        let reserved = bytes[0] & 0x01;
49        Ok(ClutParameters {
50            entry_max_number,
51            colour_component_type,
52            output_bit_depth,
53            reserved,
54            dynamic_range_and_colour_gamut: bytes[1],
55        })
56    }
57
58    fn serialize_into(&self, buf: &mut [u8]) {
59        buf[0] = (self.entry_max_number << 6)
60            | (self.colour_component_type << 4)
61            | (self.output_bit_depth << 1)
62            | (self.reserved & 0x01);
63        buf[1] = self.dynamic_range_and_colour_gamut;
64    }
65}
66
67/// Output bit depth as defined in Table 33.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize))]
70#[repr(u8)]
71#[non_exhaustive]
72pub enum OutputBitDepth {
73    /// 8-bit per component.
74    Bit8 = 0x00,
75    /// 10-bit per component.
76    Bit10 = 0x01,
77    /// Reserved.
78    Reserved(u8),
79}
80
81impl OutputBitDepth {
82    /// Human-readable name for this bit depth.
83    #[must_use]
84    pub fn name(&self) -> &'static str {
85        match self {
86            Self::Bit8 => "8-bit",
87            Self::Bit10 => "10-bit",
88            Self::Reserved(_) => "reserved",
89        }
90    }
91}
92
93broadcast_common::impl_spec_display!(OutputBitDepth, Reserved);
94
95/// Dynamic range and colour gamut as defined in Table 34.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize))]
98#[repr(u8)]
99#[non_exhaustive]
100pub enum DynamicRangeColourGamut {
101    /// SDR; ITU-R BT.709.
102    SdrBt709 = 0x00,
103    /// SDR; ITU-R BT.2020-2.
104    SdrBt2020 = 0x01,
105    /// HDR; ITU-R BT.2100-1 PQ.
106    HdrBt2100Pq = 0x02,
107    /// HDR; ITU-R BT.2100-1 HLG.
108    HdrBt2100Hlg = 0x03,
109    /// Reserved.
110    Reserved(u8),
111}
112
113impl DynamicRangeColourGamut {
114    /// Human-readable name for this dynamic range and colour gamut.
115    #[must_use]
116    pub fn name(&self) -> &'static str {
117        match self {
118            Self::SdrBt709 => "SDR_BT.709",
119            Self::SdrBt2020 => "SDR_BT.2020",
120            Self::HdrBt2100Pq => "HDR_BT.2100_PQ",
121            Self::HdrBt2100Hlg => "HDR_BT.2100_HLG",
122            Self::Reserved(_) => "reserved",
123        }
124    }
125}
126
127broadcast_common::impl_spec_display!(DynamicRangeColourGamut, Reserved);
128
129/// A single alternative CLUT entry.
130#[derive(Debug, Clone, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132pub struct AlternativeClutEntry {
133    /// Luma value (8 or 10 bit).
134    pub luma_value: u16,
135    /// Chroma1 (Cb) value (8 or 10 bit).
136    pub chroma1_value: u16,
137    /// Chroma2 (Cr) value (8 or 10 bit).
138    pub chroma2_value: u16,
139    /// Transparency value (8 or 10 bit).
140    pub t_value: u16,
141}
142
143impl AlternativeClutEntry {
144    fn serialized_len(output_bit_depth: u8) -> usize {
145        match output_bit_depth {
146            0 => ENTRY_8BIT_LEN,
147            1 => ENTRY_10BIT_LEN,
148            _ => ENTRY_8BIT_LEN,
149        }
150    }
151
152    fn serialize_into(&self, buf: &mut [u8], output_bit_depth: u8) {
153        if output_bit_depth == 1 {
154            let v0 = self.luma_value;
155            let v1 = self.chroma1_value;
156            let v2 = self.chroma2_value;
157            let v3 = self.t_value;
158            buf[0] = (v0 >> 2) as u8;
159            buf[1] = (((v0 & 0x03) << 6) | ((v1 & 0x3FC) >> 4)) as u8;
160            buf[2] = (((v1 & 0x0F) << 4) | ((v2 & 0x3F0) >> 6)) as u8;
161            buf[3] = (((v2 & 0x3F) << 2) | ((v3 & 0x300) >> 8)) as u8;
162            buf[4] = (v3 & 0xFF) as u8;
163        } else {
164            buf[0] = self.luma_value as u8;
165            buf[1] = self.chroma1_value as u8;
166            buf[2] = self.chroma2_value as u8;
167            buf[3] = self.t_value as u8;
168        }
169    }
170
171    fn parse(bytes: &[u8], output_bit_depth: u8) -> Result<Self> {
172        let entry_len = Self::serialized_len(output_bit_depth);
173        if bytes.len() < entry_len {
174            return Err(Error::BufferTooShort {
175                need: entry_len,
176                have: bytes.len(),
177                what: "alternative_CLUT_entry",
178            });
179        }
180        let (luma, c1, c2, t) = if output_bit_depth == 1 {
181            let v0 = ((bytes[0] as u16) << 2) | ((bytes[1] as u16) >> 6);
182            let v1 = (((bytes[1] as u16) & 0x3F) << 4) | ((bytes[2] as u16) >> 4);
183            let v2 = (((bytes[2] as u16) & 0x0F) << 6) | ((bytes[3] as u16) >> 2);
184            let v3 = (((bytes[3] as u16) & 0x03) << 8) | (bytes[4] as u16);
185            (v0, v1, v2, v3)
186        } else {
187            (
188                bytes[0] as u16,
189                bytes[1] as u16,
190                bytes[2] as u16,
191                bytes[3] as u16,
192            )
193        };
194        Ok(AlternativeClutEntry {
195            luma_value: luma,
196            chroma1_value: c1,
197            chroma2_value: c2,
198            t_value: t,
199        })
200    }
201}
202
203/// Alternative CLUT Segment.
204#[derive(Debug, Clone, PartialEq, Eq)]
205#[cfg_attr(feature = "serde", derive(serde::Serialize))]
206pub struct AlternativeClutSegment {
207    /// The page_id from the segment header.
208    pub page_id: u16,
209    /// CLUT family identifier.
210    pub clut_id: u8,
211    /// CLUT version number (modulo 16).
212    pub clut_version_number: u8,
213    /// Reserved bits in body byte 1 (bits `[3:0]`).
214    pub reserved: u8,
215    /// CLUT parameters.
216    pub clut_parameters: ClutParameters,
217    /// CLUT entries.
218    pub entries: alloc::vec::Vec<AlternativeClutEntry>,
219}
220
221impl<'a> Parse<'a> for AlternativeClutSegment {
222    type Error = Error;
223
224    fn parse(bytes: &'a [u8]) -> Result<Self> {
225        if bytes.len() < HEADER_LEN + FIXED_LEN {
226            return Err(Error::BufferTooShort {
227                need: HEADER_LEN + FIXED_LEN,
228                have: bytes.len(),
229                what: "alternative_CLUT_segment",
230            });
231        }
232        if bytes[1] != SEGMENT_TYPE {
233            return Err(Error::UnknownSegmentType(bytes[1]));
234        }
235        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
236        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
237        let total = HEADER_LEN + segment_length;
238        if bytes.len() < total {
239            return Err(Error::BufferTooShort {
240                need: total,
241                have: bytes.len(),
242                what: "alternative_CLUT_segment data",
243            });
244        }
245        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
246        if body.len() < FIXED_LEN {
247            return Err(Error::BufferTooShort {
248                need: FIXED_LEN,
249                have: body.len(),
250                what: "alternative_CLUT_segment body",
251            });
252        }
253        let clut_id = body[0];
254        let clut_version_number = body[1] >> 4;
255        let reserved = body[1] & 0x0F;
256        let clut_parameters = ClutParameters::parse(&body[2..4])?;
257        let output_bit_depth = clut_parameters.output_bit_depth;
258        let entry_len = AlternativeClutEntry::serialized_len(output_bit_depth);
259
260        let entry_data = &body[FIXED_LEN..];
261        let num_entries = entry_data.len() / entry_len;
262        let mut entries = alloc::vec::Vec::with_capacity(num_entries);
263        for i in 0..num_entries {
264            let off = i * entry_len;
265            entries.push(AlternativeClutEntry::parse(
266                &entry_data[off..off + entry_len],
267                output_bit_depth,
268            )?);
269        }
270
271        Ok(AlternativeClutSegment {
272            page_id,
273            clut_id,
274            clut_version_number,
275            reserved,
276            clut_parameters,
277            entries,
278        })
279    }
280}
281
282impl Serialize for AlternativeClutSegment {
283    type Error = Error;
284
285    fn serialized_len(&self) -> usize {
286        let entry_len = AlternativeClutEntry::serialized_len(self.clut_parameters.output_bit_depth);
287        HEADER_LEN + FIXED_LEN + self.entries.len() * entry_len
288    }
289
290    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
291        let len = self.serialized_len();
292        if buf.len() < len {
293            return Err(Error::BufferTooShort {
294                need: len,
295                have: buf.len(),
296                what: "alternative_CLUT_segment serialize",
297            });
298        }
299        buf[0] = 0x0F;
300        buf[1] = SEGMENT_TYPE;
301        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
302        let seg_len = (len - HEADER_LEN) as u16;
303        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
304
305        buf[6] = self.clut_id;
306        buf[7] = (self.clut_version_number << 4) | (self.reserved & 0x0F);
307        self.clut_parameters.serialize_into(&mut buf[8..10]);
308
309        let entry_len = AlternativeClutEntry::serialized_len(self.clut_parameters.output_bit_depth);
310        for (i, entry) in self.entries.iter().enumerate() {
311            let off = HEADER_LEN + FIXED_LEN + i * entry_len;
312            entry.serialize_into(
313                &mut buf[off..off + entry_len],
314                self.clut_parameters.output_bit_depth,
315            );
316        }
317        Ok(len)
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324    use broadcast_common::{Parse, Serialize};
325
326    #[test]
327    fn round_trip_8bit() {
328        let bytes = [
329            0x0F, 0x16, 0x00, 0x01, 0x00, 0x08, 0x03, 0x10, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80,
330        ];
331        let seg = AlternativeClutSegment::parse(&bytes).unwrap();
332        assert_eq!(seg.clut_id, 3);
333        assert_eq!(seg.entries.len(), 1);
334        assert_eq!(seg.entries[0].luma_value, 128);
335        let out = seg.to_bytes();
336        assert_eq!(out, bytes);
337
338        // Biting test
339        let mut seg2 = seg.clone();
340        seg2.entries[0].luma_value = 200;
341        let out2 = seg2.to_bytes();
342        assert_ne!(out2, bytes);
343        let reparse = AlternativeClutSegment::parse(&out2).unwrap();
344        assert_eq!(reparse.entries[0].luma_value, 200);
345    }
346
347    #[test]
348    fn round_trip_10bit() {
349        let bytes = [
350            0x0F, 0x16, 0x00, 0x01, 0x00, 0x09, 0x03, 0x10, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00,
351            0x00,
352        ];
353        let seg = AlternativeClutSegment::parse(&bytes).unwrap();
354        assert_eq!(seg.clut_parameters.output_bit_depth, 1);
355        assert_eq!(seg.entries.len(), 1);
356        let out = seg.to_bytes();
357        assert_eq!(out, bytes);
358
359        // Biting test
360        let mut seg2 = seg.clone();
361        seg2.clut_id = 7;
362        let out2 = seg2.to_bytes();
363        assert_ne!(out2, bytes);
364        let reparse = AlternativeClutSegment::parse(&out2).unwrap();
365        assert_eq!(reparse.clut_id, 7);
366    }
367}