moxcms/
cicp.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1.  Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3.  Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use crate::gamma::{
30    bt1361_to_linear, hlg_to_linear, iec61966_to_linear, log100_sqrt10_to_linear, log100_to_linear,
31    pq_to_linear, smpte240_to_linear, smpte428_to_linear,
32};
33use crate::{
34    Chromaticity, ColorProfile, Matrix3d, Matrix3f, XyYRepresentable,
35    err::CmsError,
36    trc::{ToneReprCurve, build_trc_table, curve_from_gamma},
37};
38use std::convert::TryFrom;
39
40/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 2
41/// Values 0, 3, 13–21, 23–255 are all reserved so all map to the same variant
42#[derive(Clone, Copy, Debug, PartialEq)]
43pub enum CicpColorPrimaries {
44    /// For future use by ITU-T | ISO/IEC
45    Reserved,
46    /// Rec. ITU-R BT.709-6<br />
47    /// Rec. ITU-R BT.1361-0 conventional colour gamut system and extended colour gamut system (historical)<br />
48    /// IEC 61966-2-1 sRGB or sYCC IEC 61966-2-4<br />
49    /// Society of Motion Picture and Television Engineers (MPTE) RP 177 (1993) Annex B<br />
50    Bt709 = 1,
51    /// Unspecified<br />
52    /// Image characteristics are unknown or are determined by the application.
53    Unspecified = 2,
54    /// Rec. ITU-R BT.470-6 System M (historical)<br />
55    /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
56    /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
57    Bt470M = 4,
58    /// Rec. ITU-R BT.470-6 System B, G (historical) Rec. ITU-R BT.601-7 625<br />
59    /// Rec. ITU-R BT.1358-0 625 (historical)<br />
60    /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
61    Bt470Bg = 5,
62    /// Rec. ITU-R BT.601-7 525<br />
63    /// Rec. ITU-R BT.1358-1 525 or 625 (historical) Rec. ITU-R BT.1700-0 NTSC<br />
64    /// SMPTE 170M (2004)<br />
65    /// (functionally the same as the value 7)<br />
66    Bt601 = 6,
67    /// SMPTE 240M (1999) (historical) (functionally the same as the value 6)<br />
68    Smpte240 = 7,
69    /// Generic film (colour filters using Illuminant C)<br />
70    GenericFilm = 8,
71    /// Rec. ITU-R BT.2020-2<br />
72    /// Rec. ITU-R BT.2100-0<br />
73    Bt2020 = 9,
74    /// SMPTE ST 428-1<br />
75    /// (CIE 1931 XYZ as in ISO 11664-1)<br />
76    Xyz = 10,
77    /// SMPTE RP 431-2 (2011)<br />
78    Smpte431 = 11,
79    /// SMPTE EG 432-1 (2010)<br />
80    Smpte432 = 12,
81    /// EBU Tech. 3213-E (1975)<br />
82    Ebu3213 = 22,
83}
84
85impl TryFrom<u8> for CicpColorPrimaries {
86    type Error = CmsError;
87
88    #[allow(unreachable_patterns)]
89    fn try_from(value: u8) -> Result<Self, Self::Error> {
90        match value {
91            // Values 0, 3, 13–21, 23–255 are all reserved so all map to the
92            // same variant.
93            0 | 3 | 13..=21 | 23..=255 => Ok(Self::Reserved),
94            1 => Ok(Self::Bt709),
95            2 => Ok(Self::Unspecified),
96            4 => Ok(Self::Bt470M),
97            5 => Ok(Self::Bt470Bg),
98            6 => Ok(Self::Bt601),
99            7 => Ok(Self::Smpte240),
100            8 => Ok(Self::GenericFilm),
101            9 => Ok(Self::Bt2020),
102            10 => Ok(Self::Xyz),
103            11 => Ok(Self::Smpte431),
104            12 => Ok(Self::Smpte432),
105            22 => Ok(Self::Ebu3213),
106            _ => Err(CmsError::InvalidCicp),
107        }
108    }
109}
110
111#[derive(Clone, Copy, Debug)]
112#[repr(C)]
113pub struct ColorPrimaries {
114    pub red: Chromaticity,
115    pub green: Chromaticity,
116    pub blue: Chromaticity,
117}
118
119/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 2.
120impl ColorPrimaries {
121    /// [ACEScg](https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#ACEScg).
122    pub const ACES_CG: ColorPrimaries = ColorPrimaries {
123        red: Chromaticity { x: 0.713, y: 0.293 },
124        green: Chromaticity { x: 0.165, y: 0.830 },
125        blue: Chromaticity { x: 0.128, y: 0.044 },
126    };
127
128    /// [ACES2065-1](https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#ACES2065-1).
129    pub const ACES_2065_1: ColorPrimaries = ColorPrimaries {
130        red: Chromaticity {
131            x: 0.7347,
132            y: 0.2653,
133        },
134        green: Chromaticity {
135            x: 0.0000,
136            y: 1.0000,
137        },
138        blue: Chromaticity {
139            x: 0.0001,
140            y: -0.0770,
141        },
142    };
143
144    /// [Adobe RGB](https://en.wikipedia.org/wiki/Adobe_RGB_color_space) (1998).
145    pub const ADOBE_RGB: ColorPrimaries = ColorPrimaries {
146        red: Chromaticity { x: 0.64, y: 0.33 },
147        green: Chromaticity { x: 0.21, y: 0.71 },
148        blue: Chromaticity { x: 0.15, y: 0.06 },
149    };
150
151    /// [DCI P3](https://en.wikipedia.org/wiki/DCI-P3#DCI_P3).
152    ///
153    /// This is the same as [`DISPLAY_P3`](Self::DISPLAY_P3),
154    /// [`SMPTE_431`](Self::SMPTE_431) and [`SMPTE_432`](Self::SMPTE_432).
155    pub const DCI_P3: ColorPrimaries = ColorPrimaries {
156        red: Chromaticity { x: 0.680, y: 0.320 },
157        green: Chromaticity { x: 0.265, y: 0.690 },
158        blue: Chromaticity { x: 0.150, y: 0.060 },
159    };
160
161    /// [Diplay P3](https://en.wikipedia.org/wiki/DCI-P3#Display_P3).
162    ///
163    /// This is the same as [`DCI_P3`](Self::DCI_P3),
164    /// [`SMPTE_431`](Self::SMPTE_431) and [`SMPTE_432`](Self::SMPTE_432).
165    pub const DISPLAY_P3: ColorPrimaries = Self::DCI_P3;
166
167    /// SMPTE RP 431-2 (2011).
168    ///
169    /// This is the same as [`DCI_P3`](Self::DCI_P3),
170    /// [`DISPLAY_P3`](Self::DISPLAY_P3) and [`SMPTE_432`](Self::SMPTE_432).
171    pub const SMPTE_431: ColorPrimaries = Self::DCI_P3;
172
173    /// SMPTE EG 432-1 (2010).
174    ///
175    /// This is the same as [`DCI_P3`](Self::DCI_P3),
176    /// [`DISPLAY_P3`](Self::DISPLAY_P3) and [`SMPTE_431`](Self::SMPTE_431).
177    pub const SMPTE_432: ColorPrimaries = Self::DCI_P3;
178
179    /// [ProPhoto RGB](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space).
180    pub const PRO_PHOTO_RGB: ColorPrimaries = ColorPrimaries {
181        red: Chromaticity {
182            x: 0.734699,
183            y: 0.265301,
184        },
185        green: Chromaticity {
186            x: 0.159597,
187            y: 0.840403,
188        },
189        blue: Chromaticity {
190            x: 0.036598,
191            y: 0.000105,
192        },
193    };
194
195    /// Rec. ITU-R BT.709-6
196    ///
197    /// Rec. ITU-R BT.1361-0 conventional colour gamut system and extended
198    /// colour gamut system (historical).
199    ///
200    /// IEC 61966-2-1 sRGB or sYCC IEC 61966-2-4).
201    ///
202    /// Society of Motion Picture and Television Engineers (MPTE) RP 177 (1993) Annex B.
203    pub const BT_709: ColorPrimaries = ColorPrimaries {
204        red: Chromaticity { x: 0.64, y: 0.33 },
205        green: Chromaticity { x: 0.30, y: 0.60 },
206        blue: Chromaticity { x: 0.15, y: 0.06 },
207    };
208
209    /// Rec. ITU-R BT.470-6 System M (historical).
210    ///
211    /// United States National Television System Committee 1953 Recommendation
212    /// for transmission standards for color television.
213    ///
214    /// United States Federal Communications Commission (2003) Title 47 Code of
215    /// Federal Regulations 73.682 (a) (20).
216    pub const BT_470M: ColorPrimaries = ColorPrimaries {
217        red: Chromaticity { x: 0.67, y: 0.33 },
218        green: Chromaticity { x: 0.21, y: 0.71 },
219        blue: Chromaticity { x: 0.14, y: 0.08 },
220    };
221
222    /// Rec. ITU-R BT.470-6 System B, G (historical) Rec. ITU-R BT.601-7 625.
223    ///
224    /// Rec. ITU-R BT.1358-0 625 (historical).
225    /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM.
226    pub const BT_470BG: ColorPrimaries = ColorPrimaries {
227        red: Chromaticity { x: 0.64, y: 0.33 },
228        green: Chromaticity { x: 0.29, y: 0.60 },
229        blue: Chromaticity { x: 0.15, y: 0.06 },
230    };
231
232    /// Rec. ITU-R BT.601-7 525.
233    ///
234    /// Rec. ITU-R BT.1358-1 525 or 625 (historical) Rec. ITU-R BT.1700-0 NTSC.
235    ///
236    /// SMPTE 170M (2004) (functionally the same as the [`SMPTE_240`](Self::SMPTE_240)).
237    pub const BT_601: ColorPrimaries = ColorPrimaries {
238        red: Chromaticity { x: 0.630, y: 0.340 },
239        green: Chromaticity { x: 0.310, y: 0.595 },
240        blue: Chromaticity { x: 0.155, y: 0.070 },
241    };
242
243    /// SMPTE 240M (1999) (historical) (functionally the same as [`BT_601`](Self::BT_601)).
244    pub const SMPTE_240: ColorPrimaries = Self::BT_601;
245
246    /// Generic film (colour filters using Illuminant C).
247    pub const GENERIC_FILM: ColorPrimaries = ColorPrimaries {
248        red: Chromaticity { x: 0.681, y: 0.319 },
249        green: Chromaticity { x: 0.243, y: 0.692 },
250        blue: Chromaticity { x: 0.145, y: 0.049 },
251    };
252
253    /// Rec. ITU-R BT.2020-2.
254    ///
255    /// Rec. ITU-R BT.2100-0.
256    pub const BT_2020: ColorPrimaries = ColorPrimaries {
257        red: Chromaticity { x: 0.708, y: 0.292 },
258        green: Chromaticity { x: 0.170, y: 0.797 },
259        blue: Chromaticity { x: 0.131, y: 0.046 },
260    };
261
262    /// SMPTE ST 428-1 (CIE 1931 XYZ as in ISO 11664-1).
263    pub const XYZ: ColorPrimaries = ColorPrimaries {
264        red: Chromaticity { x: 1.0, y: 0.0 },
265        green: Chromaticity { x: 0.0, y: 1.0 },
266        blue: Chromaticity { x: 0.0, y: 0.0 },
267    };
268
269    /// EBU Tech. 3213-E (1975).
270    pub const EBU_3213: ColorPrimaries = ColorPrimaries {
271        red: Chromaticity { x: 0.630, y: 0.340 },
272        green: Chromaticity { x: 0.295, y: 0.605 },
273        blue: Chromaticity { x: 0.155, y: 0.077 },
274    };
275}
276
277impl ColorPrimaries {
278    /// Returns RGB -> XYZ conversion matrix
279    ///
280    /// # Arguments
281    ///
282    /// * `white_point`: [Chromaticity] or [crate::XyY] or any item conforming [XyYRepresentable]
283    ///
284    /// returns: [Matrix3d]
285    pub fn transform_to_xyz_d(self, white_point: impl XyYRepresentable) -> Matrix3d {
286        let red_xyz = self.red.to_scaled_xyzd();
287        let green_xyz = self.green.to_scaled_xyzd();
288        let blue_xyz = self.blue.to_scaled_xyzd();
289
290        let xyz_matrix = Matrix3d {
291            v: [
292                [red_xyz.x, green_xyz.x, blue_xyz.x],
293                [red_xyz.y, green_xyz.y, blue_xyz.y],
294                [red_xyz.z, green_xyz.z, blue_xyz.z],
295            ],
296        };
297        ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyy().to_xyzd())
298    }
299
300    /// Returns RGB -> XYZ conversion matrix
301    ///
302    /// # Arguments
303    ///
304    /// * `white_point`: [Chromaticity] or [crate::XyY] or any item conforming [XyYRepresentable]
305    ///
306    /// returns: [Matrix3f]
307    pub fn transform_to_xyz(self, white_point: impl XyYRepresentable) -> Matrix3f {
308        let red_xyz = self.red.to_scaled_xyz();
309        let green_xyz = self.green.to_scaled_xyz();
310        let blue_xyz = self.blue.to_scaled_xyz();
311
312        let xyz_matrix = Matrix3f {
313            v: [
314                [red_xyz.x, green_xyz.x, blue_xyz.x],
315                [red_xyz.y, green_xyz.y, blue_xyz.y],
316                [red_xyz.z, green_xyz.z, blue_xyz.z],
317            ],
318        };
319        ColorProfile::rgb_to_xyz_static(xyz_matrix, white_point.to_xyy().to_xyz())
320    }
321}
322
323/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 3
324/// Values 0, 3, 19–255 are all reserved so all map to the same variant
325#[derive(Clone, Copy, Debug, PartialEq)]
326pub enum TransferCharacteristics {
327    /// For future use by ITU-T | ISO/IEC
328    Reserved,
329    /// Rec. ITU-R BT.709-6<br />
330    /// Rec. ITU-R BT.1361-0 conventional colour gamut system (historical)<br />
331    /// (functionally the same as the values 6, 14 and 15)    <br />
332    Bt709 = 1,
333    /// Image characteristics are unknown or are determined by the application.<br />
334    Unspecified = 2,
335    /// Rec. ITU-R BT.470-6 System M (historical)<br />
336    /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
337    /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
338    /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
339    Bt470M = 4,
340    /// Rec. ITU-R BT.470-6 System B, G (historical)<br />
341    Bt470Bg = 5,
342    /// Rec. ITU-R BT.601-7 525 or 625<br />
343    /// Rec. ITU-R BT.1358-1 525 or 625 (historical)<br />
344    /// Rec. ITU-R BT.1700-0 NTSC SMPTE 170M (2004)<br />
345    /// (functionally the same as the values 1, 14 and 15)<br />
346    Bt601 = 6,
347    /// SMPTE 240M (1999) (historical)<br />
348    Smpte240 = 7,
349    /// Linear transfer characteristics<br />
350    Linear = 8,
351    /// Logarithmic transfer characteristic (100:1 range)<br />
352    Log100 = 9,
353    /// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range)<br />
354    Log100sqrt10 = 10,
355    /// IEC 61966-2-4<br />
356    Iec61966 = 11,
357    /// Rec. ITU-R BT.1361-0 extended colour gamut system (historical)<br />
358    Bt1361 = 12,
359    /// IEC 61966-2-1 sRGB or sYCC<br />
360    Srgb = 13,
361    /// Rec. ITU-R BT.2020-2 (10-bit system)<br />
362    /// (functionally the same as the values 1, 6 and 15)<br />
363    Bt202010bit = 14,
364    /// Rec. ITU-R BT.2020-2 (12-bit system)<br />
365    /// (functionally the same as the values 1, 6 and 14)<br />
366    Bt202012bit = 15,
367    /// SMPTE ST 2084 for 10-, 12-, 14- and 16-bitsystems<br />
368    /// Rec. ITU-R BT.2100-0 perceptual quantization (PQ) system<br />
369    Smpte2084 = 16,
370    /// SMPTE ST 428-1<br />
371    Smpte428 = 17,
372    /// ARIB STD-B67<br />
373    /// Rec. ITU-R BT.2100-0 hybrid log- gamma (HLG) system<br />
374    Hlg = 18,
375}
376
377impl TryFrom<u8> for TransferCharacteristics {
378    type Error = CmsError;
379
380    #[allow(unreachable_patterns)]
381    fn try_from(value: u8) -> Result<Self, Self::Error> {
382        match value {
383            0 | 3 | 19..=255 => Ok(Self::Reserved),
384            1 => Ok(Self::Bt709),
385            2 => Ok(Self::Unspecified),
386            4 => Ok(Self::Bt470M),
387            5 => Ok(Self::Bt470Bg),
388            6 => Ok(Self::Bt601),
389            7 => Ok(Self::Smpte240), // unimplemented
390            8 => Ok(Self::Linear),
391            9 => Ok(Self::Log100),
392            10 => Ok(Self::Log100sqrt10),
393            11 => Ok(Self::Iec61966), // unimplemented
394            12 => Ok(Self::Bt1361),   // unimplemented
395            13 => Ok(Self::Srgb),
396            14 => Ok(Self::Bt202010bit),
397            15 => Ok(Self::Bt202012bit),
398            16 => Ok(Self::Smpte2084),
399            17 => Ok(Self::Smpte428), // unimplemented
400            18 => Ok(Self::Hlg),
401            _ => Err(CmsError::InvalidCicp),
402        }
403    }
404}
405
406impl CicpColorPrimaries {
407    pub(crate) const fn has_chromaticity(self) -> bool {
408        self as u8 != Self::Reserved as u8 && self as u8 != Self::Unspecified as u8
409    }
410
411    pub(crate) const fn white_point(self) -> Result<Chromaticity, CmsError> {
412        Ok(match self {
413            Self::Reserved => return Err(CmsError::UnsupportedColorPrimaries(self as u8)),
414            Self::Bt709
415            | Self::Bt470Bg
416            | Self::Bt601
417            | Self::Smpte240
418            | Self::Bt2020
419            | Self::Smpte432
420            | Self::Ebu3213 => Chromaticity::D65,
421            Self::Unspecified => return Err(CmsError::UnsupportedColorPrimaries(self as u8)),
422            Self::Bt470M => Chromaticity { x: 0.310, y: 0.316 },
423            Self::GenericFilm => Chromaticity { x: 0.310, y: 0.316 },
424            Self::Xyz => Chromaticity {
425                x: 1. / 3.,
426                y: 1. / 3.,
427            },
428            Self::Smpte431 => Chromaticity { x: 0.314, y: 0.351 },
429        })
430    }
431}
432
433impl TryFrom<CicpColorPrimaries> for ColorPrimaries {
434    type Error = CmsError;
435
436    fn try_from(value: CicpColorPrimaries) -> Result<Self, Self::Error> {
437        match value {
438            CicpColorPrimaries::Reserved => Err(CmsError::UnsupportedColorPrimaries(value as u8)),
439            CicpColorPrimaries::Bt709 => Ok(ColorPrimaries::BT_709),
440            CicpColorPrimaries::Unspecified => {
441                Err(CmsError::UnsupportedColorPrimaries(value as u8))
442            }
443            CicpColorPrimaries::Bt470M => Ok(ColorPrimaries::BT_470M),
444            CicpColorPrimaries::Bt470Bg => Ok(ColorPrimaries::BT_470BG),
445            CicpColorPrimaries::Bt601 | CicpColorPrimaries::Smpte240 => Ok(ColorPrimaries::BT_601),
446            CicpColorPrimaries::GenericFilm => Ok(ColorPrimaries::GENERIC_FILM),
447            CicpColorPrimaries::Bt2020 => Ok(ColorPrimaries::BT_2020),
448            CicpColorPrimaries::Xyz => Ok(ColorPrimaries::XYZ),
449            // These two share primaries, but have distinct white points
450            CicpColorPrimaries::Smpte431 | CicpColorPrimaries::Smpte432 => {
451                Ok(ColorPrimaries::SMPTE_431)
452            }
453            CicpColorPrimaries::Ebu3213 => Ok(ColorPrimaries::EBU_3213),
454        }
455    }
456}
457
458impl TransferCharacteristics {
459    pub(crate) fn has_transfer_curve(self) -> bool {
460        self != Self::Reserved && self != Self::Unspecified
461    }
462}
463
464pub(crate) fn create_rec709_parametric() -> [f32; 5] {
465    const POW_EXP: f32 = 0.45;
466
467    const G: f32 = 1. / POW_EXP;
468    const B: f32 = (0.09929682680944f64 / 1.09929682680944f64) as f32;
469    const C: f32 = 1f32 / 4.5f32;
470    const D: f32 = (4.5f64 * 0.018053968510807f64) as f32;
471    const A: f32 = (1. / 1.09929682680944f64) as f32;
472
473    [G, A, B, C, D]
474}
475
476impl TryFrom<TransferCharacteristics> for ToneReprCurve {
477    type Error = CmsError;
478    /// See [ICC.1:2010](https://www.color.org/specification/ICC1v43_2010-12.pdf)
479    /// See [Rec. ITU-R BT.2100-2](https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf)
480    fn try_from(value: TransferCharacteristics) -> Result<Self, Self::Error> {
481        const NUM_TRC_TABLE_ENTRIES: i32 = 1024;
482
483        Ok(match value {
484            TransferCharacteristics::Reserved => {
485                return Err(CmsError::UnsupportedTrc(value as u8));
486            }
487            TransferCharacteristics::Bt709
488            | TransferCharacteristics::Bt601
489            | TransferCharacteristics::Bt202010bit
490            | TransferCharacteristics::Bt202012bit => {
491                // The opto-electronic transfer characteristic function (OETF)
492                // as defined in ITU-T H.273 table 3, row 1:
493                //
494                // V = (α * Lc^0.45) − (α − 1)  for 1 >= Lc >= β
495                // V = 4.500 * Lc               for β >  Lc >= 0
496                //
497                // Inverting gives the electro-optical transfer characteristic
498                // function (EOTF) which can be represented as ICC
499                // parametricCurveType with 4 parameters (ICC.1:2010 Table 5).
500                // Converting between the two (Lc ↔︎ Y, V ↔︎ X):
501                //
502                // Y = (a * X + b)^g  for (X >= d)
503                // Y = c * X          for (X < d)
504                //
505                // g, a, b, c, d can then be defined in terms of α and β:
506                //
507                // g = 1 / 0.45
508                // a = 1 / α
509                // b = 1 - α
510                // c = 1 / 4.500
511                // d = 4.500 * β
512                //
513                // α and β are determined by solving the piecewise equations to
514                // ensure continuity of both value and slope at the value β.
515                // We use the values specified for 10-bit systems in
516                // https://www.itu.int/rec/R-REC-BT.2020-2-201510-I Table 4
517                // since this results in the similar values as available ICC
518                // profiles after converting to s15Fixed16Number, providing us
519                // good test coverage.
520
521                ToneReprCurve::Parametric(create_rec709_parametric().to_vec())
522            }
523            TransferCharacteristics::Unspecified => {
524                return Err(CmsError::UnsupportedTrc(value as u8));
525            }
526            TransferCharacteristics::Bt470M => curve_from_gamma(2.2),
527            TransferCharacteristics::Bt470Bg => curve_from_gamma(2.8),
528            TransferCharacteristics::Smpte240 => {
529                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, smpte240_to_linear);
530                ToneReprCurve::Lut(table)
531            }
532            TransferCharacteristics::Linear => curve_from_gamma(1.),
533            TransferCharacteristics::Log100 => {
534                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, log100_to_linear);
535                ToneReprCurve::Lut(table)
536            }
537            TransferCharacteristics::Log100sqrt10 => {
538                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, log100_sqrt10_to_linear);
539                ToneReprCurve::Lut(table)
540            }
541            TransferCharacteristics::Iec61966 => {
542                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, iec61966_to_linear);
543                ToneReprCurve::Lut(table)
544            }
545            TransferCharacteristics::Bt1361 => {
546                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, bt1361_to_linear);
547                ToneReprCurve::Lut(table)
548            }
549            TransferCharacteristics::Srgb => {
550                ToneReprCurve::Parametric(vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045])
551            }
552            TransferCharacteristics::Smpte2084 => {
553                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, pq_to_linear);
554                ToneReprCurve::Lut(table)
555            }
556            TransferCharacteristics::Smpte428 => {
557                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, smpte428_to_linear);
558                ToneReprCurve::Lut(table)
559            }
560            TransferCharacteristics::Hlg => {
561                let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, hlg_to_linear);
562                ToneReprCurve::Lut(table)
563            }
564        })
565    }
566}
567
568/// Matrix Coefficients Enum (from ISO/IEC 23091-4 / MPEG CICP)
569#[derive(Debug, Copy, Clone, PartialEq, Eq)]
570#[repr(C)]
571pub enum MatrixCoefficients {
572    Identity = 0,                // RGB (Identity matrix)
573    Bt709 = 1,                   // Rec. 709
574    Unspecified = 2,             // Unspecified
575    Reserved = 3,                // Reserved
576    Fcc = 4,                     // FCC
577    Bt470Bg = 5,                 // BT.470BG / BT.601-625
578    Smpte170m = 6,               // SMPTE 170M / BT.601-525
579    Smpte240m = 7,               // SMPTE 240M
580    YCgCo = 8,                   // YCgCo
581    Bt2020Ncl = 9,               // BT.2020 (non-constant luminance)
582    Bt2020Cl = 10,               // BT.2020 (constant luminance)
583    Smpte2085 = 11,              // SMPTE ST 2085
584    ChromaticityDerivedNCL = 12, // Chromaticity-derived non-constant luminance
585    ChromaticityDerivedCL = 13,  // Chromaticity-derived constant luminance
586    ICtCp = 14,                  // ICtCp
587}
588
589impl TryFrom<u8> for MatrixCoefficients {
590    type Error = CmsError;
591
592    fn try_from(value: u8) -> Result<Self, CmsError> {
593        match value {
594            0 => Ok(MatrixCoefficients::Identity),
595            1 => Ok(MatrixCoefficients::Bt709),
596            2 => Ok(MatrixCoefficients::Unspecified),
597            3 => Ok(MatrixCoefficients::Reserved),
598            4 => Ok(MatrixCoefficients::Fcc),
599            5 => Ok(MatrixCoefficients::Bt470Bg),
600            6 => Ok(MatrixCoefficients::Smpte170m),
601            7 => Ok(MatrixCoefficients::Smpte240m),
602            8 => Ok(MatrixCoefficients::YCgCo),
603            9 => Ok(MatrixCoefficients::Bt2020Ncl),
604            10 => Ok(MatrixCoefficients::Bt2020Cl),
605            11 => Ok(MatrixCoefficients::Smpte2085),
606            12 => Ok(MatrixCoefficients::ChromaticityDerivedNCL),
607            13 => Ok(MatrixCoefficients::ChromaticityDerivedCL),
608            14 => Ok(MatrixCoefficients::ICtCp),
609            _ => Err(CmsError::InvalidCicp),
610        }
611    }
612}
613
614#[cfg(test)]
615mod tests {
616    use super::*;
617    use crate::WHITE_POINT_D65;
618
619    #[test]
620    fn test_to_xyz_using_absolute_coordinates() {
621        let conversion_matrix = ColorPrimaries::BT_709.transform_to_xyz_d(WHITE_POINT_D65);
622        assert!((conversion_matrix.v[0][0] - 0.4121524015214193).abs() < 1e-14);
623        assert!((conversion_matrix.v[1][1] - 0.7153537403945436).abs() < 1e-14);
624        assert!((conversion_matrix.v[2][2] - 0.9497138466283235).abs() < 1e-14);
625    }
626
627    #[test]
628    fn test_to_xyz_using_absolute_coordinates_xyz() {
629        let conversion_matrix = ColorPrimaries::XYZ.transform_to_xyz_d(WHITE_POINT_D65);
630        assert!((conversion_matrix.v[0][0] - 0.95015469385536477).abs() < 1e-14);
631        assert!((conversion_matrix.v[1][1] - 1.0).abs() < 1e-14);
632        assert!((conversion_matrix.v[2][2] - 1.0882590676722474).abs() < 1e-14);
633    }
634
635    #[test]
636    fn test_to_xyz_using_absolute_coordinates_f() {
637        let conversion_matrix = ColorPrimaries::BT_709.transform_to_xyz(WHITE_POINT_D65);
638        assert!((conversion_matrix.v[0][0] - 0.4121524015214193).abs() < 1e-5);
639        assert!((conversion_matrix.v[1][1] - 0.7153537403945436).abs() < 1e-5);
640        assert!((conversion_matrix.v[2][2] - 0.9497138466283235).abs() < 1e-5);
641    }
642}