1use crate::cache::{Cache, CacheKey};
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::{
14 ColorProfile, DataColorSpace, Layout, Transform8BitExecutor, TransformF32BitExecutor,
15 TransformOptions, Xyzd,
16};
17use smallvec::{SmallVec, ToSmallVec, smallvec};
18use std::fmt::{Debug, Formatter};
19use std::ops::Deref;
20use std::sync::{Arc, LazyLock};
21
22pub type ColorComponents = SmallVec<[f32; 4]>;
24
25#[derive(Debug, Copy, Clone)]
27pub struct AlphaColor {
28 components: [f32; 4],
29}
30
31impl AlphaColor {
32 pub const BLACK: Self = Self::new([0., 0., 0., 1.]);
34
35 pub const TRANSPARENT: Self = Self::new([0., 0., 0., 0.]);
37
38 pub const WHITE: Self = Self::new([1., 1., 1., 1.]);
40
41 pub const fn new(components: [f32; 4]) -> Self {
43 Self { components }
44 }
45
46 pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
48 let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), 1.];
49 Self::new(components)
50 }
51
52 pub fn premultiplied(&self) -> [f32; 4] {
54 [
55 self.components[0] * self.components[3],
56 self.components[1] * self.components[3],
57 self.components[2] * self.components[3],
58 self.components[3],
59 ]
60 }
61
62 pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
64 let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
65 Self::new(components)
66 }
67
68 pub fn to_rgba8(&self) -> [u8; 4] {
70 [
71 (self.components[0] * 255.0 + 0.5) as u8,
72 (self.components[1] * 255.0 + 0.5) as u8,
73 (self.components[2] * 255.0 + 0.5) as u8,
74 (self.components[3] * 255.0 + 0.5) as u8,
75 ]
76 }
77
78 pub fn components(&self) -> [f32; 4] {
80 self.components
81 }
82}
83
84const fn u8_to_f32(x: u8) -> f32 {
85 x as f32 * (1.0 / 255.0)
86}
87
88#[derive(Debug, Clone)]
89pub(crate) enum ColorSpaceType {
90 DeviceCmyk,
91 DeviceGray,
92 DeviceRgb,
93 Pattern(ColorSpace),
94 Indexed(Indexed),
95 ICCBased(ICCProfile),
96 CalGray(CalGray),
97 CalRgb(CalRgb),
98 Lab(Lab),
99 Separation(Separation),
100 DeviceN(DeviceN),
101}
102
103impl ColorSpaceType {
104 fn new(object: Object<'_>, cache: &Cache) -> Option<Self> {
105 Self::new_inner(object, cache)
106 }
107
108 fn new_inner(object: Object<'_>, cache: &Cache) -> Option<Self> {
109 if let Some(name) = object.clone().into_name() {
110 return Self::new_from_name(name.clone());
111 } else if let Some(color_array) = object.clone().into_array() {
112 let mut iter = color_array.clone().flex_iter();
113 let name = iter.next::<Name<'_>>()?;
114
115 match name.deref() {
116 ICC_BASED => {
117 let icc_stream = iter.next::<Stream<'_>>()?;
118 let dict = icc_stream.dict();
119 let num_components = dict.get::<usize>(N)?;
120
121 return cache.get_or_insert_with(icc_stream.cache_key(), || {
122 if let Some(decoded) = icc_stream.decoded().ok().as_ref() {
123 ICCProfile::new(decoded, num_components)
124 .map(|icc| {
125 if icc.is_srgb() {
130 Self::DeviceRgb
131 } else {
132 Self::ICCBased(icc)
133 }
134 })
135 .or_else(|| {
136 dict.get::<Object<'_>>(ALTERNATE)
137 .and_then(|o| Self::new(o, cache))
138 })
139 .or_else(|| match dict.get::<u8>(N) {
140 Some(1) => Some(Self::DeviceGray),
141 Some(3) => Some(Self::DeviceRgb),
142 Some(4) => Some(Self::DeviceCmyk),
143 _ => None,
144 })
145 } else {
146 None
147 }
148 });
149 }
150 CALCMYK => return Some(Self::DeviceCmyk),
151 CALGRAY => {
152 let cal_dict = iter.next::<Dict<'_>>()?;
153 return Some(Self::CalGray(CalGray::new(&cal_dict)?));
154 }
155 CALRGB => {
156 let cal_dict = iter.next::<Dict<'_>>()?;
157 return Some(Self::CalRgb(CalRgb::new(&cal_dict)?));
158 }
159 DEVICE_RGB | RGB => return Some(Self::DeviceRgb),
160 DEVICE_GRAY | G => return Some(Self::DeviceGray),
161 DEVICE_CMYK | CMYK => return Some(Self::DeviceCmyk),
162 LAB => {
163 let lab_dict = iter.next::<Dict<'_>>()?;
164 return Some(Self::Lab(Lab::new(&lab_dict)?));
165 }
166 INDEXED | I => {
167 return Some(Self::Indexed(Indexed::new(&color_array, cache)?));
168 }
169 SEPARATION => {
170 return Some(Self::Separation(Separation::new(&color_array, cache)?));
171 }
172 DEVICE_N => {
173 return Some(Self::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(Self::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(Self::DeviceRgb),
196 DEVICE_GRAY | G => Some(Self::DeviceGray),
197 DEVICE_CMYK | CMYK => Some(Self::DeviceCmyk),
198 CALCMYK => Some(Self::DeviceCmyk),
199 PATTERN => Some(Self::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<Self> {
212 Some(Self(Arc::new(ColorSpaceType::new(object, cache)?)))
213 }
214
215 pub(crate) fn new_from_name(name: Name<'_>) -> Option<Self> {
217 ColorSpaceType::new_from_name(name).map(|c| Self(Arc::new(c)))
218 }
219
220 pub(crate) fn device_gray() -> Self {
222 Self(Arc::new(ColorSpaceType::DeviceGray))
223 }
224
225 pub(crate) fn device_rgb() -> Self {
227 Self(Arc::new(ColorSpaceType::DeviceRgb))
228 }
229
230 pub(crate) fn device_cmyk() -> Self {
232 Self(Arc::new(ColorSpaceType::DeviceCmyk))
233 }
234
235 pub(crate) fn pattern() -> Self {
237 Self(Arc::new(ColorSpaceType::Pattern(Self::device_gray())))
238 }
239
240 pub(crate) fn pattern_cs(&self) -> Option<Self> {
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 default_decode_arr(&self, n: f32) -> SmallVec<[(f32, f32); 4]> {
259 match self.0.as_ref() {
260 ColorSpaceType::DeviceCmyk => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
261 ColorSpaceType::DeviceGray => smallvec![(0.0, 1.0)],
262 ColorSpaceType::DeviceRgb => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
263 ColorSpaceType::ICCBased(i) => smallvec![(0.0, 1.0); i.0.number_components],
264 ColorSpaceType::CalGray(_) => smallvec![(0.0, 1.0)],
265 ColorSpaceType::CalRgb(_) => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
266 ColorSpaceType::Lab(l) => smallvec![
267 (0.0, 100.0),
268 (l.range[0], l.range[1]),
269 (l.range[2], l.range[3]),
270 ],
271 ColorSpaceType::Indexed(_) => smallvec![(0.0, 2.0_f32.powf(n) - 1.0)],
272 ColorSpaceType::Separation(_) => smallvec![(0.0, 1.0)],
273 ColorSpaceType::DeviceN(d) => smallvec![(0.0, 1.0); d.num_components as usize],
274 ColorSpaceType::Pattern(_) => smallvec![(0.0, 1.0)],
276 }
277 }
278
279 pub(crate) fn initial_color(&self) -> ColorComponents {
281 match self.0.as_ref() {
282 ColorSpaceType::DeviceCmyk => smallvec![0.0, 0.0, 0.0, 1.0],
283 ColorSpaceType::DeviceGray => smallvec![0.0],
284 ColorSpaceType::DeviceRgb => smallvec![0.0, 0.0, 0.0],
285 ColorSpaceType::ICCBased(icc) => match icc.0.number_components {
286 1 => smallvec![0.0],
287 3 => smallvec![0.0, 0.0, 0.0],
288 4 => smallvec![0.0, 0.0, 0.0, 1.0],
289 _ => unreachable!(),
290 },
291 ColorSpaceType::CalGray(_) => smallvec![0.0],
292 ColorSpaceType::CalRgb(_) => smallvec![0.0, 0.0, 0.0],
293 ColorSpaceType::Lab(_) => smallvec![0.0, 0.0, 0.0],
294 ColorSpaceType::Indexed(_) => smallvec![0.0],
295 ColorSpaceType::Separation(_) => smallvec![1.0],
296 ColorSpaceType::Pattern(c) => c.initial_color(),
297 ColorSpaceType::DeviceN(d) => smallvec![1.0; d.num_components as usize],
298 }
299 }
300
301 pub(crate) fn num_components(&self) -> u8 {
303 match self.0.as_ref() {
304 ColorSpaceType::DeviceCmyk => 4,
305 ColorSpaceType::DeviceGray => 1,
306 ColorSpaceType::DeviceRgb => 3,
307 ColorSpaceType::ICCBased(icc) => icc.0.number_components as u8,
308 ColorSpaceType::CalGray(_) => 1,
309 ColorSpaceType::CalRgb(_) => 3,
310 ColorSpaceType::Lab(_) => 3,
311 ColorSpaceType::Indexed(_) => 1,
312 ColorSpaceType::Separation(_) => 1,
313 ColorSpaceType::Pattern(p) => p.num_components(),
314 ColorSpaceType::DeviceN(d) => d.num_components,
315 }
316 }
317
318 pub fn to_rgba(&self, c: &[f32], opacity: f32, manual_scale: bool) -> AlphaColor {
320 self.to_alpha_color(c, opacity, manual_scale)
321 .unwrap_or(AlphaColor::BLACK)
322 }
323}
324
325impl ToRgb for ColorSpace {
326 fn convert_f32(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()> {
327 match self.0.as_ref() {
328 ColorSpaceType::DeviceCmyk => {
329 let converted = input.iter().copied().map(f32_to_u8).collect::<Vec<_>>();
330 CMYK_TRANSFORM.convert_u8(&converted, output)
331 }
332 ColorSpaceType::DeviceGray => {
333 let converted = input.iter().copied().map(f32_to_u8).collect::<Vec<_>>();
334
335 for (input, output) in converted.iter().zip(output.chunks_exact_mut(3)) {
336 output.copy_from_slice(&[*input, *input, *input]);
337 }
338
339 Some(())
340 }
341 ColorSpaceType::DeviceRgb => {
342 for (input, output) in input.iter().copied().zip(output) {
343 *output = f32_to_u8(input);
344 }
345
346 Some(())
347 }
348 ColorSpaceType::Pattern(i) => i.convert_f32(input, output, manual_scale),
349 ColorSpaceType::Indexed(i) => i.convert_f32(input, output, manual_scale),
350 ColorSpaceType::ICCBased(i) => i.convert_f32(input, output, manual_scale),
351 ColorSpaceType::CalGray(i) => i.convert_f32(input, output, manual_scale),
352 ColorSpaceType::CalRgb(i) => i.convert_f32(input, output, manual_scale),
353 ColorSpaceType::Lab(i) => i.convert_f32(input, output, manual_scale),
354 ColorSpaceType::Separation(i) => i.convert_f32(input, output, manual_scale),
355 ColorSpaceType::DeviceN(i) => i.convert_f32(input, output, manual_scale),
356 }
357 }
358
359 fn supports_u8(&self) -> bool {
360 match self.0.as_ref() {
361 ColorSpaceType::DeviceCmyk => true,
362 ColorSpaceType::DeviceGray => true,
363 ColorSpaceType::DeviceRgb => true,
364 ColorSpaceType::Pattern(i) => i.supports_u8(),
365 ColorSpaceType::Indexed(i) => i.supports_u8(),
366 ColorSpaceType::ICCBased(i) => i.supports_u8(),
367 ColorSpaceType::CalGray(i) => i.supports_u8(),
368 ColorSpaceType::CalRgb(i) => i.supports_u8(),
369 ColorSpaceType::Lab(i) => i.supports_u8(),
370 ColorSpaceType::Separation(i) => i.supports_u8(),
371 ColorSpaceType::DeviceN(i) => i.supports_u8(),
372 }
373 }
374
375 fn convert_u8(&self, input: &[u8], output: &mut [u8]) -> Option<()> {
376 match self.0.as_ref() {
377 ColorSpaceType::DeviceCmyk => CMYK_TRANSFORM.convert_u8(input, output),
378 ColorSpaceType::DeviceGray => {
379 for (input, output) in input.iter().zip(output.chunks_exact_mut(3)) {
380 output.copy_from_slice(&[*input, *input, *input]);
381 }
382
383 Some(())
384 }
385 ColorSpaceType::DeviceRgb => {
386 output.copy_from_slice(input);
387
388 Some(())
389 }
390 ColorSpaceType::Pattern(i) => i.convert_u8(input, output),
391 ColorSpaceType::Indexed(i) => i.convert_u8(input, output),
392 ColorSpaceType::ICCBased(i) => i.convert_u8(input, output),
393 ColorSpaceType::CalGray(i) => i.convert_u8(input, output),
394 ColorSpaceType::CalRgb(i) => i.convert_u8(input, output),
395 ColorSpaceType::Lab(i) => i.convert_u8(input, output),
396 ColorSpaceType::Separation(i) => i.convert_u8(input, output),
397 ColorSpaceType::DeviceN(i) => i.convert_u8(input, output),
398 }
399 }
400
401 fn is_none(&self) -> bool {
402 match self.0.as_ref() {
403 ColorSpaceType::Separation(s) => s.is_none(),
404 ColorSpaceType::DeviceN(d) => d.is_none(),
405 _ => false,
406 }
407 }
408}
409
410#[derive(Debug, Clone)]
411pub(crate) struct CalGray {
412 white_point: [f32; 3],
413 black_point: [f32; 3],
414 gamma: f32,
415}
416
417impl CalGray {
419 fn new(dict: &Dict<'_>) -> Option<Self> {
420 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
421 let black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
422 let gamma = dict.get::<f32>(GAMMA).unwrap_or(1.0);
423
424 Some(Self {
425 white_point,
426 black_point,
427 gamma,
428 })
429 }
430}
431
432impl ToRgb for CalGray {
433 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
434 for (input, output) in input.iter().copied().zip(output.chunks_exact_mut(3)) {
435 let g = self.gamma;
436 let (_xw, yw, _zw) = {
437 let wp = self.white_point;
438 (wp[0], wp[1], wp[2])
439 };
440 let (_xb, _yb, _zb) = {
441 let bp = self.black_point;
442 (bp[0], bp[1], bp[2])
443 };
444
445 let a = input;
446 let ag = a.powf(g);
447 let l = yw * ag;
448 let val = (0.0_f32.max(295.8 * l.powf(0.333_333_34) - 40.8) + 0.5) as u8;
449
450 output.copy_from_slice(&[val, val, val]);
451 }
452
453 Some(())
454 }
455}
456
457#[derive(Debug, Clone)]
458pub(crate) struct CalRgb {
459 white_point: [f32; 3],
460 black_point: [f32; 3],
461 matrix: [f32; 9],
462 gamma: [f32; 3],
463}
464
465impl CalRgb {
470 fn new(dict: &Dict<'_>) -> Option<Self> {
471 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
472 let black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
473 let matrix = dict
474 .get::<[f32; 9]>(MATRIX)
475 .unwrap_or([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
476 let gamma = dict.get::<[f32; 3]>(GAMMA).unwrap_or([1.0, 1.0, 1.0]);
477
478 Some(Self {
479 white_point,
480 black_point,
481 matrix,
482 gamma,
483 })
484 }
485
486 const BRADFORD_SCALE_MATRIX: [f32; 9] = [
487 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296,
488 ];
489
490 const BRADFORD_SCALE_INVERSE_MATRIX: [f32; 9] = [
491 0.9869929, -0.1470543, 0.1599627, 0.4323053, 0.5183603, 0.0492912, -0.0085287, 0.0400428,
492 0.9684867,
493 ];
494
495 const SRGB_D65_XYZ_TO_RGB_MATRIX: [f32; 9] = [
496 3.2404542, -1.5371385, -0.4985314, -0.969_266, 1.8760108, 0.0415560, 0.0556434, -0.2040259,
497 1.0572252,
498 ];
499
500 const FLAT_WHITEPOINT: [f32; 3] = [1.0, 1.0, 1.0];
501 const D65_WHITEPOINT: [f32; 3] = [0.95047, 1.0, 1.08883];
502
503 fn decode_l_constant() -> f32 {
504 ((8.0_f32 + 16.0) / 116.0).powi(3) / 8.0
505 }
506
507 fn srgb_transfer_function(color: f32) -> f32 {
508 if color <= 0.0031308 {
509 (12.92 * color).clamp(0.0, 1.0)
510 } else if color >= 0.99554525 {
511 1.0
512 } else {
513 ((1.0 + 0.055) * color.powf(1.0 / 2.4) - 0.055).clamp(0.0, 1.0)
514 }
515 }
516
517 fn matrix_product(a: &[f32; 9], b: &[f32; 3]) -> [f32; 3] {
518 [
519 a[0] * b[0] + a[1] * b[1] + a[2] * b[2],
520 a[3] * b[0] + a[4] * b[1] + a[5] * b[2],
521 a[6] * b[0] + a[7] * b[1] + a[8] * b[2],
522 ]
523 }
524
525 fn to_flat(source_white_point: &[f32; 3], lms: &[f32; 3]) -> [f32; 3] {
526 [
527 lms[0] / source_white_point[0],
528 lms[1] / source_white_point[1],
529 lms[2] / source_white_point[2],
530 ]
531 }
532
533 fn to_d65(source_white_point: &[f32; 3], lms: &[f32; 3]) -> [f32; 3] {
534 [
535 lms[0] * Self::D65_WHITEPOINT[0] / source_white_point[0],
536 lms[1] * Self::D65_WHITEPOINT[1] / source_white_point[1],
537 lms[2] * Self::D65_WHITEPOINT[2] / source_white_point[2],
538 ]
539 }
540
541 fn decode_l(l: f32) -> f32 {
542 if l < 0.0 {
543 -Self::decode_l(-l)
544 } else if l > 8.0 {
545 ((l + 16.0) / 116.0).powi(3)
546 } else {
547 l * Self::decode_l_constant()
548 }
549 }
550
551 fn compensate_black_point(source_bp: &[f32; 3], xyz_flat: &[f32; 3]) -> [f32; 3] {
552 if source_bp == &[0.0, 0.0, 0.0] {
553 return *xyz_flat;
554 }
555
556 let zero_decode_l = Self::decode_l(0.0);
557
558 let mut out = [0.0; 3];
559 for i in 0..3 {
560 let src = Self::decode_l(source_bp[i]);
561 let scale = (1.0 - zero_decode_l) / (1.0 - src);
562 let offset = 1.0 - scale;
563 out[i] = xyz_flat[i] * scale + offset;
564 }
565
566 out
567 }
568
569 fn normalize_white_point_to_flat(
570 &self,
571 source_white_point: &[f32; 3],
572 xyz: &[f32; 3],
573 ) -> [f32; 3] {
574 if source_white_point[0] == 1.0 && source_white_point[2] == 1.0 {
575 return *xyz;
576 }
577 let lms = Self::matrix_product(&Self::BRADFORD_SCALE_MATRIX, xyz);
578 let lms_flat = Self::to_flat(source_white_point, &lms);
579 Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_flat)
580 }
581
582 fn normalize_white_point_to_d65(
583 &self,
584 source_white_point: &[f32; 3],
585 xyz: &[f32; 3],
586 ) -> [f32; 3] {
587 let lms = Self::matrix_product(&Self::BRADFORD_SCALE_MATRIX, xyz);
588 let lms_d65 = Self::to_d65(source_white_point, &lms);
589 Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_d65)
590 }
591}
592
593impl ToRgb for CalRgb {
594 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
595 for (input, output) in input.chunks_exact(3).zip(output.chunks_exact_mut(3)) {
596 let input = [
597 input[0].clamp(0.0, 1.0),
598 input[1].clamp(0.0, 1.0),
599 input[2].clamp(0.0, 1.0),
600 ];
601
602 let [r, g, b] = input;
603 let [gr, gg, gb] = self.gamma;
604 let [agr, bgg, cgb] = [
605 if r == 1.0 { 1.0 } else { r.powf(gr) },
606 if g == 1.0 { 1.0 } else { g.powf(gg) },
607 if b == 1.0 { 1.0 } else { b.powf(gb) },
608 ];
609
610 let m = &self.matrix;
611 let x = m[0] * agr + m[3] * bgg + m[6] * cgb;
612 let y = m[1] * agr + m[4] * bgg + m[7] * cgb;
613 let z = m[2] * agr + m[5] * bgg + m[8] * cgb;
614 let xyz = [x, y, z];
615
616 let xyz_flat = self.normalize_white_point_to_flat(&self.white_point, &xyz);
617 let xyz_black = Self::compensate_black_point(&self.black_point, &xyz_flat);
618 let xyz_d65 = self.normalize_white_point_to_d65(&Self::FLAT_WHITEPOINT, &xyz_black);
619 let srgb_xyz = Self::matrix_product(&Self::SRGB_D65_XYZ_TO_RGB_MATRIX, &xyz_d65);
620
621 output.copy_from_slice(&[
622 (Self::srgb_transfer_function(srgb_xyz[0]) * 255.0 + 0.5) as u8,
623 (Self::srgb_transfer_function(srgb_xyz[1]) * 255.0 + 0.5) as u8,
624 (Self::srgb_transfer_function(srgb_xyz[2]) * 255.0 + 0.5) as u8,
625 ]);
626 }
627
628 Some(())
629 }
630}
631
632#[derive(Debug, Clone)]
633pub(crate) struct Lab {
634 range: [f32; 4],
635 profile: ICCProfile,
636}
637
638impl Lab {
639 fn new(dict: &Dict<'_>) -> Option<Self> {
640 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
641 let _black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
643 let range = dict
644 .get::<[f32; 4]>(RANGE)
645 .unwrap_or([-100.0, 100.0, -100.0, 100.0]);
646
647 let mut profile = ColorProfile::new_from_slice(include_bytes!("../assets/LAB.icc")).ok()?;
648 profile.white_point = Xyzd::new(
649 white_point[0] as f64,
650 white_point[1] as f64,
651 white_point[2] as f64,
652 );
653
654 let profile = ICCProfile::new_from_src_profile(
655 profile, false,
656 false, 3,
660 )?;
661
662 Some(Self { range, profile })
663 }
664}
665
666impl ToRgb for Lab {
667 fn convert_f32(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()> {
668 if !manual_scale {
669 let input = input
673 .chunks_exact(3)
674 .flat_map(|i| {
675 let l = i[0] / 100.0;
676 let a = (i[1] + 128.0) / 255.0;
677 let b = (i[2] + 128.0) / 255.0;
678
679 [l, a, b]
680 })
681 .collect::<Vec<_>>();
682
683 self.profile.convert_f32(&input, output, manual_scale)
684 } else {
685 self.profile.convert_f32(input, output, manual_scale)
686 }
687 }
688}
689
690#[derive(Debug, Clone)]
691pub(crate) struct Indexed {
692 values: Vec<Vec<f32>>,
693 hival: u8,
694 base: Box<ColorSpace>,
695}
696
697impl Indexed {
698 fn new(array: &Array<'_>, cache: &Cache) -> Option<Self> {
699 let mut iter = array.flex_iter();
700 let _ = iter.next::<Name<'_>>()?;
702 let base_color_space = ColorSpace::new(iter.next::<Object<'_>>()?, cache)?;
703 let hival = iter.next::<u8>()?;
704
705 let values = {
706 let data = iter
707 .next::<Stream<'_>>()
708 .and_then(|s| s.decoded().ok())
709 .or_else(|| iter.next::<object::String<'_>>().map(|s| s.get().to_vec()))?;
710
711 let num_components = base_color_space.num_components();
712
713 let mut byte_iter = data.iter().copied();
714
715 let mut vals = vec![];
716 for _ in 0..=hival {
717 let mut temp = vec![];
718
719 for _ in 0..num_components {
720 temp.push(byte_iter.next()? as f32 / 255.0);
721 }
722
723 vals.push(temp);
724 }
725
726 vals
727 };
728
729 Some(Self {
730 values,
731 hival,
732 base: Box::new(base_color_space),
733 })
734 }
735}
736
737impl ToRgb for Indexed {
738 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
739 let mut indexed = vec![0.0; input.len() * self.base.num_components() as usize];
740
741 for (input, output) in input
742 .iter()
743 .copied()
744 .zip(indexed.chunks_exact_mut(self.base.num_components() as usize))
745 {
746 let idx = (input.clamp(0.0, self.hival as f32) + 0.5) as usize;
747 output.copy_from_slice(&self.values[idx]);
748 }
749
750 self.base.convert_f32(&indexed, output, true)
751 }
752}
753
754#[derive(Debug, Clone)]
755pub(crate) struct Separation {
756 alternate_space: ColorSpace,
757 tint_transform: Function,
758 is_none_separation: bool,
759}
760
761impl Separation {
762 fn new(array: &Array<'_>, cache: &Cache) -> Option<Self> {
763 let mut iter = array.flex_iter();
764 let _ = iter.next::<Name<'_>>()?;
766 let name = iter.next::<Name<'_>>()?;
767 let alternate_space = ColorSpace::new(iter.next::<Object<'_>>()?, cache)?;
768 let tint_transform = Function::new(&iter.next::<Object<'_>>()?)?;
769 let is_none_separation = name.as_str() == "None";
772
773 Some(Self {
774 alternate_space,
775 tint_transform,
776 is_none_separation,
777 })
778 }
779}
780
781impl ToRgb for Separation {
782 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
783 let evaluated = input
784 .iter()
785 .flat_map(|n| {
786 self.tint_transform
787 .eval(smallvec![*n])
788 .unwrap_or(self.alternate_space.initial_color())
789 })
790 .collect::<Vec<_>>();
791 self.alternate_space.convert_f32(&evaluated, output, false)
792 }
793
794 fn is_none(&self) -> bool {
795 self.is_none_separation
796 }
797}
798
799#[derive(Debug, Clone)]
800pub(crate) struct DeviceN {
801 alternate_space: ColorSpace,
802 num_components: u8,
803 tint_transform: Function,
804 is_none: bool,
805}
806
807impl DeviceN {
808 fn new(array: &Array<'_>, cache: &Cache) -> Option<Self> {
809 let mut iter = array.flex_iter();
810 let _ = iter.next::<Name<'_>>()?;
812 let names = iter
814 .next::<Array<'_>>()?
815 .iter::<Name<'_>>()
816 .collect::<Vec<_>>();
817 let num_components = u8::try_from(names.len()).ok()?;
818 let all_none = names.iter().all(|n| n.as_str() == "None");
819 let alternate_space = ColorSpace::new(iter.next::<Object<'_>>()?, cache)?;
820 let tint_transform = Function::new(&iter.next::<Object<'_>>()?)?;
821
822 if num_components == 0 {
823 return None;
824 }
825
826 Some(Self {
827 alternate_space,
828 num_components,
829 tint_transform,
830 is_none: all_none,
831 })
832 }
833}
834
835impl ToRgb for DeviceN {
836 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
837 let evaluated = input
838 .chunks_exact(self.num_components as usize)
839 .flat_map(|n| {
840 self.tint_transform
841 .eval(n.to_smallvec())
842 .unwrap_or(self.alternate_space.initial_color())
843 })
844 .collect::<Vec<_>>();
845 self.alternate_space.convert_f32(&evaluated, output, false)
846 }
847
848 fn is_none(&self) -> bool {
849 self.is_none
850 }
851}
852
853struct ICCColorRepr {
854 transform_u8: Box<Transform8BitExecutor>,
855 transform_f32: Box<TransformF32BitExecutor>,
856 number_components: usize,
857 is_srgb: bool,
858 is_lab: bool,
859}
860
861#[derive(Clone)]
862pub(crate) struct ICCProfile(Arc<ICCColorRepr>);
863
864impl Debug for ICCProfile {
865 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
866 write!(f, "ICCColor {{..}}")
867 }
868}
869
870impl ICCProfile {
871 fn new(profile: &[u8], number_components: usize) -> Option<Self> {
872 let src_profile = ColorProfile::new_from_slice(profile).ok()?;
873
874 const SRGB_MARKER: &[u8] = b"sRGB";
875
876 let is_srgb = profile
877 .get(52..56)
878 .map(|device_model| device_model == SRGB_MARKER)
879 .unwrap_or(false);
880 let is_lab = src_profile.color_space == DataColorSpace::Lab;
881
882 Self::new_from_src_profile(src_profile, is_srgb, is_lab, number_components)
883 }
884
885 fn new_from_src_profile(
886 src_profile: ColorProfile,
887 is_srgb: bool,
888 is_lab: bool,
889 number_components: usize,
890 ) -> Option<Self> {
891 let dest_profile = ColorProfile::new_srgb();
892
893 let src_layout = match number_components {
894 1 => Layout::Gray,
895 3 => Layout::Rgb,
896 4 => Layout::Rgba,
897 _ => {
898 warn!("unsupported number of components {number_components} for ICC profile");
899
900 return None;
901 }
902 };
903
904 let u8_transform = src_profile
905 .create_transform_8bit(
906 src_layout,
907 &dest_profile,
908 Layout::Rgb,
909 TransformOptions::default(),
910 )
911 .ok()?;
912
913 let f32_transform = src_profile
914 .create_transform_f32(
915 src_layout,
916 &dest_profile,
917 Layout::Rgb,
918 TransformOptions::default(),
919 )
920 .ok()?;
921
922 Some(Self(Arc::new(ICCColorRepr {
923 transform_u8: u8_transform,
924 transform_f32: f32_transform,
925 number_components,
926 is_srgb,
927 is_lab,
928 })))
929 }
930
931 fn is_srgb(&self) -> bool {
932 self.0.is_srgb
933 }
934
935 fn is_lab(&self) -> bool {
936 self.0.is_lab
937 }
938}
939
940impl ToRgb for ICCProfile {
941 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
942 let mut temp = vec![0.0_f32; output.len()];
943
944 if self.is_lab() {
945 let scaled = input
947 .chunks_exact(3)
948 .flat_map(|i| {
949 [
950 i[0] * (1.0 / 100.0),
951 (i[1] + 128.0) * (1.0 / 255.0),
952 (i[2] + 128.0) * (1.0 / 255.0),
953 ]
954 })
955 .collect::<Vec<_>>();
956 self.0.transform_f32.transform(&scaled, &mut temp).ok()?;
957 } else {
958 self.0.transform_f32.transform(input, &mut temp).ok()?;
959 };
960
961 for (input, output) in temp.iter().zip(output.iter_mut()) {
962 *output = (input * 255.0 + 0.5) as u8;
963 }
964
965 Some(())
966 }
967
968 fn supports_u8(&self) -> bool {
969 true
970 }
971
972 fn convert_u8(&self, input: &[u8], output: &mut [u8]) -> Option<()> {
973 if self.is_srgb() {
974 output.copy_from_slice(input);
975 } else {
976 self.0.transform_u8.transform(input, output).ok()?;
977 }
978
979 Some(())
980 }
981}
982
983#[inline(always)]
984fn f32_to_u8(val: f32) -> u8 {
985 (val * 255.0 + 0.5) as u8
986}
987
988#[derive(Debug, Clone)]
989pub struct Color {
991 color_space: ColorSpace,
992 components: ColorComponents,
993 opacity: f32,
994}
995
996impl Color {
997 pub(crate) fn new(color_space: ColorSpace, components: ColorComponents, opacity: f32) -> Self {
998 Self {
999 color_space,
1000 components,
1001 opacity,
1002 }
1003 }
1004
1005 pub fn to_rgba(&self) -> AlphaColor {
1007 self.color_space
1008 .to_rgba(&self.components, self.opacity, false)
1009 }
1010}
1011
1012static CMYK_TRANSFORM: LazyLock<ICCProfile> = LazyLock::new(|| {
1013 ICCProfile::new(include_bytes!("../assets/CGATS001Compat-v2-micro.icc"), 4).unwrap()
1014});
1015
1016pub(crate) trait ToRgb {
1017 fn convert_f32(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()>;
1018 fn supports_u8(&self) -> bool {
1019 false
1020 }
1021 fn convert_u8(&self, _: &[u8], _: &mut [u8]) -> Option<()> {
1022 unimplemented!();
1023 }
1024 fn is_none(&self) -> bool {
1025 false
1026 }
1027 fn to_alpha_color(
1028 &self,
1029 input: &[f32],
1030 mut opacity: f32,
1031 manual_scale: bool,
1032 ) -> Option<AlphaColor> {
1033 let mut output = [0; 3];
1034 self.convert_f32(input, &mut output, manual_scale)?;
1035
1036 if self.is_none() {
1041 opacity = 0.0;
1042 }
1043
1044 Some(AlphaColor::from_rgba8(
1045 output[0],
1046 output[1],
1047 output[2],
1048 (opacity * 255.0 + 0.5) as u8,
1049 ))
1050 }
1051}