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 moxcms::{
13 ColorProfile, DataColorSpace, Layout, Transform8BitExecutor, TransformF32Executor,
14 TransformOptions, Xyzd,
15};
16use smallvec::{SmallVec, ToSmallVec, smallvec};
17use std::borrow::Cow;
18use std::fmt::{Debug, Formatter};
19use std::ops::Deref;
20use std::sync::{Arc, LazyLock, OnceLock};
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 Object::Name(name) = object {
110 return Self::new_from_name(&name);
111 } else if let Object::Array(color_array) = object {
112 let mut iter = color_array.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 inverted_default_decode_arr(&self, n: f32) -> SmallVec<[(f32, f32); 4]> {
280 self.default_decode_arr(n)
281 .iter()
282 .map(|(min, max)| (*max, *min))
283 .collect()
284 }
285
286 pub(crate) fn initial_color(&self) -> ColorComponents {
288 match self.0.as_ref() {
289 ColorSpaceType::DeviceCmyk => smallvec![0.0, 0.0, 0.0, 1.0],
290 ColorSpaceType::DeviceGray => smallvec![0.0],
291 ColorSpaceType::DeviceRgb => smallvec![0.0, 0.0, 0.0],
292 ColorSpaceType::ICCBased(icc) => match icc.0.number_components {
293 1 => smallvec![0.0],
294 3 => smallvec![0.0, 0.0, 0.0],
295 4 => smallvec![0.0, 0.0, 0.0, 1.0],
296 _ => unreachable!(),
297 },
298 ColorSpaceType::CalGray(_) => smallvec![0.0],
299 ColorSpaceType::CalRgb(_) => smallvec![0.0, 0.0, 0.0],
300 ColorSpaceType::Lab(_) => smallvec![0.0, 0.0, 0.0],
301 ColorSpaceType::Indexed(_) => smallvec![0.0],
302 ColorSpaceType::Separation(_) => smallvec![1.0],
303 ColorSpaceType::Pattern(c) => c.initial_color(),
304 ColorSpaceType::DeviceN(d) => smallvec![1.0; d.num_components as usize],
305 }
306 }
307
308 pub(crate) fn is_device_gray(&self) -> bool {
309 matches!(self.0.as_ref(), ColorSpaceType::DeviceGray)
310 }
311
312 pub(crate) fn num_components(&self) -> u8 {
314 match self.0.as_ref() {
315 ColorSpaceType::DeviceCmyk => 4,
316 ColorSpaceType::DeviceGray => 1,
317 ColorSpaceType::DeviceRgb => 3,
318 ColorSpaceType::ICCBased(icc) => icc.0.number_components as u8,
319 ColorSpaceType::CalGray(_) => 1,
320 ColorSpaceType::CalRgb(_) => 3,
321 ColorSpaceType::Lab(_) => 3,
322 ColorSpaceType::Indexed(_) => 1,
323 ColorSpaceType::Separation(_) => 1,
324 ColorSpaceType::Pattern(p) => p.num_components(),
325 ColorSpaceType::DeviceN(d) => d.num_components,
326 }
327 }
328
329 pub fn to_rgba(&self, c: &[f32], opacity: f32, manual_scale: bool) -> AlphaColor {
331 self.to_alpha_color(c, opacity, manual_scale)
332 .unwrap_or(AlphaColor::BLACK)
333 }
334}
335
336impl ToRgb for ColorSpace {
337 fn convert_f32(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()> {
338 match self.0.as_ref() {
339 ColorSpaceType::DeviceCmyk => {
340 if input.len() == 4 {
341 let converted = [
342 f32_to_u8(input[0]),
343 f32_to_u8(input[1]),
344 f32_to_u8(input[2]),
345 f32_to_u8(input[3]),
346 ];
347 CMYK_TRANSFORM.convert_u8(&converted, output)
348 } else {
349 let converted = input.iter().copied().map(f32_to_u8).collect::<Vec<_>>();
350 CMYK_TRANSFORM.convert_u8(&converted, output)
351 }
352 }
353 ColorSpaceType::DeviceGray => {
354 for (gray, output) in input.iter().zip(output.chunks_exact_mut(3)) {
355 let gray = f32_to_u8(*gray);
356 output.copy_from_slice(&[gray, gray, gray]);
357 }
358
359 Some(())
360 }
361 ColorSpaceType::DeviceRgb => {
362 for (input, output) in input.iter().copied().zip(output) {
363 *output = f32_to_u8(input);
364 }
365
366 Some(())
367 }
368 ColorSpaceType::Pattern(i) => i.convert_f32(input, output, manual_scale),
369 ColorSpaceType::Indexed(i) => i.convert_f32(input, output, manual_scale),
370 ColorSpaceType::ICCBased(i) => i.convert_f32(input, output, manual_scale),
371 ColorSpaceType::CalGray(i) => i.convert_f32(input, output, manual_scale),
372 ColorSpaceType::CalRgb(i) => i.convert_f32(input, output, manual_scale),
373 ColorSpaceType::Lab(i) => i.convert_f32(input, output, manual_scale),
374 ColorSpaceType::Separation(i) => i.convert_f32(input, output, manual_scale),
375 ColorSpaceType::DeviceN(i) => i.convert_f32(input, output, manual_scale),
376 }
377 }
378
379 fn supports_u8(&self) -> bool {
380 match self.0.as_ref() {
381 ColorSpaceType::DeviceCmyk => true,
382 ColorSpaceType::DeviceGray => true,
383 ColorSpaceType::DeviceRgb => true,
384 ColorSpaceType::Pattern(i) => i.supports_u8(),
385 ColorSpaceType::Indexed(i) => i.supports_u8(),
386 ColorSpaceType::ICCBased(i) => i.supports_u8(),
387 ColorSpaceType::CalGray(i) => i.supports_u8(),
388 ColorSpaceType::CalRgb(i) => i.supports_u8(),
389 ColorSpaceType::Lab(i) => i.supports_u8(),
390 ColorSpaceType::Separation(i) => i.supports_u8(),
391 ColorSpaceType::DeviceN(i) => i.supports_u8(),
392 }
393 }
394
395 fn convert_u8(&self, input: &[u8], output: &mut [u8]) -> Option<()> {
396 match self.0.as_ref() {
397 ColorSpaceType::DeviceCmyk => CMYK_TRANSFORM.convert_u8(input, output),
398 ColorSpaceType::DeviceGray => {
399 for (input, output) in input.iter().zip(output.chunks_exact_mut(3)) {
400 output.copy_from_slice(&[*input, *input, *input]);
401 }
402
403 Some(())
404 }
405 ColorSpaceType::DeviceRgb => {
406 for (input, output) in input.iter().zip(output.iter_mut()) {
407 *output = *input;
408 }
409
410 Some(())
411 }
412 ColorSpaceType::Pattern(i) => i.convert_u8(input, output),
413 ColorSpaceType::Indexed(i) => i.convert_u8(input, output),
414 ColorSpaceType::ICCBased(i) => i.convert_u8(input, output),
415 ColorSpaceType::CalGray(i) => i.convert_u8(input, output),
416 ColorSpaceType::CalRgb(i) => i.convert_u8(input, output),
417 ColorSpaceType::Lab(i) => i.convert_u8(input, output),
418 ColorSpaceType::Separation(i) => i.convert_u8(input, output),
419 ColorSpaceType::DeviceN(i) => i.convert_u8(input, output),
420 }
421 }
422
423 fn is_none(&self) -> bool {
424 match self.0.as_ref() {
425 ColorSpaceType::Separation(s) => s.is_none(),
426 ColorSpaceType::DeviceN(d) => d.is_none(),
427 _ => false,
428 }
429 }
430}
431
432#[derive(Debug, Clone)]
433pub(crate) struct CalGray {
434 white_point: [f32; 3],
435 black_point: [f32; 3],
436 gamma: f32,
437}
438
439impl CalGray {
441 fn new(dict: &Dict<'_>) -> Option<Self> {
442 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
443 let black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
444 let gamma = dict.get::<f32>(GAMMA).unwrap_or(1.0);
445
446 Some(Self {
447 white_point,
448 black_point,
449 gamma,
450 })
451 }
452}
453
454impl ToRgb for CalGray {
455 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
456 for (input, output) in input.iter().copied().zip(output.chunks_exact_mut(3)) {
457 let g = self.gamma;
458 let (_xw, yw, _zw) = {
459 let wp = self.white_point;
460 (wp[0], wp[1], wp[2])
461 };
462 let (_xb, _yb, _zb) = {
463 let bp = self.black_point;
464 (bp[0], bp[1], bp[2])
465 };
466
467 let a = input;
468 let ag = a.powf(g);
469 let l = yw * ag;
470 let val = (0.0_f32.max(295.8 * l.powf(0.333_333_34) - 40.8) + 0.5) as u8;
471
472 output.copy_from_slice(&[val, val, val]);
473 }
474
475 Some(())
476 }
477}
478
479#[derive(Debug, Clone)]
480pub(crate) struct CalRgb {
481 white_point: [f32; 3],
482 black_point: [f32; 3],
483 matrix: [f32; 9],
484 gamma: [f32; 3],
485}
486
487impl CalRgb {
492 fn new(dict: &Dict<'_>) -> Option<Self> {
493 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
494 let black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
495 let matrix = dict
496 .get::<[f32; 9]>(MATRIX)
497 .unwrap_or([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
498 let gamma = dict.get::<[f32; 3]>(GAMMA).unwrap_or([1.0, 1.0, 1.0]);
499
500 Some(Self {
501 white_point,
502 black_point,
503 matrix,
504 gamma,
505 })
506 }
507
508 const BRADFORD_SCALE_MATRIX: [f32; 9] = [
509 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296,
510 ];
511
512 const BRADFORD_SCALE_INVERSE_MATRIX: [f32; 9] = [
513 0.9869929, -0.1470543, 0.1599627, 0.4323053, 0.5183603, 0.0492912, -0.0085287, 0.0400428,
514 0.9684867,
515 ];
516
517 const SRGB_D65_XYZ_TO_RGB_MATRIX: [f32; 9] = [
518 3.2404542, -1.5371385, -0.4985314, -0.969_266, 1.8760108, 0.0415560, 0.0556434, -0.2040259,
519 1.0572252,
520 ];
521
522 const FLAT_WHITEPOINT: [f32; 3] = [1.0, 1.0, 1.0];
523 const D65_WHITEPOINT: [f32; 3] = [0.95047, 1.0, 1.08883];
524
525 fn decode_l_constant() -> f32 {
526 ((8.0_f32 + 16.0) / 116.0).powi(3) / 8.0
527 }
528
529 fn srgb_transfer_function(color: f32) -> f32 {
530 if color <= 0.0031308 {
531 (12.92 * color).clamp(0.0, 1.0)
532 } else if color >= 0.99554525 {
533 1.0
534 } else {
535 ((1.0 + 0.055) * color.powf(1.0 / 2.4) - 0.055).clamp(0.0, 1.0)
536 }
537 }
538
539 fn matrix_product(a: &[f32; 9], b: &[f32; 3]) -> [f32; 3] {
540 [
541 a[0] * b[0] + a[1] * b[1] + a[2] * b[2],
542 a[3] * b[0] + a[4] * b[1] + a[5] * b[2],
543 a[6] * b[0] + a[7] * b[1] + a[8] * b[2],
544 ]
545 }
546
547 fn to_flat(source_white_point: &[f32; 3], lms: &[f32; 3]) -> [f32; 3] {
548 [
549 lms[0] / source_white_point[0],
550 lms[1] / source_white_point[1],
551 lms[2] / source_white_point[2],
552 ]
553 }
554
555 fn to_d65(source_white_point: &[f32; 3], lms: &[f32; 3]) -> [f32; 3] {
556 [
557 lms[0] * Self::D65_WHITEPOINT[0] / source_white_point[0],
558 lms[1] * Self::D65_WHITEPOINT[1] / source_white_point[1],
559 lms[2] * Self::D65_WHITEPOINT[2] / source_white_point[2],
560 ]
561 }
562
563 fn decode_l(l: f32) -> f32 {
564 if l < 0.0 {
565 -Self::decode_l(-l)
566 } else if l > 8.0 {
567 ((l + 16.0) / 116.0).powi(3)
568 } else {
569 l * Self::decode_l_constant()
570 }
571 }
572
573 fn compensate_black_point(source_bp: &[f32; 3], xyz_flat: &[f32; 3]) -> [f32; 3] {
574 if source_bp == &[0.0, 0.0, 0.0] {
575 return *xyz_flat;
576 }
577
578 let zero_decode_l = Self::decode_l(0.0);
579
580 let mut out = [0.0; 3];
581 for i in 0..3 {
582 let src = Self::decode_l(source_bp[i]);
583 let scale = (1.0 - zero_decode_l) / (1.0 - src);
584 let offset = 1.0 - scale;
585 out[i] = xyz_flat[i] * scale + offset;
586 }
587
588 out
589 }
590
591 fn normalize_white_point_to_flat(
592 &self,
593 source_white_point: &[f32; 3],
594 xyz: &[f32; 3],
595 ) -> [f32; 3] {
596 if source_white_point[0] == 1.0 && source_white_point[2] == 1.0 {
597 return *xyz;
598 }
599 let lms = Self::matrix_product(&Self::BRADFORD_SCALE_MATRIX, xyz);
600 let lms_flat = Self::to_flat(source_white_point, &lms);
601 Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_flat)
602 }
603
604 fn normalize_white_point_to_d65(
605 &self,
606 source_white_point: &[f32; 3],
607 xyz: &[f32; 3],
608 ) -> [f32; 3] {
609 let lms = Self::matrix_product(&Self::BRADFORD_SCALE_MATRIX, xyz);
610 let lms_d65 = Self::to_d65(source_white_point, &lms);
611 Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_d65)
612 }
613}
614
615impl ToRgb for CalRgb {
616 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
617 for (input, output) in input.chunks_exact(3).zip(output.chunks_exact_mut(3)) {
618 let input = [
619 input[0].clamp(0.0, 1.0),
620 input[1].clamp(0.0, 1.0),
621 input[2].clamp(0.0, 1.0),
622 ];
623
624 let [r, g, b] = input;
625 let [gr, gg, gb] = self.gamma;
626 let [agr, bgg, cgb] = [
627 if r == 1.0 { 1.0 } else { r.powf(gr) },
628 if g == 1.0 { 1.0 } else { g.powf(gg) },
629 if b == 1.0 { 1.0 } else { b.powf(gb) },
630 ];
631
632 let m = &self.matrix;
633 let x = m[0] * agr + m[3] * bgg + m[6] * cgb;
634 let y = m[1] * agr + m[4] * bgg + m[7] * cgb;
635 let z = m[2] * agr + m[5] * bgg + m[8] * cgb;
636 let xyz = [x, y, z];
637
638 let xyz_flat = self.normalize_white_point_to_flat(&self.white_point, &xyz);
639 let xyz_black = Self::compensate_black_point(&self.black_point, &xyz_flat);
640 let xyz_d65 = self.normalize_white_point_to_d65(&Self::FLAT_WHITEPOINT, &xyz_black);
641 let srgb_xyz = Self::matrix_product(&Self::SRGB_D65_XYZ_TO_RGB_MATRIX, &xyz_d65);
642
643 output.copy_from_slice(&[
644 (Self::srgb_transfer_function(srgb_xyz[0]) * 255.0 + 0.5) as u8,
645 (Self::srgb_transfer_function(srgb_xyz[1]) * 255.0 + 0.5) as u8,
646 (Self::srgb_transfer_function(srgb_xyz[2]) * 255.0 + 0.5) as u8,
647 ]);
648 }
649
650 Some(())
651 }
652}
653
654#[derive(Debug, Clone)]
655pub(crate) struct Lab {
656 range: [f32; 4],
657 profile: ICCProfile,
658}
659
660impl Lab {
661 fn new(dict: &Dict<'_>) -> Option<Self> {
662 let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
663 let _black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
665 let range = dict
666 .get::<[f32; 4]>(RANGE)
667 .unwrap_or([-100.0, 100.0, -100.0, 100.0]);
668
669 let mut profile = ColorProfile::new_from_slice(include_bytes!("../assets/LAB.icc")).ok()?;
670 profile.white_point = Xyzd::new(
671 white_point[0] as f64,
672 white_point[1] as f64,
673 white_point[2] as f64,
674 );
675
676 let profile = ICCProfile::new_from_src_profile(
677 profile, false,
678 false, 3,
682 )?;
683
684 Some(Self { range, profile })
685 }
686}
687
688impl ToRgb for Lab {
689 fn convert_f32(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()> {
690 if !manual_scale {
691 let input = input
695 .chunks_exact(3)
696 .flat_map(|i| {
697 let l = i[0] / 100.0;
698 let a = (i[1] + 128.0) / 255.0;
699 let b = (i[2] + 128.0) / 255.0;
700
701 [l, a, b]
702 })
703 .collect::<Vec<_>>();
704
705 self.profile.convert_f32(&input, output, manual_scale)
706 } else {
707 self.profile.convert_f32(input, output, manual_scale)
708 }
709 }
710}
711
712#[derive(Debug, Clone)]
713pub(crate) struct Indexed {
714 values: Vec<Vec<f32>>,
715 hival: u8,
716 base: Box<ColorSpace>,
717}
718
719impl Indexed {
720 fn new(array: &Array<'_>, cache: &Cache) -> Option<Self> {
721 let mut iter = array.flex_iter();
722 let _ = iter.next::<Name<'_>>()?;
724 let base_color_space = ColorSpace::new(iter.next::<Object<'_>>()?, cache)?;
725 let hival = iter.next::<u32>()?.min(u8::MAX as u32) as u8;
726
727 let values = {
728 let data = iter
729 .next::<Stream<'_>>()
730 .and_then(|s| s.decoded().ok())
731 .or_else(|| {
732 iter.next::<object::String<'_>>()
733 .map(|s| Cow::Owned(s.to_vec()))
734 })?;
735
736 let num_components = base_color_space.num_components();
737
738 let mut byte_iter = data.iter().copied();
739
740 let mut vals = vec![];
741 for _ in 0..=hival {
742 let mut temp = vec![];
743
744 for _ in 0..num_components {
745 temp.push(byte_iter.next()? as f32 / 255.0);
746 }
747
748 vals.push(temp);
749 }
750
751 vals
752 };
753
754 Some(Self {
755 values,
756 hival,
757 base: Box::new(base_color_space),
758 })
759 }
760}
761
762impl ToRgb for Indexed {
763 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
764 let mut indexed = vec![0.0; input.len() * self.base.num_components() as usize];
765
766 for (input, output) in input
767 .iter()
768 .copied()
769 .zip(indexed.chunks_exact_mut(self.base.num_components() as usize))
770 {
771 let idx = (input.clamp(0.0, self.hival as f32) + 0.5) as usize;
772 output.copy_from_slice(&self.values[idx]);
773 }
774
775 self.base.convert_f32(&indexed, output, true)
776 }
777}
778
779#[derive(Debug, Clone)]
780pub(crate) struct Separation {
781 alternate_space: ColorSpace,
782 tint_transform: Function,
783 is_none_separation: bool,
784}
785
786impl Separation {
787 fn new(array: &Array<'_>, cache: &Cache) -> Option<Self> {
788 let mut iter = array.flex_iter();
789 let _ = iter.next::<Name<'_>>()?;
791 let name = iter.next::<Name<'_>>()?;
792 let alternate_space = ColorSpace::new(iter.next::<Object<'_>>()?, cache)?;
793 let tint_transform = Function::new(&iter.next::<Object<'_>>()?)?;
794 let is_none_separation = name.as_str() == "None";
797
798 Some(Self {
799 alternate_space,
800 tint_transform,
801 is_none_separation,
802 })
803 }
804}
805
806impl ToRgb for Separation {
807 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
808 let evaluated = input
809 .iter()
810 .flat_map(|n| {
811 self.tint_transform
812 .eval(smallvec![*n])
813 .unwrap_or(self.alternate_space.initial_color())
814 })
815 .collect::<Vec<_>>();
816 self.alternate_space.convert_f32(&evaluated, output, false)
817 }
818
819 fn is_none(&self) -> bool {
820 self.is_none_separation
821 }
822}
823
824#[derive(Debug, Clone)]
825pub(crate) struct DeviceN {
826 alternate_space: ColorSpace,
827 num_components: u8,
828 tint_transform: Function,
829 is_none: bool,
830}
831
832impl DeviceN {
833 fn new(array: &Array<'_>, cache: &Cache) -> Option<Self> {
834 let mut iter = array.flex_iter();
835 let _ = iter.next::<Name<'_>>()?;
837 let names = iter
839 .next::<Array<'_>>()?
840 .iter::<Name<'_>>()
841 .collect::<Vec<_>>();
842 let num_components = u8::try_from(names.len()).ok()?;
843 let all_none = names.iter().all(|n| n.as_str() == "None");
844 let alternate_space = ColorSpace::new(iter.next::<Object<'_>>()?, cache)?;
845 let tint_transform = Function::new(&iter.next::<Object<'_>>()?)?;
846
847 if num_components == 0 {
848 return None;
849 }
850
851 Some(Self {
852 alternate_space,
853 num_components,
854 tint_transform,
855 is_none: all_none,
856 })
857 }
858}
859
860impl ToRgb for DeviceN {
861 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
862 let evaluated = input
863 .chunks_exact(self.num_components as usize)
864 .flat_map(|n| {
865 self.tint_transform
866 .eval(n.to_smallvec())
867 .unwrap_or(self.alternate_space.initial_color())
868 })
869 .collect::<Vec<_>>();
870 self.alternate_space.convert_f32(&evaluated, output, false)
871 }
872
873 fn is_none(&self) -> bool {
874 self.is_none
875 }
876}
877
878struct ICCColorRepr {
879 src_profile: ColorProfile,
880 src_layout: Layout,
881 number_components: usize,
882 is_srgb: bool,
883 is_lab: bool,
884 transform_u8: Arc<Transform8BitExecutor>,
885 transform_f32: OnceLock<Arc<TransformF32Executor>>,
886}
887
888#[derive(Clone)]
889pub(crate) struct ICCProfile(Arc<ICCColorRepr>);
890
891impl Debug for ICCProfile {
892 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
893 write!(f, "ICCColor {{..}}")
894 }
895}
896
897impl ICCProfile {
898 fn new(profile: &[u8], number_components: usize) -> Option<Self> {
899 let src_profile = ColorProfile::new_from_slice(profile).ok()?;
900
901 const SRGB_MARKER: &[u8] = b"sRGB";
902
903 let is_srgb = profile
904 .get(52..56)
905 .map(|device_model| device_model == SRGB_MARKER)
906 .unwrap_or(false);
907 let is_lab = src_profile.color_space == DataColorSpace::Lab;
908
909 Self::new_from_src_profile(src_profile, is_srgb, is_lab, number_components)
910 }
911
912 fn new_from_src_profile(
913 src_profile: ColorProfile,
914 is_srgb: bool,
915 is_lab: bool,
916 number_components: usize,
917 ) -> Option<Self> {
918 let src_layout = match number_components {
919 1 => Layout::Gray,
920 3 => Layout::Rgb,
921 4 => Layout::Rgba,
922 _ => {
923 warn!("unsupported number of components {number_components} for ICC profile");
924
925 return None;
926 }
927 };
928
929 let dest_profile = ColorProfile::new_srgb();
930 let transform_u8 = src_profile
931 .clone()
932 .create_transform_8bit(
933 src_layout,
934 &dest_profile,
935 Layout::Rgb,
936 TransformOptions::default(),
937 )
938 .ok()?;
939
940 Some(Self(Arc::new(ICCColorRepr {
941 src_profile,
942 src_layout,
943 number_components,
944 is_srgb,
945 is_lab,
946 transform_u8,
947 transform_f32: OnceLock::new(),
948 })))
949 }
950
951 fn is_srgb(&self) -> bool {
952 self.0.is_srgb
953 }
954
955 fn is_lab(&self) -> bool {
956 self.0.is_lab
957 }
958
959 fn transform_u8(&self) -> &Arc<Transform8BitExecutor> {
960 &self.0.transform_u8
961 }
962
963 fn transform_f32(&self) -> &Arc<TransformF32Executor> {
964 self.0.transform_f32.get_or_init(|| {
968 let dest_profile = ColorProfile::new_srgb();
969 self.0
970 .src_profile
971 .clone()
972 .create_transform_f32(
973 self.0.src_layout,
974 &dest_profile,
975 Layout::Rgb,
976 TransformOptions::default(),
977 )
978 .unwrap()
980 })
981 }
982}
983
984impl ToRgb for ICCProfile {
985 fn convert_f32(&self, input: &[f32], output: &mut [u8], _: bool) -> Option<()> {
986 let mut temp = vec![0.0_f32; output.len()];
987
988 if self.is_lab() {
989 let scaled = input
991 .chunks_exact(3)
992 .flat_map(|i| {
993 [
994 i[0] * (1.0 / 100.0),
995 (i[1] + 128.0) * (1.0 / 255.0),
996 (i[2] + 128.0) * (1.0 / 255.0),
997 ]
998 })
999 .collect::<Vec<_>>();
1000 self.transform_f32().transform(&scaled, &mut temp).ok()?;
1001 } else {
1002 self.transform_f32().transform(input, &mut temp).ok()?;
1003 };
1004
1005 for (input, output) in temp.iter().zip(output.iter_mut()) {
1006 *output = (input * 255.0 + 0.5) as u8;
1007 }
1008
1009 Some(())
1010 }
1011
1012 fn supports_u8(&self) -> bool {
1013 true
1014 }
1015
1016 fn convert_u8(&self, input: &[u8], output: &mut [u8]) -> Option<()> {
1017 if self.is_srgb() {
1018 output.copy_from_slice(input);
1019 } else {
1020 self.transform_u8().transform(input, output).ok()?;
1021 }
1022
1023 Some(())
1024 }
1025}
1026
1027#[inline(always)]
1028fn f32_to_u8(val: f32) -> u8 {
1029 (val * 255.0 + 0.5) as u8
1030}
1031
1032#[derive(Debug, Clone)]
1033pub struct Color {
1035 color_space: ColorSpace,
1036 components: ColorComponents,
1037 opacity: f32,
1038}
1039
1040impl Color {
1041 pub(crate) fn new(color_space: ColorSpace, components: ColorComponents, opacity: f32) -> Self {
1042 Self {
1043 color_space,
1044 components,
1045 opacity,
1046 }
1047 }
1048
1049 pub fn to_rgba(&self) -> AlphaColor {
1051 self.color_space
1052 .to_rgba(&self.components, self.opacity, false)
1053 }
1054
1055 pub fn from_rgba(rgba: AlphaColor) -> Self {
1057 let c = rgba.components();
1058 Self {
1059 color_space: ColorSpace::device_rgb(),
1060 components: smallvec![c[0], c[1], c[2]],
1061 opacity: c[3],
1062 }
1063 }
1064}
1065
1066static CMYK_TRANSFORM: LazyLock<ICCProfile> = LazyLock::new(|| {
1067 ICCProfile::new(include_bytes!("../assets/CGATS001Compat-v2-micro.icc"), 4).unwrap()
1068});
1069
1070pub(crate) trait ToRgb {
1071 fn convert_sample(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()> {
1072 if self.supports_u8() {
1076 let converted = input
1077 .iter()
1078 .copied()
1079 .map(f32_to_u8)
1080 .collect::<SmallVec<[u8; 4]>>();
1081
1082 if self.convert_u8(&converted, output).is_some() {
1083 return Some(());
1084 }
1085 }
1086
1087 self.convert_f32(input, output, manual_scale)
1088 }
1089
1090 fn convert_f32(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()>;
1091 fn supports_u8(&self) -> bool {
1092 false
1093 }
1094 fn convert_u8(&self, _: &[u8], _: &mut [u8]) -> Option<()> {
1095 unimplemented!();
1096 }
1097 fn is_none(&self) -> bool {
1098 false
1099 }
1100 fn to_alpha_color(
1101 &self,
1102 input: &[f32],
1103 mut opacity: f32,
1104 manual_scale: bool,
1105 ) -> Option<AlphaColor> {
1106 let mut output = [0; 3];
1107 self.convert_sample(input, &mut output, manual_scale)?;
1108
1109 if self.is_none() {
1114 opacity = 0.0;
1115 }
1116
1117 Some(AlphaColor::from_rgba8(
1118 output[0],
1119 output[1],
1120 output[2],
1121 (opacity * 255.0 + 0.5) as u8,
1122 ))
1123 }
1124}