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