1use crate::chad::BRADFORD_D;
30use crate::cicp::{
31 CicpColorPrimaries, ColorPrimaries, MatrixCoefficients, TransferCharacteristics,
32};
33use crate::dat::ColorDateTime;
34use crate::err::CmsError;
35use crate::matrix::{Matrix3f, Xyz};
36use crate::reader::s15_fixed16_number_to_float;
37use crate::safe_math::{SafeAdd, SafeMul};
38use crate::tag::{TAG_SIZE, Tag};
39use crate::trc::ToneReprCurve;
40use crate::{Chromaticity, Layout, Matrix3d, Vector3d, XyY, Xyzd, adapt_to_d50_d};
41use std::io::Read;
42
43const MAX_PROFILE_SIZE: usize = 1024 * 1024 * 10; #[repr(u32)]
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum ProfileSignature {
48 Acsp,
49}
50
51impl TryFrom<u32> for ProfileSignature {
52 type Error = CmsError;
53 #[inline]
54 fn try_from(value: u32) -> Result<Self, Self::Error> {
55 if value == u32::from_ne_bytes(*b"acsp").to_be() {
56 return Ok(ProfileSignature::Acsp);
57 }
58 Err(CmsError::InvalidProfile)
59 }
60}
61
62impl From<ProfileSignature> for u32 {
63 #[inline]
64 fn from(value: ProfileSignature) -> Self {
65 match value {
66 ProfileSignature::Acsp => u32::from_ne_bytes(*b"acsp").to_be(),
67 }
68 }
69}
70
71#[repr(u32)]
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Ord, PartialOrd)]
73pub enum ProfileVersion {
74 V2_0 = 0x02000000,
75 V2_1 = 0x02100000,
76 V2_2 = 0x02200000,
77 V2_3 = 0x02300000,
78 V2_4 = 0x02400000,
79 V4_0 = 0x04000000,
80 V4_1 = 0x04100000,
81 V4_2 = 0x04200000,
82 V4_3 = 0x04300000,
83 #[default]
84 V4_4 = 0x04400000,
85 Unknown,
86}
87
88impl TryFrom<u32> for ProfileVersion {
89 type Error = CmsError;
90 fn try_from(value: u32) -> Result<Self, Self::Error> {
91 match value {
92 0x02000000 => Ok(ProfileVersion::V2_0),
93 0x02100000 => Ok(ProfileVersion::V2_1),
94 0x02200000 => Ok(ProfileVersion::V2_2),
95 0x02300000 => Ok(ProfileVersion::V2_3),
96 0x02400000 => Ok(ProfileVersion::V2_4),
97 0x04000000 => Ok(ProfileVersion::V4_0),
98 0x04100000 => Ok(ProfileVersion::V4_1),
99 0x04200000 => Ok(ProfileVersion::V4_2),
100 0x04300000 => Ok(ProfileVersion::V4_3),
101 0x04400000 => Ok(ProfileVersion::V4_3),
102 _ => Err(CmsError::InvalidProfile),
103 }
104 }
105}
106
107impl From<ProfileVersion> for u32 {
108 fn from(value: ProfileVersion) -> Self {
109 match value {
110 ProfileVersion::V2_0 => 0x02000000,
111 ProfileVersion::V2_1 => 0x02100000,
112 ProfileVersion::V2_2 => 0x02200000,
113 ProfileVersion::V2_3 => 0x02300000,
114 ProfileVersion::V2_4 => 0x02400000,
115 ProfileVersion::V4_0 => 0x04000000,
116 ProfileVersion::V4_1 => 0x04100000,
117 ProfileVersion::V4_2 => 0x04200000,
118 ProfileVersion::V4_3 => 0x04300000,
119 ProfileVersion::V4_4 => 0x04400000,
120 ProfileVersion::Unknown => 0x02000000,
121 }
122 }
123}
124
125#[repr(u32)]
126#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Hash)]
127pub enum DataColorSpace {
128 #[default]
129 Xyz,
130 Lab,
131 Luv,
132 YCbr,
133 Yxy,
134 Rgb,
135 Gray,
136 Hsv,
137 Hls,
138 Cmyk,
139 Cmy,
140 Color2,
141 Color3,
142 Color4,
143 Color5,
144 Color6,
145 Color7,
146 Color8,
147 Color9,
148 Color10,
149 Color11,
150 Color12,
151 Color13,
152 Color14,
153 Color15,
154}
155
156impl DataColorSpace {
157 #[inline]
158 pub fn check_layout(self, layout: Layout) -> Result<(), CmsError> {
159 let unsupported: bool = match self {
160 DataColorSpace::Xyz => layout != Layout::Rgb,
161 DataColorSpace::Lab => layout != Layout::Rgb,
162 DataColorSpace::Luv => layout != Layout::Rgb,
163 DataColorSpace::YCbr => layout != Layout::Rgb,
164 DataColorSpace::Yxy => layout != Layout::Rgb,
165 DataColorSpace::Rgb => layout != Layout::Rgb && layout != Layout::Rgba,
166 DataColorSpace::Gray => layout != Layout::Gray && layout != Layout::GrayAlpha,
167 DataColorSpace::Hsv => layout != Layout::Rgb,
168 DataColorSpace::Hls => layout != Layout::Rgb,
169 DataColorSpace::Cmyk => layout != Layout::Rgba,
170 DataColorSpace::Cmy => layout != Layout::Rgb,
171 DataColorSpace::Color2 => layout != Layout::GrayAlpha,
172 DataColorSpace::Color3 => layout != Layout::Rgb,
173 DataColorSpace::Color4 => layout != Layout::Rgba,
174 DataColorSpace::Color5 => layout != Layout::Inks5,
175 DataColorSpace::Color6 => layout != Layout::Inks6,
176 DataColorSpace::Color7 => layout != Layout::Inks7,
177 DataColorSpace::Color8 => layout != Layout::Inks8,
178 DataColorSpace::Color9 => layout != Layout::Inks9,
179 DataColorSpace::Color10 => layout != Layout::Inks10,
180 DataColorSpace::Color11 => layout != Layout::Inks11,
181 DataColorSpace::Color12 => layout != Layout::Inks12,
182 DataColorSpace::Color13 => layout != Layout::Inks13,
183 DataColorSpace::Color14 => layout != Layout::Inks14,
184 DataColorSpace::Color15 => layout != Layout::Inks15,
185 };
186 if unsupported {
187 Err(CmsError::InvalidLayout)
188 } else {
189 Ok(())
190 }
191 }
192
193 pub(crate) fn is_three_channels(self) -> bool {
194 matches!(
195 self,
196 DataColorSpace::Xyz
197 | DataColorSpace::Lab
198 | DataColorSpace::Luv
199 | DataColorSpace::YCbr
200 | DataColorSpace::Yxy
201 | DataColorSpace::Rgb
202 | DataColorSpace::Hsv
203 | DataColorSpace::Hls
204 | DataColorSpace::Cmy
205 | DataColorSpace::Color3
206 )
207 }
208}
209
210#[repr(u32)]
211#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)]
212pub enum ProfileClass {
213 InputDevice,
214 #[default]
215 DisplayDevice,
216 OutputDevice,
217 DeviceLink,
218 ColorSpace,
219 Abstract,
220 Named,
221}
222
223impl TryFrom<u32> for ProfileClass {
224 type Error = CmsError;
225 fn try_from(value: u32) -> Result<Self, Self::Error> {
226 if value == u32::from_ne_bytes(*b"scnr").to_be() {
227 return Ok(ProfileClass::InputDevice);
228 } else if value == u32::from_ne_bytes(*b"mntr").to_be() {
229 return Ok(ProfileClass::DisplayDevice);
230 } else if value == u32::from_ne_bytes(*b"prtr").to_be() {
231 return Ok(ProfileClass::OutputDevice);
232 } else if value == u32::from_ne_bytes(*b"link").to_be() {
233 return Ok(ProfileClass::DeviceLink);
234 } else if value == u32::from_ne_bytes(*b"spac").to_be() {
235 return Ok(ProfileClass::ColorSpace);
236 } else if value == u32::from_ne_bytes(*b"abst").to_be() {
237 return Ok(ProfileClass::Abstract);
238 } else if value == u32::from_ne_bytes(*b"nmcl").to_be() {
239 return Ok(ProfileClass::Named);
240 }
241 Err(CmsError::InvalidProfile)
242 }
243}
244
245impl From<ProfileClass> for u32 {
246 fn from(val: ProfileClass) -> Self {
247 match val {
248 ProfileClass::InputDevice => u32::from_ne_bytes(*b"scnr").to_be(),
249 ProfileClass::DisplayDevice => u32::from_ne_bytes(*b"mntr").to_be(),
250 ProfileClass::OutputDevice => u32::from_ne_bytes(*b"prtr").to_be(),
251 ProfileClass::DeviceLink => u32::from_ne_bytes(*b"link").to_be(),
252 ProfileClass::ColorSpace => u32::from_ne_bytes(*b"spac").to_be(),
253 ProfileClass::Abstract => u32::from_ne_bytes(*b"abst").to_be(),
254 ProfileClass::Named => u32::from_ne_bytes(*b"nmcl").to_be(),
255 }
256 }
257}
258
259#[derive(Debug, Clone)]
260pub enum LutStore {
261 Store8(Vec<u8>),
262 Store16(Vec<u16>),
263}
264
265#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
266pub enum LutType {
267 Lut8,
268 Lut16,
269 LutMab,
270 LutMba,
271}
272
273impl TryFrom<u32> for LutType {
274 type Error = CmsError;
275 fn try_from(value: u32) -> Result<Self, Self::Error> {
276 if value == u32::from_ne_bytes(*b"mft1").to_be() {
277 return Ok(LutType::Lut8);
278 } else if value == u32::from_ne_bytes(*b"mft2").to_be() {
279 return Ok(LutType::Lut16);
280 } else if value == u32::from_ne_bytes(*b"mAB ").to_be() {
281 return Ok(LutType::LutMab);
282 } else if value == u32::from_ne_bytes(*b"mBA ").to_be() {
283 return Ok(LutType::LutMba);
284 }
285 Err(CmsError::InvalidProfile)
286 }
287}
288
289impl From<LutType> for u32 {
290 fn from(val: LutType) -> Self {
291 match val {
292 LutType::Lut8 => u32::from_ne_bytes(*b"mft1").to_be(),
293 LutType::Lut16 => u32::from_ne_bytes(*b"mft2").to_be(),
294 LutType::LutMab => u32::from_ne_bytes(*b"mAB ").to_be(),
295 LutType::LutMba => u32::from_ne_bytes(*b"mBA ").to_be(),
296 }
297 }
298}
299
300impl TryFrom<u32> for DataColorSpace {
301 type Error = CmsError;
302 fn try_from(value: u32) -> Result<Self, Self::Error> {
303 if value == u32::from_ne_bytes(*b"XYZ ").to_be() {
304 return Ok(DataColorSpace::Xyz);
305 } else if value == u32::from_ne_bytes(*b"Lab ").to_be() {
306 return Ok(DataColorSpace::Lab);
307 } else if value == u32::from_ne_bytes(*b"Luv ").to_be() {
308 return Ok(DataColorSpace::Luv);
309 } else if value == u32::from_ne_bytes(*b"YCbr").to_be() {
310 return Ok(DataColorSpace::YCbr);
311 } else if value == u32::from_ne_bytes(*b"Yxy ").to_be() {
312 return Ok(DataColorSpace::Yxy);
313 } else if value == u32::from_ne_bytes(*b"RGB ").to_be() {
314 return Ok(DataColorSpace::Rgb);
315 } else if value == u32::from_ne_bytes(*b"GRAY").to_be() {
316 return Ok(DataColorSpace::Gray);
317 } else if value == u32::from_ne_bytes(*b"HSV ").to_be() {
318 return Ok(DataColorSpace::Hsv);
319 } else if value == u32::from_ne_bytes(*b"HLS ").to_be() {
320 return Ok(DataColorSpace::Hls);
321 } else if value == u32::from_ne_bytes(*b"CMYK").to_be() {
322 return Ok(DataColorSpace::Cmyk);
323 } else if value == u32::from_ne_bytes(*b"CMY ").to_be() {
324 return Ok(DataColorSpace::Cmy);
325 } else if value == u32::from_ne_bytes(*b"2CLR").to_be() {
326 return Ok(DataColorSpace::Color2);
327 } else if value == u32::from_ne_bytes(*b"3CLR").to_be() {
328 return Ok(DataColorSpace::Color3);
329 } else if value == u32::from_ne_bytes(*b"4CLR").to_be() {
330 return Ok(DataColorSpace::Color4);
331 } else if value == u32::from_ne_bytes(*b"5CLR").to_be() {
332 return Ok(DataColorSpace::Color5);
333 } else if value == u32::from_ne_bytes(*b"6CLR").to_be() {
334 return Ok(DataColorSpace::Color6);
335 } else if value == u32::from_ne_bytes(*b"7CLR").to_be() {
336 return Ok(DataColorSpace::Color7);
337 } else if value == u32::from_ne_bytes(*b"8CLR").to_be() {
338 return Ok(DataColorSpace::Color8);
339 } else if value == u32::from_ne_bytes(*b"9CLR").to_be() {
340 return Ok(DataColorSpace::Color9);
341 } else if value == u32::from_ne_bytes(*b"ACLR").to_be() {
342 return Ok(DataColorSpace::Color10);
343 } else if value == u32::from_ne_bytes(*b"BCLR").to_be() {
344 return Ok(DataColorSpace::Color11);
345 } else if value == u32::from_ne_bytes(*b"CCLR").to_be() {
346 return Ok(DataColorSpace::Color12);
347 } else if value == u32::from_ne_bytes(*b"DCLR").to_be() {
348 return Ok(DataColorSpace::Color13);
349 } else if value == u32::from_ne_bytes(*b"ECLR").to_be() {
350 return Ok(DataColorSpace::Color14);
351 } else if value == u32::from_ne_bytes(*b"FCLR").to_be() {
352 return Ok(DataColorSpace::Color15);
353 }
354 Err(CmsError::InvalidProfile)
355 }
356}
357
358impl From<DataColorSpace> for u32 {
359 fn from(val: DataColorSpace) -> Self {
360 match val {
361 DataColorSpace::Xyz => u32::from_ne_bytes(*b"XYZ ").to_be(),
362 DataColorSpace::Lab => u32::from_ne_bytes(*b"Lab ").to_be(),
363 DataColorSpace::Luv => u32::from_ne_bytes(*b"Luv ").to_be(),
364 DataColorSpace::YCbr => u32::from_ne_bytes(*b"YCbr").to_be(),
365 DataColorSpace::Yxy => u32::from_ne_bytes(*b"Yxy ").to_be(),
366 DataColorSpace::Rgb => u32::from_ne_bytes(*b"RGB ").to_be(),
367 DataColorSpace::Gray => u32::from_ne_bytes(*b"GRAY").to_be(),
368 DataColorSpace::Hsv => u32::from_ne_bytes(*b"HSV ").to_be(),
369 DataColorSpace::Hls => u32::from_ne_bytes(*b"HLS ").to_be(),
370 DataColorSpace::Cmyk => u32::from_ne_bytes(*b"CMYK").to_be(),
371 DataColorSpace::Cmy => u32::from_ne_bytes(*b"CMY ").to_be(),
372 DataColorSpace::Color2 => u32::from_ne_bytes(*b"2CLR").to_be(),
373 DataColorSpace::Color3 => u32::from_ne_bytes(*b"3CLR").to_be(),
374 DataColorSpace::Color4 => u32::from_ne_bytes(*b"4CLR").to_be(),
375 DataColorSpace::Color5 => u32::from_ne_bytes(*b"5CLR").to_be(),
376 DataColorSpace::Color6 => u32::from_ne_bytes(*b"6CLR").to_be(),
377 DataColorSpace::Color7 => u32::from_ne_bytes(*b"7CLR").to_be(),
378 DataColorSpace::Color8 => u32::from_ne_bytes(*b"8CLR").to_be(),
379 DataColorSpace::Color9 => u32::from_ne_bytes(*b"9CLR").to_be(),
380 DataColorSpace::Color10 => u32::from_ne_bytes(*b"ACLR").to_be(),
381 DataColorSpace::Color11 => u32::from_ne_bytes(*b"BCLR").to_be(),
382 DataColorSpace::Color12 => u32::from_ne_bytes(*b"CCLR").to_be(),
383 DataColorSpace::Color13 => u32::from_ne_bytes(*b"DCLR").to_be(),
384 DataColorSpace::Color14 => u32::from_ne_bytes(*b"ECLR").to_be(),
385 DataColorSpace::Color15 => u32::from_ne_bytes(*b"FCLR").to_be(),
386 }
387 }
388}
389
390#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
391pub enum TechnologySignatures {
392 FilmScanner,
393 DigitalCamera,
394 ReflectiveScanner,
395 InkJetPrinter,
396 ThermalWaxPrinter,
397 ElectrophotographicPrinter,
398 ElectrostaticPrinter,
399 DyeSublimationPrinter,
400 PhotographicPaperPrinter,
401 FilmWriter,
402 VideoMonitor,
403 VideoCamera,
404 ProjectionTelevision,
405 CathodeRayTubeDisplay,
406 PassiveMatrixDisplay,
407 ActiveMatrixDisplay,
408 LiquidCrystalDisplay,
409 OrganicLedDisplay,
410 PhotoCd,
411 PhotographicImageSetter,
412 Gravure,
413 OffsetLithography,
414 Silkscreen,
415 Flexography,
416 MotionPictureFilmScanner,
417 MotionPictureFilmRecorder,
418 DigitalMotionPictureCamera,
419 DigitalCinemaProjector,
420 Unknown(u32),
421}
422
423impl From<u32> for TechnologySignatures {
424 fn from(value: u32) -> Self {
425 if value == u32::from_ne_bytes(*b"fscn").to_be() {
426 return TechnologySignatures::FilmScanner;
427 } else if value == u32::from_ne_bytes(*b"dcam").to_be() {
428 return TechnologySignatures::DigitalCamera;
429 } else if value == u32::from_ne_bytes(*b"rscn").to_be() {
430 return TechnologySignatures::ReflectiveScanner;
431 } else if value == u32::from_ne_bytes(*b"ijet").to_be() {
432 return TechnologySignatures::InkJetPrinter;
433 } else if value == u32::from_ne_bytes(*b"twax").to_be() {
434 return TechnologySignatures::ThermalWaxPrinter;
435 } else if value == u32::from_ne_bytes(*b"epho").to_be() {
436 return TechnologySignatures::ElectrophotographicPrinter;
437 } else if value == u32::from_ne_bytes(*b"esta").to_be() {
438 return TechnologySignatures::ElectrostaticPrinter;
439 } else if value == u32::from_ne_bytes(*b"dsub").to_be() {
440 return TechnologySignatures::DyeSublimationPrinter;
441 } else if value == u32::from_ne_bytes(*b"rpho").to_be() {
442 return TechnologySignatures::PhotographicPaperPrinter;
443 } else if value == u32::from_ne_bytes(*b"fprn").to_be() {
444 return TechnologySignatures::FilmWriter;
445 } else if value == u32::from_ne_bytes(*b"vidm").to_be() {
446 return TechnologySignatures::VideoMonitor;
447 } else if value == u32::from_ne_bytes(*b"vidc").to_be() {
448 return TechnologySignatures::VideoCamera;
449 } else if value == u32::from_ne_bytes(*b"pjtv").to_be() {
450 return TechnologySignatures::ProjectionTelevision;
451 } else if value == u32::from_ne_bytes(*b"CRT ").to_be() {
452 return TechnologySignatures::CathodeRayTubeDisplay;
453 } else if value == u32::from_ne_bytes(*b"PMD ").to_be() {
454 return TechnologySignatures::PassiveMatrixDisplay;
455 } else if value == u32::from_ne_bytes(*b"AMD ").to_be() {
456 return TechnologySignatures::ActiveMatrixDisplay;
457 } else if value == u32::from_ne_bytes(*b"LCD ").to_be() {
458 return TechnologySignatures::LiquidCrystalDisplay;
459 } else if value == u32::from_ne_bytes(*b"OLED").to_be() {
460 return TechnologySignatures::OrganicLedDisplay;
461 } else if value == u32::from_ne_bytes(*b"KPCD").to_be() {
462 return TechnologySignatures::PhotoCd;
463 } else if value == u32::from_ne_bytes(*b"imgs").to_be() {
464 return TechnologySignatures::PhotographicImageSetter;
465 } else if value == u32::from_ne_bytes(*b"grav").to_be() {
466 return TechnologySignatures::Gravure;
467 } else if value == u32::from_ne_bytes(*b"offs").to_be() {
468 return TechnologySignatures::OffsetLithography;
469 } else if value == u32::from_ne_bytes(*b"silk").to_be() {
470 return TechnologySignatures::Silkscreen;
471 } else if value == u32::from_ne_bytes(*b"flex").to_be() {
472 return TechnologySignatures::Flexography;
473 } else if value == u32::from_ne_bytes(*b"mpfs").to_be() {
474 return TechnologySignatures::MotionPictureFilmScanner;
475 } else if value == u32::from_ne_bytes(*b"mpfr").to_be() {
476 return TechnologySignatures::MotionPictureFilmRecorder;
477 } else if value == u32::from_ne_bytes(*b"dmpc").to_be() {
478 return TechnologySignatures::DigitalMotionPictureCamera;
479 } else if value == u32::from_ne_bytes(*b"dcpj").to_be() {
480 return TechnologySignatures::DigitalCinemaProjector;
481 }
482 TechnologySignatures::Unknown(value)
483 }
484}
485
486#[derive(Debug, Clone)]
487pub enum LutWarehouse {
488 Lut(LutDataType),
489 Multidimensional(LutMultidimensionalType),
490}
491
492#[derive(Debug, Clone)]
493pub struct LutDataType {
494 pub num_input_channels: u8,
496 pub num_output_channels: u8,
497 pub num_clut_grid_points: u8,
498 pub matrix: Matrix3d,
499 pub num_input_table_entries: u16,
500 pub num_output_table_entries: u16,
501 pub input_table: LutStore,
502 pub clut_table: LutStore,
503 pub output_table: LutStore,
504 pub lut_type: LutType,
505}
506
507impl LutDataType {
508 pub(crate) fn has_same_kind(&self) -> bool {
509 matches!(
510 (&self.input_table, &self.clut_table, &self.output_table),
511 (
512 LutStore::Store8(_),
513 LutStore::Store8(_),
514 LutStore::Store8(_)
515 ) | (
516 LutStore::Store16(_),
517 LutStore::Store16(_),
518 LutStore::Store16(_)
519 )
520 )
521 }
522}
523
524#[derive(Debug, Clone)]
525pub struct LutMultidimensionalType {
526 pub num_input_channels: u8,
527 pub num_output_channels: u8,
528 pub grid_points: [u8; 16],
529 pub clut: Option<LutStore>,
530 pub a_curves: Vec<ToneReprCurve>,
531 pub b_curves: Vec<ToneReprCurve>,
532 pub m_curves: Vec<ToneReprCurve>,
533 pub matrix: Matrix3d,
534 pub bias: Vector3d,
535}
536
537#[repr(u32)]
538#[derive(Clone, Copy, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash)]
539pub enum RenderingIntent {
540 AbsoluteColorimetric = 3,
541 Saturation = 2,
542 RelativeColorimetric = 1,
543 #[default]
544 Perceptual = 0,
545}
546
547impl TryFrom<u32> for RenderingIntent {
548 type Error = CmsError;
549
550 #[inline]
551 fn try_from(value: u32) -> Result<Self, Self::Error> {
552 match value {
553 0 => Ok(RenderingIntent::Perceptual),
554 1 => Ok(RenderingIntent::RelativeColorimetric),
555 2 => Ok(RenderingIntent::Saturation),
556 3 => Ok(RenderingIntent::AbsoluteColorimetric),
557 _ => Err(CmsError::InvalidRenderingIntent),
558 }
559 }
560}
561
562impl From<RenderingIntent> for u32 {
563 #[inline]
564 fn from(value: RenderingIntent) -> Self {
565 match value {
566 RenderingIntent::AbsoluteColorimetric => 3,
567 RenderingIntent::Saturation => 2,
568 RenderingIntent::RelativeColorimetric => 1,
569 RenderingIntent::Perceptual => 0,
570 }
571 }
572}
573
574#[repr(C)]
576#[derive(Debug, Clone, Copy)]
577pub(crate) struct ProfileHeader {
578 pub size: u32, pub cmm_type: u32, pub version: ProfileVersion, pub profile_class: ProfileClass, pub data_color_space: DataColorSpace, pub pcs: DataColorSpace, pub creation_date_time: ColorDateTime, pub signature: ProfileSignature, pub platform: u32, pub flags: u32, pub device_manufacturer: u32, pub device_model: u32, pub device_attributes: [u8; 8], pub rendering_intent: RenderingIntent, pub illuminant: Xyz, pub creator: u32, pub profile_id: [u8; 16], pub reserved: [u8; 28], pub tag_count: u32, }
598
599impl ProfileHeader {
600 #[allow(dead_code)]
601 pub(crate) fn new(size: u32) -> Self {
602 Self {
603 size,
604 cmm_type: 0,
605 version: ProfileVersion::V4_3,
606 profile_class: ProfileClass::DisplayDevice,
607 data_color_space: DataColorSpace::Rgb,
608 pcs: DataColorSpace::Xyz,
609 creation_date_time: ColorDateTime::default(),
610 signature: ProfileSignature::Acsp,
611 platform: 0,
612 flags: 0x00000000,
613 device_manufacturer: 0,
614 device_model: 0,
615 device_attributes: [0; 8],
616 rendering_intent: RenderingIntent::Perceptual,
617 illuminant: Chromaticity::D50.to_xyz(),
618 creator: 0,
619 profile_id: [0; 16],
620 reserved: [0; 28],
621 tag_count: 0,
622 }
623 }
624
625 pub(crate) fn new_from_slice(slice: &[u8]) -> Result<Self, CmsError> {
627 if slice.len() < size_of::<ProfileHeader>() {
628 return Err(CmsError::InvalidProfile);
629 }
630 let mut cursor = std::io::Cursor::new(slice);
631 let mut buffer = [0u8; size_of::<ProfileHeader>()];
632 cursor
633 .read_exact(&mut buffer)
634 .map_err(|_| CmsError::InvalidProfile)?;
635
636 let header = Self {
637 size: u32::from_be_bytes(buffer[0..4].try_into().unwrap()),
638 cmm_type: u32::from_be_bytes(buffer[4..8].try_into().unwrap()),
639 version: ProfileVersion::try_from(u32::from_be_bytes(
640 buffer[8..12].try_into().unwrap(),
641 ))?,
642 profile_class: ProfileClass::try_from(u32::from_be_bytes(
643 buffer[12..16].try_into().unwrap(),
644 ))?,
645 data_color_space: DataColorSpace::try_from(u32::from_be_bytes(
646 buffer[16..20].try_into().unwrap(),
647 ))?,
648 pcs: DataColorSpace::try_from(u32::from_be_bytes(buffer[20..24].try_into().unwrap()))?,
649 creation_date_time: ColorDateTime::new_from_slice(buffer[24..36].try_into().unwrap())?,
650 signature: ProfileSignature::try_from(u32::from_be_bytes(
651 buffer[36..40].try_into().unwrap(),
652 ))?,
653 platform: u32::from_be_bytes(buffer[40..44].try_into().unwrap()),
654 flags: u32::from_be_bytes(buffer[44..48].try_into().unwrap()),
655 device_manufacturer: u32::from_be_bytes(buffer[48..52].try_into().unwrap()),
656 device_model: u32::from_be_bytes(buffer[52..56].try_into().unwrap()),
657 device_attributes: buffer[56..64].try_into().unwrap(),
658 rendering_intent: RenderingIntent::try_from(u32::from_be_bytes(
659 buffer[64..68].try_into().unwrap(),
660 ))?,
661 illuminant: Xyz::new(
662 s15_fixed16_number_to_float(i32::from_be_bytes(buffer[68..72].try_into().unwrap())),
663 s15_fixed16_number_to_float(i32::from_be_bytes(buffer[72..76].try_into().unwrap())),
664 s15_fixed16_number_to_float(i32::from_be_bytes(buffer[76..80].try_into().unwrap())),
665 ),
666 creator: u32::from_be_bytes(buffer[80..84].try_into().unwrap()),
667 profile_id: buffer[84..100].try_into().unwrap(),
668 reserved: buffer[100..128].try_into().unwrap(),
669 tag_count: u32::from_be_bytes(buffer[128..132].try_into().unwrap()),
670 };
671 Ok(header)
672 }
673}
674
675#[repr(C)]
677#[derive(Debug, Clone, Copy)]
678pub struct CicpProfile {
679 pub color_primaries: CicpColorPrimaries,
680 pub transfer_characteristics: TransferCharacteristics,
681 pub matrix_coefficients: MatrixCoefficients,
682 pub full_range: bool,
683}
684
685#[derive(Debug, Clone)]
686pub struct LocalizableString {
687 pub language: String,
689 pub country: String,
691 pub value: String,
692}
693
694impl LocalizableString {
695 pub fn new(language: String, country: String, value: String) -> Self {
704 Self {
705 language,
706 country,
707 value,
708 }
709 }
710}
711
712#[derive(Debug, Clone)]
713pub struct DescriptionString {
714 pub ascii_string: String,
715 pub unicode_language_code: u32,
716 pub unicode_string: String,
717 pub script_code_code: i8,
718 pub mac_string: String,
719}
720
721#[derive(Debug, Clone)]
722pub enum ProfileText {
723 PlainString(String),
724 Localizable(Vec<LocalizableString>),
725 Description(DescriptionString),
726}
727
728impl ProfileText {
729 pub(crate) fn has_values(&self) -> bool {
730 match self {
731 ProfileText::PlainString(_) => true,
732 ProfileText::Localizable(lc) => !lc.is_empty(),
733 ProfileText::Description(_) => true,
734 }
735 }
736}
737
738#[derive(Debug, Clone, Copy)]
739pub enum StandardObserver {
740 D50,
741 D65,
742 Unknown,
743}
744
745impl From<u32> for StandardObserver {
746 fn from(value: u32) -> Self {
747 if value == 1 {
748 return StandardObserver::D50;
749 } else if value == 2 {
750 return StandardObserver::D65;
751 }
752 StandardObserver::Unknown
753 }
754}
755
756#[derive(Debug, Clone, Copy)]
757pub struct ViewingConditions {
758 pub illuminant: Xyz,
759 pub surround: Xyz,
760 pub observer: StandardObserver,
761}
762
763#[derive(Debug, Clone, Copy)]
764pub enum MeasurementGeometry {
765 Unknown,
766 D45to45,
768 D0to0,
770}
771
772impl From<u32> for MeasurementGeometry {
773 fn from(value: u32) -> Self {
774 if value == 1 {
775 Self::D45to45
776 } else if value == 2 {
777 Self::D0to0
778 } else {
779 Self::Unknown
780 }
781 }
782}
783
784#[derive(Debug, Clone, Copy)]
785pub enum StandardIlluminant {
786 Unknown,
787 D50,
788 D65,
789 D93,
790 F2,
791 D55,
792 A,
793 EquiPower,
794 F8,
795}
796
797impl From<u32> for StandardIlluminant {
798 fn from(value: u32) -> Self {
799 match value {
800 1 => StandardIlluminant::D50,
801 2 => StandardIlluminant::D65,
802 3 => StandardIlluminant::D93,
803 4 => StandardIlluminant::F2,
804 5 => StandardIlluminant::D55,
805 6 => StandardIlluminant::A,
806 7 => StandardIlluminant::EquiPower,
807 8 => StandardIlluminant::F8,
808 _ => Self::Unknown,
809 }
810 }
811}
812
813impl From<StandardIlluminant> for u32 {
814 fn from(value: StandardIlluminant) -> Self {
815 match value {
816 StandardIlluminant::Unknown => 0u32,
817 StandardIlluminant::D50 => 1u32,
818 StandardIlluminant::D65 => 2u32,
819 StandardIlluminant::D93 => 3,
820 StandardIlluminant::F2 => 4,
821 StandardIlluminant::D55 => 5,
822 StandardIlluminant::A => 6,
823 StandardIlluminant::EquiPower => 7,
824 StandardIlluminant::F8 => 8,
825 }
826 }
827}
828
829#[derive(Debug, Clone, Copy)]
830pub struct Measurement {
831 pub observer: StandardObserver,
832 pub backing: Xyz,
833 pub geometry: MeasurementGeometry,
834 pub flare: f32,
835 pub illuminant: StandardIlluminant,
836}
837
838#[repr(C)]
840#[derive(Debug, Clone, Default)]
841pub struct ColorProfile {
842 pub pcs: DataColorSpace,
843 pub color_space: DataColorSpace,
844 pub profile_class: ProfileClass,
845 pub rendering_intent: RenderingIntent,
846 pub red_colorant: Xyzd,
847 pub green_colorant: Xyzd,
848 pub blue_colorant: Xyzd,
849 pub white_point: Xyzd,
850 pub black_point: Option<Xyzd>,
851 pub media_white_point: Option<Xyzd>,
852 pub luminance: Option<Xyzd>,
853 pub measurement: Option<Measurement>,
854 pub red_trc: Option<ToneReprCurve>,
855 pub green_trc: Option<ToneReprCurve>,
856 pub blue_trc: Option<ToneReprCurve>,
857 pub gray_trc: Option<ToneReprCurve>,
858 pub cicp: Option<CicpProfile>,
859 pub chromatic_adaptation: Option<Matrix3d>,
860 pub lut_a_to_b_perceptual: Option<LutWarehouse>,
861 pub lut_a_to_b_colorimetric: Option<LutWarehouse>,
862 pub lut_a_to_b_saturation: Option<LutWarehouse>,
863 pub lut_b_to_a_perceptual: Option<LutWarehouse>,
864 pub lut_b_to_a_colorimetric: Option<LutWarehouse>,
865 pub lut_b_to_a_saturation: Option<LutWarehouse>,
866 pub gamut: Option<LutWarehouse>,
867 pub copyright: Option<ProfileText>,
868 pub description: Option<ProfileText>,
869 pub device_manufacturer: Option<ProfileText>,
870 pub device_model: Option<ProfileText>,
871 pub char_target: Option<ProfileText>,
872 pub viewing_conditions: Option<ViewingConditions>,
873 pub viewing_conditions_description: Option<ProfileText>,
874 pub technology: Option<TechnologySignatures>,
875 pub calibration_date: Option<ColorDateTime>,
876 pub(crate) version_internal: ProfileVersion,
879}
880
881#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Hash)]
882pub struct ParsingOptions {
883 pub max_profile_size: usize,
885 pub max_allowed_clut_size: usize,
887 pub max_allowed_trc_size: usize,
889}
890
891impl Default for ParsingOptions {
892 fn default() -> Self {
893 Self {
894 max_profile_size: MAX_PROFILE_SIZE,
895 max_allowed_clut_size: 10_000_000,
896 max_allowed_trc_size: 40_000,
897 }
898 }
899}
900
901impl ColorProfile {
902 pub fn version(&self) -> ProfileVersion {
904 self.version_internal
905 }
906
907 pub fn new_from_slice(slice: &[u8]) -> Result<Self, CmsError> {
908 Self::new_from_slice_with_options(slice, Default::default())
909 }
910
911 pub fn new_from_slice_with_options(
912 slice: &[u8],
913 options: ParsingOptions,
914 ) -> Result<Self, CmsError> {
915 let header = ProfileHeader::new_from_slice(slice)?;
916 let tags_count = header.tag_count as usize;
917 if slice.len() >= options.max_profile_size {
918 return Err(CmsError::InvalidProfile);
919 }
920 let tags_end = tags_count
921 .safe_mul(TAG_SIZE)?
922 .safe_add(size_of::<ProfileHeader>())?;
923 if slice.len() < tags_end {
924 return Err(CmsError::InvalidProfile);
925 }
926 let tags_slice = &slice[size_of::<ProfileHeader>()..tags_end];
927 let mut profile = ColorProfile {
928 rendering_intent: header.rendering_intent,
929 pcs: header.pcs,
930 profile_class: header.profile_class,
931 color_space: header.data_color_space,
932 white_point: header.illuminant.to_xyzd(),
933 version_internal: header.version,
934 ..Default::default()
935 };
936 let color_space = profile.color_space;
937 for tag in tags_slice.chunks_exact(TAG_SIZE) {
938 let tag_value = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
939 let tag_entry = u32::from_be_bytes([tag[4], tag[5], tag[6], tag[7]]);
940 let tag_size = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
941 if let Ok(tag) = Tag::try_from(tag_value) {
943 match tag {
944 Tag::RedXyz => {
945 if color_space == DataColorSpace::Rgb {
946 profile.red_colorant =
947 Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
948 }
949 }
950 Tag::GreenXyz => {
951 if color_space == DataColorSpace::Rgb {
952 profile.green_colorant =
953 Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
954 }
955 }
956 Tag::BlueXyz => {
957 if color_space == DataColorSpace::Rgb {
958 profile.blue_colorant =
959 Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
960 }
961 }
962 Tag::RedToneReproduction => {
963 if color_space == DataColorSpace::Rgb {
964 profile.red_trc = Self::read_trc_tag_s(
965 slice,
966 tag_entry as usize,
967 tag_size,
968 &options,
969 )?;
970 }
971 }
972 Tag::GreenToneReproduction => {
973 if color_space == DataColorSpace::Rgb {
974 profile.green_trc = Self::read_trc_tag_s(
975 slice,
976 tag_entry as usize,
977 tag_size,
978 &options,
979 )?;
980 }
981 }
982 Tag::BlueToneReproduction => {
983 if color_space == DataColorSpace::Rgb {
984 profile.blue_trc = Self::read_trc_tag_s(
985 slice,
986 tag_entry as usize,
987 tag_size,
988 &options,
989 )?;
990 }
991 }
992 Tag::GreyToneReproduction => {
993 if color_space == DataColorSpace::Gray {
994 profile.gray_trc = Self::read_trc_tag_s(
995 slice,
996 tag_entry as usize,
997 tag_size,
998 &options,
999 )?;
1000 }
1001 }
1002 Tag::MediaWhitePoint => {
1003 profile.media_white_point =
1004 Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?;
1005 }
1006 Tag::Luminance => {
1007 profile.luminance =
1008 Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?;
1009 }
1010 Tag::Measurement => {
1011 profile.measurement =
1012 Self::read_meas_tag(slice, tag_entry as usize, tag_size)?;
1013 }
1014 Tag::CodeIndependentPoints => {
1015 if (profile.profile_class == ProfileClass::InputDevice
1019 || profile.profile_class == ProfileClass::DisplayDevice)
1020 && (profile.color_space == DataColorSpace::Rgb
1021 || profile.color_space == DataColorSpace::YCbr
1022 || profile.color_space == DataColorSpace::Xyz)
1023 {
1024 profile.cicp =
1025 Self::read_cicp_tag(slice, tag_entry as usize, tag_size)?;
1026 }
1027 }
1028 Tag::ChromaticAdaptation => {
1029 profile.chromatic_adaptation =
1030 Self::read_chad_tag(slice, tag_entry as usize, tag_size)?;
1031 }
1032 Tag::BlackPoint => {
1033 profile.black_point =
1034 Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?
1035 }
1036 Tag::DeviceToPcsLutPerceptual => {
1037 profile.lut_a_to_b_perceptual =
1038 Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1039 }
1040 Tag::DeviceToPcsLutColorimetric => {
1041 profile.lut_a_to_b_colorimetric =
1042 Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1043 }
1044 Tag::DeviceToPcsLutSaturation => {
1045 profile.lut_a_to_b_saturation =
1046 Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1047 }
1048 Tag::PcsToDeviceLutPerceptual => {
1049 profile.lut_b_to_a_perceptual =
1050 Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1051 }
1052 Tag::PcsToDeviceLutColorimetric => {
1053 profile.lut_b_to_a_colorimetric =
1054 Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1055 }
1056 Tag::PcsToDeviceLutSaturation => {
1057 profile.lut_b_to_a_saturation =
1058 Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1059 }
1060 Tag::Gamut => {
1061 profile.gamut = Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1062 }
1063 Tag::Copyright => {
1064 profile.copyright =
1065 Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1066 }
1067 Tag::ProfileDescription => {
1068 profile.description =
1069 Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1070 }
1071 Tag::ViewingConditionsDescription => {
1072 profile.viewing_conditions_description =
1073 Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1074 }
1075 Tag::DeviceModel => {
1076 profile.device_model =
1077 Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1078 }
1079 Tag::DeviceManufacturer => {
1080 profile.device_manufacturer =
1081 Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1082 }
1083 Tag::CharTarget => {
1084 profile.char_target =
1085 Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1086 }
1087 Tag::Chromaticity => {}
1088 Tag::ObserverConditions => {
1089 profile.viewing_conditions =
1090 Self::read_viewing_conditions(slice, tag_entry as usize, tag_size)?;
1091 }
1092 Tag::Technology => {
1093 profile.technology =
1094 Self::read_tech_tag(slice, tag_entry as usize, tag_size)?;
1095 }
1096 Tag::CalibrationDateTime => {
1097 profile.calibration_date =
1098 Self::read_date_time_tag(slice, tag_entry as usize, tag_size)?;
1099 }
1100 }
1101 }
1102 }
1103
1104 Ok(profile)
1105 }
1106}
1107
1108impl ColorProfile {
1109 #[inline]
1110 pub fn colorant_matrix(&self) -> Matrix3d {
1111 Matrix3d {
1112 v: [
1113 [
1114 self.red_colorant.x,
1115 self.green_colorant.x,
1116 self.blue_colorant.x,
1117 ],
1118 [
1119 self.red_colorant.y,
1120 self.green_colorant.y,
1121 self.blue_colorant.y,
1122 ],
1123 [
1124 self.red_colorant.z,
1125 self.green_colorant.z,
1126 self.blue_colorant.z,
1127 ],
1128 ],
1129 }
1130 }
1131
1132 pub const fn colorants_matrix(white_point: XyY, primaries: ColorPrimaries) -> Matrix3d {
1137 let red_xyz = primaries.red.to_xyzd();
1138 let green_xyz = primaries.green.to_xyzd();
1139 let blue_xyz = primaries.blue.to_xyzd();
1140
1141 let xyz_matrix = Matrix3d {
1142 v: [
1143 [red_xyz.x, green_xyz.x, blue_xyz.x],
1144 [red_xyz.y, green_xyz.y, blue_xyz.y],
1145 [red_xyz.z, green_xyz.z, blue_xyz.z],
1146 ],
1147 };
1148 let colorants = ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyzd());
1149 adapt_to_d50_d(colorants, white_point)
1150 }
1151
1152 pub const fn update_rgb_colorimetry(&mut self, white_point: XyY, primaries: ColorPrimaries) {
1154 let red_xyz = primaries.red.to_xyzd();
1155 let green_xyz = primaries.green.to_xyzd();
1156 let blue_xyz = primaries.blue.to_xyzd();
1157
1158 self.chromatic_adaptation = Some(BRADFORD_D);
1159 self.update_rgb_colorimetry_triplet(white_point, red_xyz, green_xyz, blue_xyz)
1160 }
1161
1162 pub const fn update_rgb_colorimetry_triplet(
1167 &mut self,
1168 white_point: XyY,
1169 red_xyz: Xyzd,
1170 green_xyz: Xyzd,
1171 blue_xyz: Xyzd,
1172 ) {
1173 let xyz_matrix = Matrix3d {
1174 v: [
1175 [red_xyz.x, green_xyz.x, blue_xyz.x],
1176 [red_xyz.y, green_xyz.y, blue_xyz.y],
1177 [red_xyz.z, green_xyz.z, blue_xyz.z],
1178 ],
1179 };
1180 let colorants = ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyzd());
1181 let colorants = adapt_to_d50_d(colorants, white_point);
1182
1183 self.update_colorants(colorants);
1184 }
1185
1186 pub(crate) const fn update_colorants(&mut self, colorants: Matrix3d) {
1187 self.red_colorant.x = colorants.v[0][0];
1189 self.red_colorant.y = colorants.v[1][0];
1190 self.red_colorant.z = colorants.v[2][0];
1191 self.green_colorant.x = colorants.v[0][1];
1192 self.green_colorant.y = colorants.v[1][1];
1193 self.green_colorant.z = colorants.v[2][1];
1194 self.blue_colorant.x = colorants.v[0][2];
1195 self.blue_colorant.y = colorants.v[1][2];
1196 self.blue_colorant.z = colorants.v[2][2];
1197 }
1198
1199 pub fn update_rgb_colorimetry_from_cicp(&mut self, cicp: CicpProfile) -> bool {
1201 self.cicp = Some(cicp);
1202 if !cicp.color_primaries.has_chromaticity()
1203 || !cicp.transfer_characteristics.has_transfer_curve()
1204 {
1205 return false;
1206 }
1207 let primaries_xy: ColorPrimaries = match cicp.color_primaries.try_into() {
1208 Ok(primaries) => primaries,
1209 Err(_) => return false,
1210 };
1211 let white_point: Chromaticity = match cicp.color_primaries.white_point() {
1212 Ok(v) => v,
1213 Err(_) => return false,
1214 };
1215 self.update_rgb_colorimetry(white_point.to_xyyb(), primaries_xy);
1216
1217 let red_trc: ToneReprCurve = match cicp.transfer_characteristics.try_into() {
1218 Ok(trc) => trc,
1219 Err(_) => return false,
1220 };
1221 self.green_trc = Some(red_trc.clone());
1222 self.blue_trc = Some(red_trc.clone());
1223 self.red_trc = Some(red_trc);
1224 false
1225 }
1226
1227 pub const fn rgb_to_xyz(&self, xyz_matrix: Matrix3f, wp: Xyz) -> Matrix3f {
1228 let xyz_inverse = xyz_matrix.inverse();
1229 let s = xyz_inverse.mul_vector(wp.to_vector());
1230 let mut v = xyz_matrix.mul_row_vector::<0>(s);
1231 v = v.mul_row_vector::<1>(s);
1232 v.mul_row_vector::<2>(s)
1233 }
1234
1235 pub(crate) const fn rgb_to_xyz_static(xyz_matrix: Matrix3f, wp: Xyz) -> Matrix3f {
1237 let xyz_inverse = xyz_matrix.inverse();
1238 let s = xyz_inverse.mul_vector(wp.to_vector());
1239 let mut v = xyz_matrix.mul_row_vector::<0>(s);
1240 v = v.mul_row_vector::<1>(s);
1241 v.mul_row_vector::<2>(s)
1242 }
1243
1244 pub const fn rgb_to_xyz_d(xyz_matrix: Matrix3d, wp: Xyzd) -> Matrix3d {
1247 let xyz_inverse = xyz_matrix.inverse();
1248 let s = xyz_inverse.mul_vector(wp.to_vector_d());
1249 let mut v = xyz_matrix.mul_row_vector::<0>(s);
1250 v = v.mul_row_vector::<1>(s);
1251 v = v.mul_row_vector::<2>(s);
1252 v
1253 }
1254
1255 pub fn rgb_to_xyz_matrix(&self) -> Matrix3d {
1256 let xyz_matrix = self.colorant_matrix();
1257 let white_point = Chromaticity::D50.to_xyzd();
1258 ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point)
1259 }
1260
1261 pub fn transform_matrix(&self, dest: &ColorProfile) -> Matrix3d {
1264 let source = self.rgb_to_xyz_matrix();
1265 let dst = dest.rgb_to_xyz_matrix();
1266 let dest_inverse = dst.inverse();
1267 dest_inverse.mat_mul(source)
1268 }
1269
1270 pub fn profile_volume(&self) -> Option<f32> {
1272 let red_prim = self.red_colorant;
1273 let green_prim = self.green_colorant;
1274 let blue_prim = self.blue_colorant;
1275 let tetrahedral_vertices = Matrix3d {
1276 v: [
1277 [red_prim.x, red_prim.y, red_prim.z],
1278 [green_prim.x, green_prim.y, green_prim.z],
1279 [blue_prim.x, blue_prim.y, blue_prim.z],
1280 ],
1281 };
1282 let det = tetrahedral_vertices.determinant()?;
1283 Some((det / 6.0f64) as f32)
1284 }
1285
1286 pub(crate) fn has_device_to_pcs_lut(&self) -> bool {
1287 self.lut_a_to_b_perceptual.is_some()
1288 || self.lut_a_to_b_saturation.is_some()
1289 || self.lut_a_to_b_colorimetric.is_some()
1290 }
1291
1292 pub(crate) fn has_pcs_to_device_lut(&self) -> bool {
1293 self.lut_b_to_a_perceptual.is_some()
1294 || self.lut_b_to_a_saturation.is_some()
1295 || self.lut_b_to_a_colorimetric.is_some()
1296 }
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301 use super::*;
1302 use std::fs;
1303
1304 #[test]
1305 fn test_gray() {
1306 if let Ok(gray_icc) = fs::read("./assets/Generic Gray Gamma 2.2 Profile.icc") {
1307 let f_p = ColorProfile::new_from_slice(&gray_icc).unwrap();
1308 assert!(f_p.gray_trc.is_some());
1309 }
1310 }
1311
1312 #[test]
1313 fn test_perceptual() {
1314 if let Ok(srgb_perceptual_icc) = fs::read("./assets/srgb_perceptual.icc") {
1315 let f_p = ColorProfile::new_from_slice(&srgb_perceptual_icc).unwrap();
1316 assert_eq!(f_p.pcs, DataColorSpace::Lab);
1317 assert_eq!(f_p.color_space, DataColorSpace::Rgb);
1318 assert_eq!(f_p.version(), ProfileVersion::V4_2);
1319 assert!(f_p.lut_a_to_b_perceptual.is_some());
1320 assert!(f_p.lut_b_to_a_perceptual.is_some());
1321 }
1322 }
1323
1324 #[test]
1325 fn test_us_swop_coated() {
1326 if let Ok(us_swop_coated) = fs::read("./assets/us_swop_coated.icc") {
1327 let f_p = ColorProfile::new_from_slice(&us_swop_coated).unwrap();
1328 assert_eq!(f_p.pcs, DataColorSpace::Lab);
1329 assert_eq!(f_p.color_space, DataColorSpace::Cmyk);
1330 assert_eq!(f_p.version(), ProfileVersion::V2_0);
1331
1332 assert!(f_p.lut_a_to_b_perceptual.is_some());
1333 assert!(f_p.lut_b_to_a_perceptual.is_some());
1334
1335 assert!(f_p.lut_a_to_b_colorimetric.is_some());
1336 assert!(f_p.lut_b_to_a_colorimetric.is_some());
1337
1338 assert!(f_p.gamut.is_some());
1339
1340 assert!(f_p.copyright.is_some());
1341 assert!(f_p.description.is_some());
1342 }
1343 }
1344
1345 #[test]
1346 fn test_matrix_shaper() {
1347 if let Ok(matrix_shaper) = fs::read("./assets/Display P3.icc") {
1348 let f_p = ColorProfile::new_from_slice(&matrix_shaper).unwrap();
1349 assert_eq!(f_p.pcs, DataColorSpace::Xyz);
1350 assert_eq!(f_p.color_space, DataColorSpace::Rgb);
1351 assert_eq!(f_p.version(), ProfileVersion::V4_0);
1352
1353 assert!(f_p.red_trc.is_some());
1354 assert!(f_p.blue_trc.is_some());
1355 assert!(f_p.green_trc.is_some());
1356
1357 assert_ne!(f_p.red_colorant, Xyzd::default());
1358 assert_ne!(f_p.blue_colorant, Xyzd::default());
1359 assert_ne!(f_p.green_colorant, Xyzd::default());
1360
1361 assert!(f_p.copyright.is_some());
1362 assert!(f_p.description.is_some());
1363 }
1364 }
1365}