1use crate::cache::Cache;
4use crate::function::Function;
5use hayro_syntax::object;
6use hayro_syntax::object::Array;
7use hayro_syntax::object::Dict;
8use hayro_syntax::object::Name;
9use hayro_syntax::object::Object;
10use hayro_syntax::object::Stream;
11use hayro_syntax::object::dict::keys::*;
12use log::warn;
13use moxcms::{ColorProfile, DataColorSpace, Layout, Transform8BitExecutor, TransformOptions};
14use smallvec::{SmallVec, ToSmallVec, smallvec};
15use std::fmt::{Debug, Formatter};
16use std::ops::Deref;
17use std::sync::{Arc, LazyLock};
18
19pub type ColorComponents = SmallVec<[f32; 4]>;
21
22#[derive(Debug, Copy, Clone)]
24pub struct AlphaColor {
25 components: [f32; 4],
26}
27
28impl AlphaColor {
29 pub const BLACK: Self = Self::new([0., 0., 0., 1.]);
31
32 pub const TRANSPARENT: Self = Self::new([0., 0., 0., 0.]);
34
35 pub const WHITE: Self = Self::new([1., 1., 1., 1.]);
37
38 pub const fn new(components: [f32; 4]) -> Self {
40 Self { components }
41 }
42
43 pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
45 let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), 1.];
46 Self::new(components)
47 }
48
49 pub fn premultiplied(&self) -> [f32; 4] {
51 [
52 self.components[0] * self.components[3],
53 self.components[1] * self.components[3],
54 self.components[2] * self.components[3],
55 self.components[3],
56 ]
57 }
58
59 pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
61 let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
62 Self::new(components)
63 }
64
65 pub fn to_rgba8(&self) -> [u8; 4] {
67 [
68 (self.components[0] * 255.0 + 0.5) as u8,
69 (self.components[1] * 255.0 + 0.5) as u8,
70 (self.components[2] * 255.0 + 0.5) as u8,
71 (self.components[3] * 255.0 + 0.5) as u8,
72 ]
73 }
74
75 pub fn components(&self) -> [f32; 4] {
77 self.components
78 }
79}
80
81const fn u8_to_f32(x: u8) -> f32 {
82 x as f32 * (1.0 / 255.0)
83}
84
85#[derive(Debug, Clone)]
86enum ColorSpaceType {
87 DeviceCmyk,
88 DeviceGray,
89 DeviceRgb,
90 Pattern(ColorSpace),
91 Indexed(Indexed),
92 ICCBased(ICCProfile),
93 CalGray(CalGray),
94 CalRgb(CalRgb),
95 Lab(Lab),
96 Separation(Separation),
97 DeviceN(DeviceN),
98}
99
100impl ColorSpaceType {
101 fn new(object: Object, cache: &Cache) -> Option<Self> {
102 Self::new_inner(object, cache)
103 }
104
105 fn new_inner(object: Object, cache: &Cache) -> Option<ColorSpaceType> {
106 if let Some(name) = object.clone().into_name() {
107 return Self::new_from_name(name.clone());
108 } else if let Some(color_array) = object.clone().into_array() {
109 let mut iter = color_array.clone().flex_iter();
110 let name = iter.next::<Name>()?;
111
112 match name.deref() {
113 ICC_BASED => {
114 let icc_stream = iter.next::<Stream>()?;
115 let dict = icc_stream.dict();
116 let num_components = dict.get::<usize>(N)?;
117
118 return cache.get_or_insert_with(icc_stream.obj_id(), || {
119 if let Some(decoded) = icc_stream.decoded().ok().as_ref() {
120 ICCProfile::new(decoded, num_components)
121 .map(|icc| {
122 if icc.is_srgb() {
127 ColorSpaceType::DeviceRgb
128 } else {
129 ColorSpaceType::ICCBased(icc)
130 }
131 })
132 .or_else(|| {
133 dict.get::<Object>(ALTERNATE)
134 .and_then(|o| ColorSpaceType::new(o, cache))
135 })
136 .or_else(|| match dict.get::<u8>(N) {
137 Some(1) => Some(ColorSpaceType::DeviceGray),
138 Some(3) => Some(ColorSpaceType::DeviceRgb),
139 Some(4) => Some(ColorSpaceType::DeviceCmyk),
140 _ => None,
141 })
142 } else {
143 None
144 }
145 });
146 }
147 CALCMYK => return Some(ColorSpaceType::DeviceCmyk),
148 CALGRAY => {
149 let cal_dict = iter.next::<Dict>()?;
150 return Some(ColorSpaceType::CalGray(CalGray::new(&cal_dict)?));
151 }
152 CALRGB => {
153 let cal_dict = iter.next::<Dict>()?;
154 return Some(ColorSpaceType::CalRgb(CalRgb::new(&cal_dict)?));
155 }
156 DEVICE_RGB | RGB => return Some(ColorSpaceType::DeviceRgb),
157 DEVICE_GRAY | G => return Some(ColorSpaceType::DeviceGray),
158 DEVICE_CMYK | CMYK => return Some(ColorSpaceType::DeviceCmyk),
159 LAB => {
160 let lab_dict = iter.next::<Dict>()?;
161 return Some(ColorSpaceType::Lab(Lab::new(&lab_dict)?));
162 }
163 INDEXED | I => {
164 return Some(ColorSpaceType::Indexed(Indexed::new(&color_array, cache)?));
165 }
166 SEPARATION => {
167 return Some(ColorSpaceType::Separation(Separation::new(
168 &color_array,
169 cache,
170 )?));
171 }
172 DEVICE_N => {
173 return Some(ColorSpaceType::DeviceN(DeviceN::new(&color_array, cache)?));
174 }
175 PATTERN => {
176 let _ = iter.next::<Name>();
177 let cs = iter
178 .next::<Object>()
179 .and_then(|o| ColorSpace::new(o, cache))
180 .unwrap_or(ColorSpace::device_rgb());
181 return Some(ColorSpaceType::Pattern(cs));
182 }
183 _ => {
184 warn!("unsupported color space: {}", name.as_str());
185 return None;
186 }
187 }
188 }
189
190 None
191 }
192
193 fn new_from_name(name: Name) -> Option<Self> {
194 match name.deref() {
195 DEVICE_RGB | RGB => Some(ColorSpaceType::DeviceRgb),
196 DEVICE_GRAY | G => Some(ColorSpaceType::DeviceGray),
197 DEVICE_CMYK | CMYK => Some(ColorSpaceType::DeviceCmyk),
198 CALCMYK => Some(ColorSpaceType::DeviceCmyk),
199 PATTERN => Some(ColorSpaceType::Pattern(ColorSpace::device_rgb())),
200 _ => None,
201 }
202 }
203}
204
205#[derive(Debug, Clone)]
207pub struct ColorSpace(Arc<ColorSpaceType>);
208
209impl ColorSpace {
210 pub(crate) fn new(object: Object, cache: &Cache) -> Option<ColorSpace> {
212 Some(Self(Arc::new(ColorSpaceType::new(object, cache)?)))
213 }
214
215 pub(crate) fn new_from_name(name: Name) -> Option<ColorSpace> {
217 ColorSpaceType::new_from_name(name).map(|c| Self(Arc::new(c)))
218 }
219
220 pub(crate) fn device_gray() -> ColorSpace {
222 Self(Arc::new(ColorSpaceType::DeviceGray))
223 }
224
225 pub(crate) fn device_rgb() -> ColorSpace {
227 Self(Arc::new(ColorSpaceType::DeviceRgb))
228 }
229
230 pub(crate) fn device_cmyk() -> ColorSpace {
232 Self(Arc::new(ColorSpaceType::DeviceCmyk))
233 }
234
235 pub(crate) fn pattern() -> ColorSpace {
237 Self(Arc::new(ColorSpaceType::Pattern(ColorSpace::device_gray())))
238 }
239
240 pub(crate) fn pattern_cs(&self) -> Option<ColorSpace> {
241 match self.0.as_ref() {
242 ColorSpaceType::Pattern(cs) => Some(cs.clone()),
243 _ => None,
244 }
245 }
246
247 pub(crate) fn is_pattern(&self) -> bool {
249 matches!(self.0.as_ref(), ColorSpaceType::Pattern(_))
250 }
251
252 pub(crate) fn is_indexed(&self) -> bool {
254 matches!(self.0.as_ref(), ColorSpaceType::Indexed(_))
255 }
256
257 pub(crate) fn is_rgb(&self) -> bool {
259 matches!(self.0.as_ref(), ColorSpaceType::DeviceRgb)
260 }
261
262 pub(crate) fn default_decode_arr(&self, n: f32) -> SmallVec<[(f32, f32); 4]> {
264 match self.0.as_ref() {
265 ColorSpaceType::DeviceCmyk => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
266 ColorSpaceType::DeviceGray => smallvec![(0.0, 1.0)],
267 ColorSpaceType::DeviceRgb => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
268 ColorSpaceType::ICCBased(i) => smallvec![(0.0, 1.0); i.0.number_components],
269 ColorSpaceType::CalGray(_) => smallvec![(0.0, 1.0)],
270 ColorSpaceType::CalRgb(_) => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
271 ColorSpaceType::Lab(l) => smallvec![
272 (0.0, 100.0),
273 (l.range[0], l.range[1]),
274 (l.range[2], l.range[3]),
275 ],
276 ColorSpaceType::Indexed(_) => smallvec![(0.0, 2.0f32.powf(n) - 1.0)],
277 ColorSpaceType::Separation(_) => smallvec![(0.0, 1.0)],
278 ColorSpaceType::DeviceN(d) => smallvec![(0.0, 1.0); d.num_components],
279 ColorSpaceType::Pattern(_) => smallvec![(0.0, 1.0)],
281 }
282 }
283
284 pub(crate) fn initial_color(&self) -> ColorComponents {
286 match self.0.as_ref() {
287 ColorSpaceType::DeviceCmyk => smallvec![0.0, 0.0, 0.0, 1.0],
288 ColorSpaceType::DeviceGray => smallvec![0.0],
289 ColorSpaceType::DeviceRgb => smallvec![0.0, 0.0, 0.0],
290 ColorSpaceType::ICCBased(icc) => match icc.0.number_components {
291 1 => smallvec![0.0],
292 3 => smallvec![0.0, 0.0, 0.0],
293 4 => smallvec![0.0, 0.0, 0.0, 1.0],
294 _ => unreachable!(),
295 },
296 ColorSpaceType::CalGray(_) => smallvec![0.0],
297 ColorSpaceType::CalRgb(_) => smallvec![0.0, 0.0, 0.0],
298 ColorSpaceType::Lab(_) => smallvec![0.0, 0.0, 0.0],
299 ColorSpaceType::Indexed(_) => smallvec![0.0],
300 ColorSpaceType::Separation(_) => smallvec![1.0],
301 ColorSpaceType::Pattern(c) => c.initial_color(),
302 ColorSpaceType::DeviceN(d) => smallvec![1.0; d.num_components],
303 }
304 }
305
306 pub(crate) fn num_components(&self) -> u8 {
308 match self.0.as_ref() {
309 ColorSpaceType::DeviceCmyk => 4,
310 ColorSpaceType::DeviceGray => 1,
311 ColorSpaceType::DeviceRgb => 3,
312 ColorSpaceType::ICCBased(icc) => icc.0.number_components as u8,
313 ColorSpaceType::CalGray(_) => 1,
314 ColorSpaceType::CalRgb(_) => 3,
315 ColorSpaceType::Lab(_) => 3,
316 ColorSpaceType::Indexed(_) => 1,
317 ColorSpaceType::Separation(_) => 1,
318 ColorSpaceType::Pattern(p) => p.num_components(),
319 ColorSpaceType::DeviceN(d) => d.num_components as u8,
320 }
321 }
322
323 pub fn to_rgba(&self, c: &[f32], opacity: f32, manual_scale: bool) -> AlphaColor {
325 self.to_rgba_inner(c, opacity, manual_scale)
326 .unwrap_or(AlphaColor::BLACK)
327 }
328
329 fn to_rgba_inner(&self, c: &[f32], opacity: f32, manual_scale: bool) -> Option<AlphaColor> {
330 let color = match self.0.as_ref() {
331 ColorSpaceType::DeviceRgb => {
332 AlphaColor::new([*c.first()?, *c.get(1)?, *c.get(2)?, opacity])
333 }
334 ColorSpaceType::DeviceGray => {
335 AlphaColor::new([*c.first()?, *c.first()?, *c.first()?, opacity])
336 }
337 ColorSpaceType::DeviceCmyk => {
338 let opacity = f32_to_u8(opacity);
339 let srgb = CMYK_TRANSFORM.to_rgb(c)?;
340
341 AlphaColor::from_rgba8(srgb[0], srgb[1], srgb[2], opacity)
342 }
343 ColorSpaceType::ICCBased(icc) => {
344 let opacity = f32_to_u8(opacity);
345 let srgb = icc.to_rgb(c)?;
346
347 AlphaColor::from_rgba8(srgb[0], srgb[1], srgb[2], opacity)
348 }
349 ColorSpaceType::CalGray(cal) => {
350 let opacity = f32_to_u8(opacity);
351 let srgb = cal.to_rgb(*c.first()?);
352
353 AlphaColor::from_rgba8(srgb[0], srgb[1], srgb[2], opacity)
354 }
355 ColorSpaceType::CalRgb(cal) => {
356 let opacity = f32_to_u8(opacity);
357 let srgb = cal.to_rgb([*c.first()?, *c.get(1)?, *c.get(2)?]);
358
359 AlphaColor::from_rgba8(srgb[0], srgb[1], srgb[2], opacity)
360 }
361 ColorSpaceType::Lab(lab) => {
362 let opacity = f32_to_u8(opacity);
363 let srgb = lab.to_rgb([*c.first()?, *c.get(1)?, *c.get(2)?], manual_scale);
364
365 AlphaColor::from_rgba8(srgb[0], srgb[1], srgb[2], opacity)
366 }
367 ColorSpaceType::Indexed(i) => i.to_rgb(*c.first()?, opacity),
368 ColorSpaceType::Separation(s) => s.to_rgba(*c.first()?, opacity),
369 ColorSpaceType::Pattern(_) => AlphaColor::BLACK,
370 ColorSpaceType::DeviceN(d) => d.to_rgba(c, opacity),
371 };
372
373 Some(color)
374 }
375}
376
377#[derive(Debug, Clone)]
378struct CalGray {
379 white_point: [f32; 3],
380 black_point: [f32; 3],
381 gamma: f32,
382}
383
384impl CalGray {
386 fn new(dict: &Dict) -> Option<Self> {
387 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
388 let black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
389 let gamma = dict.get::<f32>(GAMMA).unwrap_or(1.0);
390
391 Some(Self {
392 white_point,
393 black_point,
394 gamma,
395 })
396 }
397
398 fn to_rgb(&self, c: f32) -> [u8; 3] {
399 let g = self.gamma;
400 let (_xw, yw, _zw) = {
401 let wp = self.white_point;
402 (wp[0], wp[1], wp[2])
403 };
404 let (_xb, _yb, _zb) = {
405 let bp = self.black_point;
406 (bp[0], bp[1], bp[2])
407 };
408
409 let a = c;
410 let ag = a.powf(g);
411 let l = yw * ag;
412 let val = (0.0f32.max(295.8 * l.powf(0.333_333_34) - 40.8) + 0.5) as u8;
413
414 [val, val, val]
415 }
416}
417
418#[derive(Debug, Clone)]
419struct CalRgb {
420 white_point: [f32; 3],
421 black_point: [f32; 3],
422 matrix: [f32; 9],
423 gamma: [f32; 3],
424}
425
426impl CalRgb {
431 fn new(dict: &Dict) -> Option<Self> {
432 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
433 let black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
434 let matrix = dict
435 .get::<[f32; 9]>(MATRIX)
436 .unwrap_or([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
437 let gamma = dict.get::<[f32; 3]>(GAMMA).unwrap_or([1.0, 1.0, 1.0]);
438
439 Some(Self {
440 white_point,
441 black_point,
442 matrix,
443 gamma,
444 })
445 }
446
447 const BRADFORD_SCALE_MATRIX: [f32; 9] = [
448 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296,
449 ];
450
451 const BRADFORD_SCALE_INVERSE_MATRIX: [f32; 9] = [
452 0.9869929, -0.1470543, 0.1599627, 0.4323053, 0.5183603, 0.0492912, -0.0085287, 0.0400428,
453 0.9684867,
454 ];
455
456 const SRGB_D65_XYZ_TO_RGB_MATRIX: [f32; 9] = [
457 3.2404542, -1.5371385, -0.4985314, -0.969_266, 1.8760108, 0.0415560, 0.0556434, -0.2040259,
458 1.0572252,
459 ];
460
461 const FLAT_WHITEPOINT: [f32; 3] = [1.0, 1.0, 1.0];
462 const D65_WHITEPOINT: [f32; 3] = [0.95047, 1.0, 1.08883];
463
464 fn decode_l_constant() -> f32 {
465 ((8.0f32 + 16.0) / 116.0).powi(3) / 8.0
466 }
467
468 fn srgb_transfer_function(color: f32) -> f32 {
469 if color <= 0.0031308 {
470 (12.92 * color).clamp(0.0, 1.0)
471 } else if color >= 0.99554525 {
472 1.0
473 } else {
474 ((1.0 + 0.055) * color.powf(1.0 / 2.4) - 0.055).clamp(0.0, 1.0)
475 }
476 }
477
478 fn matrix_product(a: &[f32; 9], b: &[f32; 3]) -> [f32; 3] {
479 [
480 a[0] * b[0] + a[1] * b[1] + a[2] * b[2],
481 a[3] * b[0] + a[4] * b[1] + a[5] * b[2],
482 a[6] * b[0] + a[7] * b[1] + a[8] * b[2],
483 ]
484 }
485
486 fn to_flat(source_white_point: &[f32; 3], lms: &[f32; 3]) -> [f32; 3] {
487 [
488 lms[0] / source_white_point[0],
489 lms[1] / source_white_point[1],
490 lms[2] / source_white_point[2],
491 ]
492 }
493
494 fn to_d65(source_white_point: &[f32; 3], lms: &[f32; 3]) -> [f32; 3] {
495 [
496 lms[0] * Self::D65_WHITEPOINT[0] / source_white_point[0],
497 lms[1] * Self::D65_WHITEPOINT[1] / source_white_point[1],
498 lms[2] * Self::D65_WHITEPOINT[2] / source_white_point[2],
499 ]
500 }
501
502 fn decode_l(l: f32) -> f32 {
503 if l < 0.0 {
504 -Self::decode_l(-l)
505 } else if l > 8.0 {
506 ((l + 16.0) / 116.0).powi(3)
507 } else {
508 l * Self::decode_l_constant()
509 }
510 }
511
512 fn compensate_black_point(source_bp: &[f32; 3], xyz_flat: &[f32; 3]) -> [f32; 3] {
513 if source_bp == &[0.0, 0.0, 0.0] {
514 return *xyz_flat;
515 }
516
517 let zero_decode_l = Self::decode_l(0.0);
518
519 let mut out = [0.0; 3];
520 for i in 0..3 {
521 let src = Self::decode_l(source_bp[i]);
522 let scale = (1.0 - zero_decode_l) / (1.0 - src);
523 let offset = 1.0 - scale;
524 out[i] = xyz_flat[i] * scale + offset;
525 }
526
527 out
528 }
529
530 fn normalize_white_point_to_flat(
531 &self,
532 source_white_point: &[f32; 3],
533 xyz: &[f32; 3],
534 ) -> [f32; 3] {
535 if source_white_point[0] == 1.0 && source_white_point[2] == 1.0 {
536 return *xyz;
537 }
538 let lms = Self::matrix_product(&Self::BRADFORD_SCALE_MATRIX, xyz);
539 let lms_flat = Self::to_flat(source_white_point, &lms);
540 Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_flat)
541 }
542
543 fn normalize_white_point_to_d65(
544 &self,
545 source_white_point: &[f32; 3],
546 xyz: &[f32; 3],
547 ) -> [f32; 3] {
548 let lms = Self::matrix_product(&Self::BRADFORD_SCALE_MATRIX, xyz);
549 let lms_d65 = Self::to_d65(source_white_point, &lms);
550 Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_d65)
551 }
552
553 fn to_rgb(&self, mut c: [f32; 3]) -> [u8; 3] {
554 for i in &mut c {
555 *i = i.clamp(0.0, 1.0);
556 }
557
558 let [r, g, b] = c;
559 let [gr, gg, gb] = self.gamma;
560 let [agr, bgg, cgb] = [
561 if r == 1.0 { 1.0 } else { r.powf(gr) },
562 if g == 1.0 { 1.0 } else { g.powf(gg) },
563 if b == 1.0 { 1.0 } else { b.powf(gb) },
564 ];
565
566 let m = &self.matrix;
567 let x = m[0] * agr + m[3] * bgg + m[6] * cgb;
568 let y = m[1] * agr + m[4] * bgg + m[7] * cgb;
569 let z = m[2] * agr + m[5] * bgg + m[8] * cgb;
570 let xyz = [x, y, z];
571
572 let xyz_flat = self.normalize_white_point_to_flat(&self.white_point, &xyz);
573 let xyz_black = Self::compensate_black_point(&self.black_point, &xyz_flat);
574 let xyz_d65 = self.normalize_white_point_to_d65(&Self::FLAT_WHITEPOINT, &xyz_black);
575 let srgb_xyz = Self::matrix_product(&Self::SRGB_D65_XYZ_TO_RGB_MATRIX, &xyz_d65);
576
577 [
578 (Self::srgb_transfer_function(srgb_xyz[0]) * 255.0 + 0.5) as u8,
579 (Self::srgb_transfer_function(srgb_xyz[1]) * 255.0 + 0.5) as u8,
580 (Self::srgb_transfer_function(srgb_xyz[2]) * 255.0 + 0.5) as u8,
581 ]
582 }
583}
584
585#[derive(Debug, Clone)]
586struct Lab {
587 white_point: [f32; 3],
588 _black_point: [f32; 3],
589 range: [f32; 4],
590}
591
592impl Lab {
593 fn new(dict: &Dict) -> Option<Self> {
594 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
595 let black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
596 let range = dict
597 .get::<[f32; 4]>(RANGE)
598 .unwrap_or([-100.0, 100.0, -100.0, 100.0]);
599
600 Some(Self {
601 white_point,
602 _black_point: black_point,
603 range,
604 })
605 }
606
607 fn fn_g(x: f32) -> f32 {
608 if x >= 6.0 / 29.0 {
609 x.powi(3)
610 } else {
611 (108.0 / 841.0) * (x - 4.0 / 29.0)
612 }
613 }
614
615 fn to_rgb(&self, c: [f32; 3], manual_scale: bool) -> [u8; 3] {
616 let (mut l, mut a, mut b) = (c[0], c[1], c[2]);
617
618 if manual_scale {
621 l *= 100.0;
622 a = self.range[0] + a * (self.range[1] - self.range[0]);
623 b = self.range[2] + b * (self.range[3] - self.range[2]);
624 }
625
626 let m = (l + 16.0) / 116.0;
627 let l = m + a / 500.0;
628 let n = m - b / 200.0;
629
630 let x = self.white_point[0] * Self::fn_g(l);
631 let y = self.white_point[1] * Self::fn_g(m);
632 let z = self.white_point[2] * Self::fn_g(n);
633
634 let (r, g, b) = if self.white_point[2] < 1.0 {
635 (
636 x * 3.1339 + y * -1.617 + z * -0.4906,
637 x * -0.9785 + y * 1.916 + z * 0.0333,
638 x * 0.072 + y * -0.229 + z * 1.4057,
639 )
640 } else {
641 (
642 x * 3.2406 + y * -1.5372 + z * -0.4986,
643 x * -0.9689 + y * 1.8758 + z * 0.0415,
644 x * 0.0557 + y * -0.204 + z * 1.057,
645 )
646 };
647
648 let conv = |v: f32| (v.max(0.0).sqrt() * 255.0).clamp(0.0, 255.0) as u8;
649
650 [conv(r), conv(g), conv(b)]
651 }
652}
653
654#[derive(Debug, Clone)]
655struct Indexed {
656 values: Vec<Vec<f32>>,
657 hival: u8,
658 base: Box<ColorSpace>,
659}
660
661impl Indexed {
662 fn new(array: &Array, cache: &Cache) -> Option<Self> {
663 let mut iter = array.flex_iter();
664 let _ = iter.next::<Name>()?;
666 let base_color_space = ColorSpace::new(iter.next::<Object>()?, cache)?;
667 let hival = iter.next::<u8>()?;
668
669 let values = {
670 let data = iter
671 .next::<Stream>()
672 .and_then(|s| s.decoded().ok())
673 .or_else(|| iter.next::<object::String>().map(|s| s.get().to_vec()))?;
674
675 let num_components = base_color_space.num_components();
676
677 let mut byte_iter = data.iter().copied();
678
679 let mut vals = vec![];
680 for _ in 0..=hival {
681 let mut temp = vec![];
682
683 for _ in 0..num_components {
684 temp.push(byte_iter.next()? as f32 / 255.0)
685 }
686
687 vals.push(temp);
688 }
689
690 vals
691 };
692
693 Some(Self {
694 values,
695 hival,
696 base: Box::new(base_color_space),
697 })
698 }
699
700 pub fn to_rgb(&self, val: f32, opacity: f32) -> AlphaColor {
701 let idx = (val.clamp(0.0, self.hival as f32) + 0.5) as usize;
702 self.base
703 .to_rgba(self.values[idx].as_slice(), opacity, true)
704 }
705}
706
707#[derive(Debug, Clone)]
708struct Separation {
709 alternate_space: ColorSpace,
710 tint_transform: Function,
711}
712
713impl Separation {
714 fn new(array: &Array, cache: &Cache) -> Option<Self> {
715 let mut iter = array.flex_iter();
716 let _ = iter.next::<Name>()?;
718 let name = iter.next::<Name>()?;
719 let alternate_space = ColorSpace::new(iter.next::<Object>()?, cache)?;
720 let tint_transform = Function::new(&iter.next::<Object>()?)?;
721
722 if matches!(name.as_str(), "All" | "None") {
723 warn!("Separation color spaces with `All` or `None` as name are not supported yet");
724 }
725
726 Some(Self {
727 alternate_space,
728 tint_transform,
729 })
730 }
731
732 fn to_rgba(&self, c: f32, opacity: f32) -> AlphaColor {
733 let res = self
734 .tint_transform
735 .eval(smallvec![c])
736 .unwrap_or(self.alternate_space.initial_color());
737
738 self.alternate_space.to_rgba(&res, opacity, false)
739 }
740}
741
742#[derive(Debug, Clone)]
743struct DeviceN {
744 alternate_space: ColorSpace,
745 num_components: usize,
746 tint_transform: Function,
747}
748
749impl DeviceN {
750 fn new(array: &Array, cache: &Cache) -> Option<Self> {
751 let mut iter = array.flex_iter();
752 let _ = iter.next::<Name>()?;
754 let num_components = iter.next::<Array>()?.iter::<Name>().count();
756 let alternate_space = ColorSpace::new(iter.next::<Object>()?, cache)?;
757 let tint_transform = Function::new(&iter.next::<Object>()?)?;
758
759 Some(Self {
760 alternate_space,
761 num_components,
762 tint_transform,
763 })
764 }
765
766 fn to_rgba(&self, c: &[f32], opacity: f32) -> AlphaColor {
767 let res = self
768 .tint_transform
769 .eval(c.to_smallvec())
770 .unwrap_or(self.alternate_space.initial_color());
771 self.alternate_space.to_rgba(&res, opacity, false)
772 }
773}
774
775struct ICCColorRepr {
776 transform: Box<Transform8BitExecutor>,
777 number_components: usize,
778 is_srgb: bool,
779}
780
781#[derive(Clone)]
782struct ICCProfile(Arc<ICCColorRepr>);
783
784impl Debug for ICCProfile {
785 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
786 write!(f, "ICCColor {{..}}")
787 }
788}
789
790impl ICCProfile {
791 fn new(profile: &[u8], number_components: usize) -> Option<Self> {
792 let src_profile = ColorProfile::new_from_slice(profile).ok()?;
793
794 if src_profile.color_space == DataColorSpace::Lab {
796 return None;
797 }
798
799 let dest_profile = ColorProfile::new_srgb();
800
801 let src_layout = match number_components {
802 1 => Layout::Gray,
803 3 => Layout::Rgb,
804 4 => Layout::Rgba,
805 _ => {
806 warn!("unsupported number of components {number_components} for ICC profile");
807
808 return None;
809 }
810 };
811
812 let transform = src_profile
813 .create_transform_8bit(
814 src_layout,
815 &dest_profile,
816 Layout::Rgb,
817 TransformOptions::default(),
818 )
819 .ok()?;
820
821 const SRGB_MARKER: &[u8] = b"sRGB";
822 let is_srgb = profile
823 .get(52..56)
824 .map(|device_model| device_model == SRGB_MARKER)
825 .unwrap_or(false);
826
827 Some(Self(Arc::new(ICCColorRepr {
828 transform,
829 number_components,
830 is_srgb,
831 })))
832 }
833
834 fn is_srgb(&self) -> bool {
835 self.0.is_srgb
836 }
837
838 fn to_rgb(&self, c: &[f32]) -> Option<[u8; 3]> {
839 let mut srgb = [0, 0, 0];
840
841 match self.0.number_components {
842 1 => self
843 .0
844 .transform
845 .transform(&[f32_to_u8(*c.first()?)], &mut srgb),
846 3 => self.0.transform.transform(
847 &[
848 f32_to_u8(*c.first()?),
849 f32_to_u8(*c.get(1)?),
850 f32_to_u8(*c.get(2)?),
851 ],
852 &mut srgb,
853 ),
854 4 => self.0.transform.transform(
855 &[
856 f32_to_u8(*c.first()?),
857 f32_to_u8(*c.get(1)?),
858 f32_to_u8(*c.get(2)?),
859 f32_to_u8(*c.get(3)?),
860 ],
861 &mut srgb,
862 ),
863 _ => return None,
864 }
865 .ok()?;
866
867 Some(srgb)
868 }
869}
870
871fn f32_to_u8(val: f32) -> u8 {
872 (val * 255.0 + 0.5) as u8
873}
874
875#[derive(Debug, Clone)]
876pub struct Color {
878 color_space: ColorSpace,
879 components: ColorComponents,
880 opacity: f32,
881}
882
883impl Color {
884 pub(crate) fn new(color_space: ColorSpace, components: ColorComponents, opacity: f32) -> Self {
885 Self {
886 color_space,
887 components,
888 opacity,
889 }
890 }
891
892 pub fn to_rgba(&self) -> AlphaColor {
894 self.color_space
895 .to_rgba(&self.components, self.opacity, false)
896 }
897}
898
899static CMYK_TRANSFORM: LazyLock<ICCProfile> = LazyLock::new(|| {
900 ICCProfile::new(include_bytes!("../assets/CGATS001Compat-v2-micro.icc"), 4).unwrap()
901});