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