1use 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 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)] #[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 #[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 #[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}