1mod cal_gray;
4mod cal_rgb;
5mod device_cmyk;
6mod device_gray;
7mod device_n;
8mod device_rgb;
9mod icc;
10mod indexed;
11mod lab;
12mod pattern;
13mod separation;
14
15use self::cal_gray::CalGray;
16use self::cal_rgb::CalRgb;
17use self::device_cmyk::DeviceCmyk;
18use self::device_gray::DeviceGray;
19use self::device_n::DeviceN;
20use self::device_rgb::DeviceRgb;
21use self::icc::ICCProfile;
22use self::indexed::Indexed;
23use self::lab::Lab;
24use self::pattern::Pattern;
25use self::separation::Separation;
26use crate::cache::{Cache, CacheKey};
27use hayro_syntax::object::Dict;
28use hayro_syntax::object::Name;
29use hayro_syntax::object::Object;
30use hayro_syntax::object::Stream;
31use hayro_syntax::object::dict::keys::*;
32use smallvec::{SmallVec, smallvec};
33use std::ops::Deref;
34use std::sync::{Arc, OnceLock};
35
36pub type ColorComponents = SmallVec<[f32; 4]>;
38
39#[derive(Clone)]
40pub(super) struct U8Lookup<T>(Arc<OnceLock<Option<Box<[T; 256]>>>>);
41
42impl<T> Default for U8Lookup<T> {
43 fn default() -> Self {
44 Self(Arc::new(OnceLock::new()))
45 }
46}
47
48impl<T> std::fmt::Debug for U8Lookup<T> {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.write_str("U8Lookup")
51 }
52}
53
54impl<T> U8Lookup<T> {
55 pub(super) fn get_or_init_with(
56 &self,
57 init: impl FnOnce(&[u8; 256]) -> Option<Box<[T; 256]>>,
58 ) -> Option<&[T; 256]> {
59 self.0
60 .get_or_init(|| {
61 let input: [u8; 256] = core::array::from_fn(|index| index as u8);
62 init(&input)
63 })
64 .as_deref()
65 }
66}
67
68impl U8Lookup<[u8; 3]> {
69 pub(super) fn get_or_init(
70 &self,
71 convert: impl FnOnce(&[u8], &mut [u8]) -> Option<()>,
72 ) -> Option<&[[u8; 3]; 256]> {
73 self.get_or_init_with(|input| {
74 let mut output = Box::new([[0; 3]; 256]);
75 convert(input, output.as_flattened_mut())?;
76 Some(output)
77 })
78 }
79}
80
81#[derive(Debug, Copy, Clone)]
83pub struct AlphaColor {
84 components: [f32; 4],
85}
86
87impl AlphaColor {
88 pub const BLACK: Self = Self::new([0., 0., 0., 1.]);
90
91 pub const TRANSPARENT: Self = Self::new([0., 0., 0., 0.]);
93
94 pub const WHITE: Self = Self::new([1., 1., 1., 1.]);
96
97 pub const fn new(components: [f32; 4]) -> Self {
99 Self { components }
100 }
101
102 pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
104 let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), 1.];
105 Self::new(components)
106 }
107
108 pub fn premultiplied(&self) -> [f32; 4] {
110 [
111 self.components[0] * self.components[3],
112 self.components[1] * self.components[3],
113 self.components[2] * self.components[3],
114 self.components[3],
115 ]
116 }
117
118 pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
120 let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
121 Self::new(components)
122 }
123
124 pub fn to_rgba8(&self) -> [u8; 4] {
126 [
127 (self.components[0] * 255.0 + 0.5) as u8,
128 (self.components[1] * 255.0 + 0.5) as u8,
129 (self.components[2] * 255.0 + 0.5) as u8,
130 (self.components[3] * 255.0 + 0.5) as u8,
131 ]
132 }
133
134 pub fn components(&self) -> [f32; 4] {
136 self.components
137 }
138}
139
140const fn u8_to_f32(x: u8) -> f32 {
141 x as f32 * (1.0 / 255.0)
142}
143
144#[derive(Debug, Clone)]
145pub(crate) enum ColorSpaceType {
146 DeviceCmyk(DeviceCmyk),
147 DeviceGray(DeviceGray),
148 DeviceRgb(DeviceRgb),
149 Pattern(Pattern),
150 Indexed(Indexed),
151 ICCBased(ICCProfile),
152 CalGray(CalGray),
153 CalRgb(CalRgb),
154 Lab(Lab),
155 Separation(Separation),
156 DeviceN(DeviceN),
157}
158
159impl ColorSpaceType {
160 fn new(object: Object<'_>, cache: &Cache) -> Option<Self> {
161 Self::new_inner(object, cache)
162 }
163
164 fn new_inner(object: Object<'_>, cache: &Cache) -> Option<Self> {
165 if let Object::Name(name) = object {
166 return Self::new_from_name(&name);
167 } else if let Object::Array(color_array) = object {
168 let mut iter = color_array.flex_iter();
169 let name = iter.next::<Name<'_>>()?;
170
171 match name.deref() {
172 ICC_BASED => {
173 let icc_stream = iter.next::<Stream<'_>>()?;
174 let dict = icc_stream.dict();
175 let num_components = dict.get::<usize>(N)?;
176
177 return cache.get_or_insert_with(icc_stream.cache_key(), || {
178 if let Some(decoded) = icc_stream.decoded().ok().as_ref() {
179 ICCProfile::new(decoded, num_components)
180 .map(|icc| {
181 if icc.is_srgb() {
186 Self::DeviceRgb(DeviceRgb)
187 } else {
188 Self::ICCBased(icc)
189 }
190 })
191 .or_else(|| {
192 dict.get::<Object<'_>>(ALTERNATE)
193 .and_then(|o| Self::new(o, cache))
194 })
195 .or_else(|| match dict.get::<u8>(N) {
196 Some(1) => Some(Self::DeviceGray(DeviceGray)),
197 Some(3) => Some(Self::DeviceRgb(DeviceRgb)),
198 Some(4) => Some(Self::DeviceCmyk(DeviceCmyk)),
199 _ => None,
200 })
201 } else {
202 None
203 }
204 });
205 }
206 CALCMYK => return Some(Self::DeviceCmyk(DeviceCmyk)),
207 CALGRAY => {
208 let cal_dict = iter.next::<Dict<'_>>()?;
209 return Some(Self::CalGray(CalGray::new(&cal_dict)?));
210 }
211 CALRGB => {
212 let cal_dict = iter.next::<Dict<'_>>()?;
213 return Some(Self::CalRgb(CalRgb::new(&cal_dict)?));
214 }
215 DEVICE_RGB | RGB => return Some(Self::DeviceRgb(DeviceRgb)),
216 DEVICE_GRAY | G => return Some(Self::DeviceGray(DeviceGray)),
217 DEVICE_CMYK | CMYK => return Some(Self::DeviceCmyk(DeviceCmyk)),
218 LAB => {
219 let lab_dict = iter.next::<Dict<'_>>()?;
220 return Some(Self::Lab(Lab::new(&lab_dict)?));
221 }
222 INDEXED | I => {
223 return Some(Self::Indexed(Indexed::new(&color_array, cache)?));
224 }
225 SEPARATION => {
226 return Some(Self::Separation(Separation::new(&color_array, cache)?));
227 }
228 DEVICE_N => {
229 return Some(Self::DeviceN(DeviceN::new(&color_array, cache)?));
230 }
231 PATTERN => {
232 let _ = iter.next::<Name<'_>>();
233 let cs = iter
234 .next::<Object<'_>>()
235 .and_then(|o| ColorSpace::new(o, cache))
236 .unwrap_or(ColorSpace::device_rgb());
237 return Some(Self::Pattern(Pattern::new(cs)));
238 }
239 _ => {
240 warn!("unsupported color space: {}", name.as_str());
241 return None;
242 }
243 }
244 }
245
246 None
247 }
248
249 fn new_from_name(name: &Name<'_>) -> Option<Self> {
250 match name.deref() {
251 DEVICE_RGB | RGB => Some(Self::DeviceRgb(DeviceRgb)),
252 DEVICE_GRAY | G => Some(Self::DeviceGray(DeviceGray)),
253 DEVICE_CMYK | CMYK => Some(Self::DeviceCmyk(DeviceCmyk)),
254 CALCMYK => Some(Self::DeviceCmyk(DeviceCmyk)),
255 PATTERN => Some(Self::Pattern(Pattern::new(ColorSpace::device_rgb()))),
256 _ => None,
257 }
258 }
259}
260
261#[derive(Debug, Clone)]
263pub struct ColorSpace(Arc<ColorSpaceType>);
264
265impl ColorSpace {
266 pub(crate) fn new(object: Object<'_>, cache: &Cache) -> Option<Self> {
268 Some(Self(Arc::new(ColorSpaceType::new(object, cache)?)))
269 }
270
271 pub(crate) fn new_from_name(name: &Name<'_>) -> Option<Self> {
273 ColorSpaceType::new_from_name(name).map(|c| Self(Arc::new(c)))
274 }
275
276 pub(crate) fn device_gray() -> Self {
278 Self(Arc::new(ColorSpaceType::DeviceGray(DeviceGray)))
279 }
280
281 pub(crate) fn device_rgb() -> Self {
283 Self(Arc::new(ColorSpaceType::DeviceRgb(DeviceRgb)))
284 }
285
286 pub(crate) fn device_cmyk() -> Self {
288 Self(Arc::new(ColorSpaceType::DeviceCmyk(DeviceCmyk)))
289 }
290
291 pub(crate) fn pattern() -> Self {
293 Self(Arc::new(ColorSpaceType::Pattern(Pattern::new(
294 Self::device_gray(),
295 ))))
296 }
297
298 pub(crate) fn pattern_cs(&self) -> Option<Self> {
299 match self.0.as_ref() {
300 ColorSpaceType::Pattern(pattern) => Some(pattern.color_space()),
301 _ => None,
302 }
303 }
304
305 pub(crate) fn is_pattern(&self) -> bool {
307 matches!(self.0.as_ref(), ColorSpaceType::Pattern(_))
308 }
309
310 pub(crate) fn is_indexed(&self) -> bool {
312 matches!(self.0.as_ref(), ColorSpaceType::Indexed(_))
313 }
314
315 pub(crate) fn indexed_hival(&self) -> Option<u8> {
316 match self.0.as_ref() {
317 ColorSpaceType::Indexed(indexed) => Some(indexed.hival()),
318 ColorSpaceType::Pattern(pattern) => pattern.color_space().indexed_hival(),
319 _ => None,
320 }
321 }
322
323 pub(crate) fn default_decode_arr(&self, n: f32) -> SmallVec<[(f32, f32); 4]> {
325 match self.0.as_ref() {
326 ColorSpaceType::DeviceCmyk(_) => {
327 smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0), (0.0, 1.0)]
328 }
329 ColorSpaceType::DeviceGray(_) => smallvec![(0.0, 1.0)],
330 ColorSpaceType::DeviceRgb(_) => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
331 ColorSpaceType::ICCBased(i) => smallvec![(0.0, 1.0); i.number_components()],
332 ColorSpaceType::CalGray(_) => smallvec![(0.0, 1.0)],
333 ColorSpaceType::CalRgb(_) => smallvec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
334 ColorSpaceType::Lab(l) => smallvec![
335 (0.0, 100.0),
336 (l.range[0], l.range[1]),
337 (l.range[2], l.range[3]),
338 ],
339 ColorSpaceType::Indexed(_) => smallvec![(0.0, 2.0_f32.powf(n) - 1.0)],
340 ColorSpaceType::Separation(_) => smallvec![(0.0, 1.0)],
341 ColorSpaceType::DeviceN(d) => smallvec![(0.0, 1.0); d.num_components as usize],
342 ColorSpaceType::Pattern(_) => smallvec![(0.0, 1.0)],
344 }
345 }
346
347 pub(crate) fn inverted_default_decode_arr(&self, n: f32) -> SmallVec<[(f32, f32); 4]> {
348 self.default_decode_arr(n)
349 .iter()
350 .map(|(min, max)| (*max, *min))
351 .collect()
352 }
353
354 pub(crate) fn component_ranges(&self) -> SmallVec<[(f32, f32); 4]> {
355 match self.0.as_ref() {
356 ColorSpaceType::DeviceCmyk(_) => smallvec![(0.0, 1.0); 4],
357 ColorSpaceType::DeviceGray(_) => smallvec![(0.0, 1.0)],
358 ColorSpaceType::DeviceRgb(_) => smallvec![(0.0, 1.0); 3],
359 ColorSpaceType::ICCBased(i) => smallvec![(0.0, 1.0); i.number_components()],
360 ColorSpaceType::CalGray(_) => smallvec![(0.0, 1.0)],
361 ColorSpaceType::CalRgb(_) => smallvec![(0.0, 1.0); 3],
362 ColorSpaceType::Lab(_) => {
363 smallvec![(0.0, 100.0), (-128.0, 127.0), (-128.0, 127.0)]
364 }
365 ColorSpaceType::Indexed(i) => smallvec![(0.0, i.hival() as f32)],
366 ColorSpaceType::Separation(_) => smallvec![(0.0, 1.0)],
367 ColorSpaceType::Pattern(pattern) => pattern.color_space().component_ranges(),
368 ColorSpaceType::DeviceN(d) => smallvec![(0.0, 1.0); d.num_components as usize],
369 }
370 }
371
372 pub(crate) fn convert_values(&self, input: &[f32], output: &mut [u8]) -> Option<()> {
373 let converted = self.encode_values(input);
374 self.convert(&converted, output)
375 }
376
377 pub(crate) fn encode_values(&self, input: &[f32]) -> SmallVec<[u8; 4]> {
378 if let Some(hival) = self.indexed_hival() {
379 return input
380 .iter()
381 .map(|value| (*value + 0.5).clamp(0.0, hival as f32) as u8)
382 .collect();
383 }
384
385 let ranges = match self.0.as_ref() {
386 ColorSpaceType::ICCBased(icc) if icc.is_lab() => {
387 smallvec![(0.0, 100.0), (-128.0, 127.0), (-128.0, 127.0)]
388 }
389 _ => self.component_ranges(),
390 };
391 encode_components(input, &ranges)
392 }
393
394 pub(crate) fn initial_color(&self) -> ColorComponents {
396 match self.0.as_ref() {
397 ColorSpaceType::DeviceCmyk(_) => smallvec![0.0, 0.0, 0.0, 1.0],
398 ColorSpaceType::DeviceGray(_) => smallvec![0.0],
399 ColorSpaceType::DeviceRgb(_) => smallvec![0.0, 0.0, 0.0],
400 ColorSpaceType::ICCBased(icc) => match icc.number_components() {
401 1 => smallvec![0.0],
402 3 => smallvec![0.0, 0.0, 0.0],
403 4 => smallvec![0.0, 0.0, 0.0, 1.0],
404 _ => unreachable!(),
405 },
406 ColorSpaceType::CalGray(_) => smallvec![0.0],
407 ColorSpaceType::CalRgb(_) => smallvec![0.0, 0.0, 0.0],
408 ColorSpaceType::Lab(_) => smallvec![0.0, 0.0, 0.0],
409 ColorSpaceType::Indexed(_) => smallvec![0.0],
410 ColorSpaceType::Separation(_) => smallvec![1.0],
411 ColorSpaceType::Pattern(pattern) => pattern.initial_color(),
412 ColorSpaceType::DeviceN(d) => smallvec![1.0; d.num_components as usize],
413 }
414 }
415
416 pub(crate) fn num_components(&self) -> u8 {
418 match self.0.as_ref() {
419 ColorSpaceType::DeviceCmyk(_) => 4,
420 ColorSpaceType::DeviceGray(_) => 1,
421 ColorSpaceType::DeviceRgb(_) => 3,
422 ColorSpaceType::ICCBased(icc) => icc.number_components() as u8,
423 ColorSpaceType::CalGray(_) => 1,
424 ColorSpaceType::CalRgb(_) => 3,
425 ColorSpaceType::Lab(_) => 3,
426 ColorSpaceType::Indexed(_) => 1,
427 ColorSpaceType::Separation(_) => 1,
428 ColorSpaceType::Pattern(pattern) => pattern.num_components(),
429 ColorSpaceType::DeviceN(d) => d.num_components,
430 }
431 }
432
433 #[inline]
435 pub fn to_rgba(&self, c: &[f32], opacity: f32) -> AlphaColor {
436 let alpha = f32_to_u8(opacity);
437
438 match self.0.as_ref() {
439 ColorSpaceType::DeviceGray(_) => {
440 let gray = c.first().copied().map(f32_to_u8).unwrap_or(0);
441 AlphaColor::from_rgba8(gray, gray, gray, alpha)
442 }
443 ColorSpaceType::DeviceRgb(_) => AlphaColor::from_rgba8(
444 c.first().copied().map(f32_to_u8).unwrap_or(0),
445 c.get(1).copied().map(f32_to_u8).unwrap_or(0),
446 c.get(2).copied().map(f32_to_u8).unwrap_or(0),
447 alpha,
448 ),
449 ColorSpaceType::DeviceCmyk(device_cmyk) if c.len() == 4 => {
450 let input = [
451 f32_to_u8(c[0]),
452 f32_to_u8(c[1]),
453 f32_to_u8(c[2]),
454 f32_to_u8(c[3]),
455 ];
456 let mut output = [0; 3];
457
458 if device_cmyk.convert(&input, &mut output).is_some() {
459 AlphaColor::from_rgba8(output[0], output[1], output[2], alpha)
460 } else {
461 AlphaColor::BLACK
462 }
463 }
464 _ => self.to_alpha_color(c, opacity).unwrap_or(AlphaColor::BLACK),
465 }
466 }
467
468 fn to_alpha_color(&self, input: &[f32], mut opacity: f32) -> Option<AlphaColor> {
469 let mut output = [0; 3];
470 self.convert_values(input, &mut output)?;
471
472 if self.is_none() {
477 opacity = 0.0;
478 }
479
480 Some(AlphaColor::from_rgba8(
481 output[0],
482 output[1],
483 output[2],
484 (opacity * 255.0 + 0.5) as u8,
485 ))
486 }
487}
488
489impl ToRgb for ColorSpace {
490 fn convert(&self, input: &[u8], output: &mut [u8]) -> Option<()> {
491 match self.0.as_ref() {
492 ColorSpaceType::DeviceCmyk(i) => i.convert(input, output),
493 ColorSpaceType::DeviceGray(i) => i.convert(input, output),
494 ColorSpaceType::DeviceRgb(i) => i.convert(input, output),
495 ColorSpaceType::Pattern(i) => i.convert(input, output),
496 ColorSpaceType::Indexed(i) => i.convert(input, output),
497 ColorSpaceType::ICCBased(i) => i.convert(input, output),
498 ColorSpaceType::CalGray(i) => i.convert(input, output),
499 ColorSpaceType::CalRgb(i) => i.convert(input, output),
500 ColorSpaceType::Lab(i) => i.convert(input, output),
501 ColorSpaceType::Separation(i) => i.convert(input, output),
502 ColorSpaceType::DeviceN(i) => i.convert(input, output),
503 }
504 }
505
506 fn convert_in_place(&self, input: &mut [u8]) -> Option<()> {
507 match self.0.as_ref() {
508 ColorSpaceType::DeviceCmyk(i) => i.convert_in_place(input),
509 ColorSpaceType::DeviceGray(i) => i.convert_in_place(input),
510 ColorSpaceType::DeviceRgb(i) => i.convert_in_place(input),
511 ColorSpaceType::Pattern(i) => i.convert_in_place(input),
512 ColorSpaceType::Indexed(i) => i.convert_in_place(input),
513 ColorSpaceType::ICCBased(i) => i.convert_in_place(input),
514 ColorSpaceType::CalGray(i) => i.convert_in_place(input),
515 ColorSpaceType::CalRgb(i) => i.convert_in_place(input),
516 ColorSpaceType::Lab(i) => i.convert_in_place(input),
517 ColorSpaceType::Separation(i) => i.convert_in_place(input),
518 ColorSpaceType::DeviceN(i) => i.convert_in_place(input),
519 }
520 }
521
522 fn is_none(&self) -> bool {
523 match self.0.as_ref() {
524 ColorSpaceType::Separation(s) => s.is_none(),
525 ColorSpaceType::DeviceN(d) => d.is_none(),
526 _ => false,
527 }
528 }
529}
530
531impl ToLuma for ColorSpace {
532 fn to_luma(&self, input: &mut [u8]) -> Option<()> {
533 match self.0.as_ref() {
534 ColorSpaceType::DeviceGray(i) => i.to_luma(input),
535 ColorSpaceType::Pattern(i) => i.to_luma(input),
536 ColorSpaceType::Indexed(i) => i.to_luma(input),
537 ColorSpaceType::ICCBased(i) => i.to_luma(input),
538 ColorSpaceType::CalGray(i) => i.to_luma(input),
539 ColorSpaceType::DeviceCmyk(_)
540 | ColorSpaceType::DeviceRgb(_)
541 | ColorSpaceType::CalRgb(_)
542 | ColorSpaceType::Lab(_)
543 | ColorSpaceType::Separation(_)
544 | ColorSpaceType::DeviceN(_) => None,
545 }
546 }
547}
548
549#[inline(always)]
550fn f32_to_u8(val: f32) -> u8 {
551 (val * 255.0 + 0.5) as u8
552}
553
554#[derive(Debug, Clone)]
555pub struct Color {
557 color_space: ColorSpace,
558 components: ColorComponents,
559 opacity: f32,
560}
561
562impl Color {
563 pub(crate) fn new(color_space: ColorSpace, components: ColorComponents, opacity: f32) -> Self {
564 Self {
565 color_space,
566 components,
567 opacity,
568 }
569 }
570
571 #[inline]
573 pub fn to_rgba(&self) -> AlphaColor {
574 self.color_space.to_rgba(&self.components, self.opacity)
575 }
576
577 #[inline]
579 pub fn from_rgba(rgba: AlphaColor) -> Self {
580 let c = rgba.components();
581 Self {
582 color_space: ColorSpace::device_rgb(),
583 components: smallvec![c[0], c[1], c[2]],
584 opacity: c[3],
585 }
586 }
587}
588
589pub(crate) trait ToRgb {
590 fn convert(&self, input: &[u8], output: &mut [u8]) -> Option<()>;
591 fn convert_in_place(&self, _input: &mut [u8]) -> Option<()> {
592 None
593 }
594 fn is_none(&self) -> bool {
595 false
596 }
597}
598
599pub(crate) trait ToLuma {
600 fn to_luma(&self, input: &mut [u8]) -> Option<()>;
601}
602
603#[inline]
604fn encode_components(input: &[f32], ranges: &[(f32, f32)]) -> SmallVec<[u8; 4]> {
605 input
606 .iter()
607 .enumerate()
608 .map(|(index, value)| {
609 let (min, max) = ranges[index % ranges.len()];
610 (((*value - min) / (max - min)) * 255.0 + 0.5) as u8
611 })
612 .collect()
613}