1use crate::chad::BRADFORD_D;
30use crate::cicp::create_rec709_parametric;
31use crate::trc::{ToneReprCurve, curve_from_gamma};
32use crate::{
33 CicpColorPrimaries, CicpProfile, ColorPrimaries, ColorProfile, DataColorSpace,
34 LocalizableString, Matrix3d, MatrixCoefficients, ProfileClass, ProfileText, RenderingIntent,
35 TransferCharacteristics, XyY,
36};
37use pxfm::{copysignk, exp, floor, pow};
38
39const fn white_point_from_temperature(temp_k: i32) -> XyY {
45 let mut white_point = XyY {
46 x: 0.,
47 y: 0.,
48 yb: 0.,
49 };
50 let temp_k = temp_k as f64; let temp_k2 = temp_k * temp_k; let temp_k3 = temp_k2 * temp_k;
54 let x = if temp_k > 4000.0 && temp_k <= 7000.0 {
56 -4.6070 * (1E9 / temp_k3) + 2.9678 * (1E6 / temp_k2) + 0.09911 * (1E3 / temp_k) + 0.244063
57 } else if temp_k > 7000.0 && temp_k <= 25000.0 {
58 -2.0064 * (1E9 / temp_k3) + 1.9018 * (1E6 / temp_k2) + 0.24748 * (1E3 / temp_k) + 0.237040
59 } else {
60 white_point.x = -1.0;
63 white_point.y = -1.0;
64 white_point.yb = -1.0;
65 debug_assert!(false, "invalid temp");
66 return white_point;
67 };
68 let y = -3.000 * (x * x) + 2.870 * x - 0.275;
70 white_point.x = x;
75 white_point.y = y;
76 white_point.yb = 1.0;
77 white_point
78}
79
80pub const WHITE_POINT_D50: XyY = white_point_from_temperature(5003);
81pub const WHITE_POINT_D60: XyY = white_point_from_temperature(6000);
82pub const WHITE_POINT_D65: XyY = white_point_from_temperature(6504);
83pub const WHITE_POINT_DCI_P3: XyY = white_point_from_temperature(6300);
84
85#[inline]
88const fn pq_curve(x: f64) -> f64 {
89 const M1: f64 = 2610.0 / 16384.0;
90 const M2: f64 = (2523.0 / 4096.0) * 128.0;
91 const C1: f64 = 3424.0 / 4096.0;
92 const C2: f64 = (2413.0 / 4096.0) * 32.0;
93 const C3: f64 = (2392.0 / 4096.0) * 32.0;
94
95 if x == 0.0 {
96 return 0.0;
97 }
98 let sign = x;
99 let x = x.abs();
100
101 let xpo = pow(x, 1.0 / M2);
102 let num = (xpo - C1).max(0.0);
103 let den = C2 - C3 * xpo;
104 let res = pow(num / den, 1.0 / M1);
105
106 copysignk(res, sign)
107}
108
109pub(crate) const fn build_trc_table_pq() -> [u16; 4096] {
110 let mut table = [0u16; 4096];
111
112 const NUM_ENTRIES: usize = 4096;
113 let mut i = 0usize;
114 while i < NUM_ENTRIES {
115 let x: f64 = i as f64 / (NUM_ENTRIES - 1) as f64;
116 let y: f64 = pq_curve(x);
117 let mut output: f64;
118 output = y * 65535.0 + 0.5;
119 if output > 65535.0 {
120 output = 65535.0
121 }
122 if output < 0.0 {
123 output = 0.0
124 }
125 table[i] = floor(output) as u16;
126 i += 1;
127 }
128 table
129}
130
131pub(crate) const fn build_trc_table_hlg() -> [u16; 4096] {
132 let mut table = [0u16; 4096];
133
134 const NUM_ENTRIES: usize = 4096;
135 let mut i = 0usize;
136 while i < NUM_ENTRIES {
137 let x: f64 = i as f64 / (NUM_ENTRIES - 1) as f64;
138 let y: f64 = hlg_curve(x);
139 let mut output: f64;
140 output = y * 65535.0 + 0.5;
141 if output > 65535.0 {
142 output = 65535.0
143 }
144 if output < 0.0 {
145 output = 0.0
146 }
147 table[i] = floor(output) as u16;
148 i += 1;
149 }
150 table
151}
152
153const fn hlg_curve(x: f64) -> f64 {
156 const BETA: f64 = 0.04;
157 const RA: f64 = 5.591816309728916; const B: f64 = 0.28466892; const C: f64 = 0.5599107295; let e = (x * (1.0 - BETA) + BETA).max(0.0);
162
163 if e == 0.0 {
164 return 0.0;
165 }
166
167 let sign = e.abs();
168
169 let res = if e <= 0.5 {
170 e * e / 3.0
171 } else {
172 (exp((e - C) * RA) + B) / 12.0
173 };
174
175 copysignk(res, sign)
176}
177
178pub const PQ_LUT_TABLE: [u16; 4096] = build_trc_table_pq();
180pub const HLG_LUT_TABLE: [u16; 4096] = build_trc_table_hlg();
182
183impl ColorProfile {
184 const SRGB_COLORANTS: Matrix3d =
185 ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::BT_709);
186
187 const DISPLAY_P3_COLORANTS: Matrix3d =
188 ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::SMPTE_432);
189
190 const ADOBE_RGB_COLORANTS: Matrix3d =
191 ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::ADOBE_RGB);
192
193 const DCI_P3_COLORANTS: Matrix3d =
194 ColorProfile::colorants_matrix(WHITE_POINT_DCI_P3, ColorPrimaries::DCI_P3);
195
196 const PRO_PHOTO_RGB_COLORANTS: Matrix3d =
197 ColorProfile::colorants_matrix(WHITE_POINT_D50, ColorPrimaries::PRO_PHOTO_RGB);
198
199 const BT2020_COLORANTS: Matrix3d =
200 ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::BT_2020);
201
202 const ACES_2065_1_COLORANTS: Matrix3d =
203 ColorProfile::colorants_matrix(WHITE_POINT_D60, ColorPrimaries::ACES_2065_1);
204
205 const ACES_CG_COLORANTS: Matrix3d =
206 ColorProfile::colorants_matrix(WHITE_POINT_D60, ColorPrimaries::ACES_CG);
207
208 #[inline]
209 fn basic_rgb_profile() -> ColorProfile {
210 ColorProfile {
211 profile_class: ProfileClass::DisplayDevice,
212 rendering_intent: RenderingIntent::Perceptual,
213 color_space: DataColorSpace::Rgb,
214 pcs: DataColorSpace::Xyz,
215 chromatic_adaptation: Some(BRADFORD_D),
216 white_point: WHITE_POINT_D50.to_xyzd(),
217 ..Default::default()
218 }
219 }
220
221 pub fn new_from_cicp(cicp_color_primaries: CicpProfile) -> ColorProfile {
223 let mut basic = ColorProfile::basic_rgb_profile();
224 basic.update_rgb_colorimetry_from_cicp(cicp_color_primaries);
225 basic
226 }
227
228 pub fn new_srgb() -> ColorProfile {
230 let mut profile = ColorProfile::basic_rgb_profile();
231 profile.update_colorants(ColorProfile::SRGB_COLORANTS);
232
233 let curve =
234 ToneReprCurve::Parametric(vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045]);
235 profile.red_trc = Some(curve.clone());
236 profile.blue_trc = Some(curve.clone());
237 profile.green_trc = Some(curve);
238 profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
239 profile.cicp = Some(CicpProfile {
240 color_primaries: CicpColorPrimaries::Bt709,
241 transfer_characteristics: TransferCharacteristics::Srgb,
242 matrix_coefficients: MatrixCoefficients::Bt709,
243 full_range: false,
244 });
245 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
246 "en".to_string(),
247 "US".to_string(),
248 "sRGB IEC61966-2.1".to_string(),
249 )]));
250 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
251 "en".to_string(),
252 "US".to_string(),
253 "Public Domain".to_string(),
254 )]));
255 profile
256 }
257
258 pub fn new_adobe_rgb() -> ColorProfile {
260 let mut profile = ColorProfile::basic_rgb_profile();
261 profile.update_colorants(ColorProfile::ADOBE_RGB_COLORANTS);
262
263 let curve = curve_from_gamma(2.19921875f32);
264 profile.red_trc = Some(curve.clone());
265 profile.blue_trc = Some(curve.clone());
266 profile.green_trc = Some(curve);
267 profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
268 profile.white_point = WHITE_POINT_D50.to_xyzd();
269 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
270 "en".to_string(),
271 "US".to_string(),
272 "Adobe RGB 1998".to_string(),
273 )]));
274 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
275 "en".to_string(),
276 "US".to_string(),
277 "Public Domain".to_string(),
278 )]));
279 profile
280 }
281
282 pub fn new_display_p3() -> ColorProfile {
284 let mut profile = ColorProfile::basic_rgb_profile();
285 profile.update_colorants(ColorProfile::DISPLAY_P3_COLORANTS);
286
287 let curve =
288 ToneReprCurve::Parametric(vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045]);
289 profile.red_trc = Some(curve.clone());
290 profile.blue_trc = Some(curve.clone());
291 profile.green_trc = Some(curve);
292 profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
293 profile.cicp = Some(CicpProfile {
294 color_primaries: CicpColorPrimaries::Smpte431,
295 transfer_characteristics: TransferCharacteristics::Srgb,
296 matrix_coefficients: MatrixCoefficients::Bt709,
297 full_range: false,
298 });
299 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
300 "en".to_string(),
301 "US".to_string(),
302 "Display P3".to_string(),
303 )]));
304 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
305 "en".to_string(),
306 "US".to_string(),
307 "Public Domain".to_string(),
308 )]));
309 profile
310 }
311
312 pub fn new_display_p3_pq() -> ColorProfile {
314 let mut profile = ColorProfile::basic_rgb_profile();
315 profile.update_colorants(ColorProfile::DISPLAY_P3_COLORANTS);
316
317 let curve = ToneReprCurve::Lut(PQ_LUT_TABLE.to_vec());
318
319 profile.red_trc = Some(curve.clone());
320 profile.blue_trc = Some(curve.clone());
321 profile.green_trc = Some(curve);
322 profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
323 profile.cicp = Some(CicpProfile {
324 color_primaries: CicpColorPrimaries::Smpte431,
325 transfer_characteristics: TransferCharacteristics::Smpte2084,
326 matrix_coefficients: MatrixCoefficients::Bt709,
327 full_range: false,
328 });
329 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
330 "en".to_string(),
331 "US".to_string(),
332 "Display P3 PQ".to_string(),
333 )]));
334 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
335 "en".to_string(),
336 "US".to_string(),
337 "Public Domain".to_string(),
338 )]));
339 profile
340 }
341
342 pub fn new_dci_p3() -> ColorProfile {
344 let mut profile = ColorProfile::basic_rgb_profile();
345 profile.update_colorants(ColorProfile::DCI_P3_COLORANTS);
346
347 let curve = curve_from_gamma(2.6f32);
348 profile.red_trc = Some(curve.clone());
349 profile.blue_trc = Some(curve.clone());
350 profile.green_trc = Some(curve);
351 profile.media_white_point = Some(WHITE_POINT_DCI_P3.to_xyzd());
352 profile.cicp = Some(CicpProfile {
353 color_primaries: CicpColorPrimaries::Smpte432,
354 transfer_characteristics: TransferCharacteristics::Srgb,
355 matrix_coefficients: MatrixCoefficients::Bt709,
356 full_range: false,
357 });
358 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
359 "en".to_string(),
360 "US".to_string(),
361 "DCI P3".to_string(),
362 )]));
363 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
364 "en".to_string(),
365 "US".to_string(),
366 "Public Domain".to_string(),
367 )]));
368 profile
369 }
370
371 pub fn new_pro_photo_rgb() -> ColorProfile {
373 let mut profile = ColorProfile::basic_rgb_profile();
374 profile.update_colorants(ColorProfile::PRO_PHOTO_RGB_COLORANTS);
375
376 let curve = curve_from_gamma(1.8f32);
377 profile.red_trc = Some(curve.clone());
378 profile.blue_trc = Some(curve.clone());
379 profile.green_trc = Some(curve);
380 profile.media_white_point = Some(WHITE_POINT_D50.to_xyzd());
381 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
382 "en".to_string(),
383 "US".to_string(),
384 "ProPhoto RGB".to_string(),
385 )]));
386 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
387 "en".to_string(),
388 "US".to_string(),
389 "Public Domain".to_string(),
390 )]));
391 profile
392 }
393
394 pub fn new_bt2020() -> ColorProfile {
396 let mut profile = ColorProfile::basic_rgb_profile();
397 profile.update_colorants(ColorProfile::BT2020_COLORANTS);
398
399 let curve = ToneReprCurve::Parametric(create_rec709_parametric().to_vec());
400 profile.red_trc = Some(curve.clone());
401 profile.blue_trc = Some(curve.clone());
402 profile.green_trc = Some(curve);
403 profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
404 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
405 "en".to_string(),
406 "US".to_string(),
407 "Rec.2020".to_string(),
408 )]));
409 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
410 "en".to_string(),
411 "US".to_string(),
412 "Public Domain".to_string(),
413 )]));
414 profile
415 }
416
417 pub fn new_bt2020_pq() -> ColorProfile {
419 let mut profile = ColorProfile::basic_rgb_profile();
420 profile.update_colorants(ColorProfile::BT2020_COLORANTS);
421
422 let curve = ToneReprCurve::Lut(PQ_LUT_TABLE.to_vec());
423
424 profile.red_trc = Some(curve.clone());
425 profile.blue_trc = Some(curve.clone());
426 profile.green_trc = Some(curve);
427 profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
428 profile.cicp = Some(CicpProfile {
429 color_primaries: CicpColorPrimaries::Bt2020,
430 transfer_characteristics: TransferCharacteristics::Smpte2084,
431 matrix_coefficients: MatrixCoefficients::Bt709,
432 full_range: false,
433 });
434 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
435 "en".to_string(),
436 "US".to_string(),
437 "Rec.2020 PQ".to_string(),
438 )]));
439 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
440 "en".to_string(),
441 "US".to_string(),
442 "Public Domain".to_string(),
443 )]));
444 profile
445 }
446
447 pub fn new_bt2020_hlg() -> ColorProfile {
449 let mut profile = ColorProfile::basic_rgb_profile();
450 profile.update_colorants(ColorProfile::BT2020_COLORANTS);
451
452 let curve = ToneReprCurve::Lut(HLG_LUT_TABLE.to_vec());
453
454 profile.red_trc = Some(curve.clone());
455 profile.blue_trc = Some(curve.clone());
456 profile.green_trc = Some(curve);
457 profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
458 profile.cicp = Some(CicpProfile {
459 color_primaries: CicpColorPrimaries::Bt2020,
460 transfer_characteristics: TransferCharacteristics::Hlg,
461 matrix_coefficients: MatrixCoefficients::Bt709,
462 full_range: false,
463 });
464 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
465 "en".to_string(),
466 "US".to_string(),
467 "Rec.2020 HLG".to_string(),
468 )]));
469 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
470 "en".to_string(),
471 "US".to_string(),
472 "Public Domain".to_string(),
473 )]));
474 profile
475 }
476
477 pub fn new_gray_with_gamma(gamma: f32) -> ColorProfile {
479 ColorProfile {
480 gray_trc: Some(curve_from_gamma(gamma)),
481 profile_class: ProfileClass::DisplayDevice,
482 rendering_intent: RenderingIntent::Perceptual,
483 color_space: DataColorSpace::Gray,
484 media_white_point: Some(WHITE_POINT_D65.to_xyzd()),
485 white_point: WHITE_POINT_D50.to_xyzd(),
486 chromatic_adaptation: Some(BRADFORD_D),
487 copyright: Some(ProfileText::Localizable(vec![LocalizableString::new(
488 "en".to_string(),
489 "US".to_string(),
490 "Public Domain".to_string(),
491 )])),
492 ..Default::default()
493 }
494 }
495
496 pub fn new_aces_aces_2065_1_linear() -> ColorProfile {
498 let mut profile = ColorProfile::basic_rgb_profile();
499 profile.update_colorants(ColorProfile::ACES_2065_1_COLORANTS);
500
501 let curve = ToneReprCurve::Lut(vec![]);
502 profile.red_trc = Some(curve.clone());
503 profile.blue_trc = Some(curve.clone());
504 profile.green_trc = Some(curve);
505 profile.media_white_point = Some(WHITE_POINT_D60.to_xyzd());
506 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
507 "en".to_string(),
508 "US".to_string(),
509 "ACES 2065-1".to_string(),
510 )]));
511 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
512 "en".to_string(),
513 "US".to_string(),
514 "Public Domain".to_string(),
515 )]));
516 profile
517 }
518
519 pub fn new_aces_cg_linear() -> ColorProfile {
521 let mut profile = ColorProfile::basic_rgb_profile();
522 profile.update_colorants(ColorProfile::ACES_CG_COLORANTS);
523
524 let curve = ToneReprCurve::Lut(vec![]);
525 profile.red_trc = Some(curve.clone());
526 profile.blue_trc = Some(curve.clone());
527 profile.green_trc = Some(curve);
528 profile.media_white_point = Some(WHITE_POINT_D60.to_xyzd());
529 profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
530 "en".to_string(),
531 "US".to_string(),
532 "ACEScg/AP1".to_string(),
533 )]));
534 profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
535 "en".to_string(),
536 "US".to_string(),
537 "Public Domain".to_string(),
538 )]));
539 profile
540 }
541}