Skip to main content

zenjxl_decoder/headers/
color_encoding.rs

1// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6use crate::{bit_reader::BitReader, error::Error, headers::encodings::*};
7use jxl_macros::UnconditionalCoder;
8use num_derive::FromPrimitive;
9use std::fmt;
10
11#[allow(clippy::upper_case_acronyms)]
12#[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)]
13pub enum ColorSpace {
14    RGB,
15    Gray,
16    XYB,
17    Unknown,
18}
19
20impl fmt::Display for ColorSpace {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        write!(
23            f,
24            "{}",
25            match self {
26                ColorSpace::RGB => "RGB",
27                ColorSpace::Gray => "Gra",
28                ColorSpace::XYB => "XYB",
29                ColorSpace::Unknown => "CS?",
30            }
31        )
32    }
33}
34
35#[allow(clippy::upper_case_acronyms)]
36#[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)]
37pub enum WhitePoint {
38    D65 = 1,
39    Custom = 2,
40    E = 10,
41    DCI = 11,
42}
43
44#[allow(clippy::upper_case_acronyms)]
45#[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)]
46pub enum Primaries {
47    SRGB = 1,
48    Custom = 2,
49    BT2100 = 9,
50    P3 = 11,
51}
52
53#[allow(clippy::upper_case_acronyms)]
54#[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)]
55pub enum TransferFunction {
56    BT709 = 1,
57    Unknown = 2,
58    Linear = 8,
59    SRGB = 13,
60    PQ = 16,
61    DCI = 17,
62    HLG = 18,
63}
64
65#[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)]
66pub enum RenderingIntent {
67    Perceptual = 0,
68    Relative,
69    Saturation,
70    Absolute,
71}
72
73impl fmt::Display for RenderingIntent {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(
76            f,
77            "{}",
78            match self {
79                RenderingIntent::Perceptual => "Per",
80                RenderingIntent::Relative => "Rel",
81                RenderingIntent::Saturation => "Sat",
82                RenderingIntent::Absolute => "Abs",
83            }
84        )
85    }
86}
87
88#[derive(UnconditionalCoder, Debug, Clone)]
89pub struct CustomXY {
90    #[default(0)]
91    #[coder(u2S(Bits(19), Bits(19) + 524288, Bits(20) + 1048576, Bits(21) + 2097152))]
92    pub x: i32,
93    #[default(0)]
94    #[coder(u2S(Bits(19), Bits(19) + 524288, Bits(20) + 1048576, Bits(21) + 2097152))]
95    pub y: i32,
96}
97
98impl CustomXY {
99    /// Converts the stored scaled integer coordinates to f32 (x, y) values.
100    pub fn as_f32_coords(&self) -> (f32, f32) {
101        (self.x as f32 / 1_000_000.0, self.y as f32 / 1_000_000.0)
102    }
103
104    pub fn from_f32_coords(x: f32, y: f32) -> Self {
105        Self {
106            x: (x * 1_000_000.0).round() as i32,
107            y: (y * 1_000_000.0).round() as i32,
108        }
109    }
110}
111
112pub struct CustomTransferFunctionNonserialized {
113    color_space: ColorSpace,
114}
115
116#[derive(UnconditionalCoder, Debug, Clone)]
117#[nonserialized(CustomTransferFunctionNonserialized)]
118#[validate]
119pub struct CustomTransferFunction {
120    #[condition(nonserialized.color_space != ColorSpace::XYB)]
121    #[default(false)]
122    pub have_gamma: bool,
123    #[condition(have_gamma)]
124    #[default(3333333)] // XYB gamma
125    #[coder(Bits(24))]
126    pub gamma: u32,
127    #[condition(!have_gamma && nonserialized.color_space != ColorSpace::XYB)]
128    #[default(TransferFunction::SRGB)]
129    pub transfer_function: TransferFunction,
130}
131
132impl CustomTransferFunction {
133    #[cfg(test)]
134    pub fn empty() -> CustomTransferFunction {
135        CustomTransferFunction {
136            have_gamma: false,
137            gamma: 0,
138            transfer_function: TransferFunction::Unknown,
139        }
140    }
141    pub fn gamma(&self) -> f32 {
142        assert!(self.have_gamma);
143        self.gamma as f32 * 0.0000001
144    }
145
146    pub fn check(&self, _: &CustomTransferFunctionNonserialized) -> Result<(), Error> {
147        if self.have_gamma {
148            let gamma = self.gamma();
149            if gamma > 1.0 || gamma * 8192.0 < 1.0 {
150                Err(Error::InvalidGamma(gamma))
151            } else {
152                Ok(())
153            }
154        } else {
155            Ok(())
156        }
157    }
158}
159
160#[derive(UnconditionalCoder, Debug, Clone)]
161#[validate]
162pub struct ColorEncoding {
163    // all_default is never read.
164    #[allow(dead_code)]
165    #[all_default]
166    all_default: bool,
167    #[default(false)]
168    pub want_icc: bool,
169    #[default(ColorSpace::RGB)]
170    pub color_space: ColorSpace,
171    #[condition(!want_icc && color_space != ColorSpace::XYB)]
172    #[default(WhitePoint::D65)]
173    pub white_point: WhitePoint,
174    // TODO(veluca): can this be merged in the enum?!
175    #[condition(white_point == WhitePoint::Custom)]
176    #[default(CustomXY::default(&field_nonserialized))]
177    pub white: CustomXY,
178    #[condition(!want_icc && color_space != ColorSpace::XYB && color_space != ColorSpace::Gray)]
179    #[default(Primaries::SRGB)]
180    pub primaries: Primaries,
181    #[condition(primaries == Primaries::Custom)]
182    #[default([CustomXY::default(&field_nonserialized), CustomXY::default(&field_nonserialized), CustomXY::default(&field_nonserialized)])]
183    pub custom_primaries: [CustomXY; 3],
184    #[condition(!want_icc)]
185    #[default(CustomTransferFunction::default(&field_nonserialized))]
186    #[nonserialized(color_space: color_space)]
187    pub tf: CustomTransferFunction,
188    #[condition(!want_icc)]
189    #[default(RenderingIntent::Relative)]
190    pub rendering_intent: RenderingIntent,
191}
192
193impl ColorEncoding {
194    pub fn check(&self, _: &Empty) -> Result<(), Error> {
195        if self.color_space == ColorSpace::Unknown
196            || self.tf.transfer_function == TransferFunction::Unknown
197            || self.color_space == ColorSpace::XYB
198        {
199            Err(Error::InvalidColorEncoding)
200        } else {
201            Ok(())
202        }
203    }
204}