Coverage Report

Created: 2025-12-20 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.11/src/profile.rs
Line
Count
Source
1
/*
2
 * // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
3
 * //
4
 * // Redistribution and use in source and binary forms, with or without modification,
5
 * // are permitted provided that the following conditions are met:
6
 * //
7
 * // 1.  Redistributions of source code must retain the above copyright notice, this
8
 * // list of conditions and the following disclaimer.
9
 * //
10
 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11
 * // this list of conditions and the following disclaimer in the documentation
12
 * // and/or other materials provided with the distribution.
13
 * //
14
 * // 3.  Neither the name of the copyright holder nor the names of its
15
 * // contributors may be used to endorse or promote products derived from
16
 * // this software without specific prior written permission.
17
 * //
18
 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29
use crate::chad::BRADFORD_D;
30
use crate::cicp::{
31
    CicpColorPrimaries, ColorPrimaries, MatrixCoefficients, TransferCharacteristics,
32
};
33
use crate::dat::ColorDateTime;
34
use crate::err::CmsError;
35
use crate::matrix::{Matrix3f, Xyz};
36
use crate::reader::s15_fixed16_number_to_float;
37
use crate::safe_math::{SafeAdd, SafeMul};
38
use crate::tag::{TAG_SIZE, Tag};
39
use crate::trc::ToneReprCurve;
40
use crate::{Chromaticity, Layout, Matrix3d, Vector3d, XyY, Xyzd, adapt_to_d50_d};
41
use std::io::Read;
42
43
const MAX_PROFILE_SIZE: usize = 1024 * 1024 * 10; // 10 MB max, for Fogra39 etc
44
45
#[repr(u32)]
46
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47
pub enum ProfileSignature {
48
    Acsp,
49
}
50
51
impl TryFrom<u32> for ProfileSignature {
52
    type Error = CmsError;
53
    #[inline]
54
0
    fn try_from(value: u32) -> Result<Self, Self::Error> {
55
0
        if value == u32::from_ne_bytes(*b"acsp").to_be() {
56
0
            return Ok(ProfileSignature::Acsp);
57
0
        }
58
0
        Err(CmsError::InvalidProfile)
59
0
    }
60
}
61
62
impl From<ProfileSignature> for u32 {
63
    #[inline]
64
0
    fn from(value: ProfileSignature) -> Self {
65
0
        match value {
66
0
            ProfileSignature::Acsp => u32::from_ne_bytes(*b"acsp").to_be(),
67
        }
68
0
    }
69
}
70
71
#[repr(u32)]
72
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Ord, PartialOrd)]
73
pub 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
88
impl TryFrom<u32> for ProfileVersion {
89
    type Error = CmsError;
90
0
    fn try_from(value: u32) -> Result<Self, Self::Error> {
91
0
        match value {
92
0
            0x02000000 => Ok(ProfileVersion::V2_0),
93
0
            0x02100000 => Ok(ProfileVersion::V2_1),
94
0
            0x02200000 => Ok(ProfileVersion::V2_2),
95
0
            0x02300000 => Ok(ProfileVersion::V2_3),
96
0
            0x02400000 => Ok(ProfileVersion::V2_4),
97
0
            0x04000000 => Ok(ProfileVersion::V4_0),
98
0
            0x04100000 => Ok(ProfileVersion::V4_1),
99
0
            0x04200000 => Ok(ProfileVersion::V4_2),
100
0
            0x04300000 => Ok(ProfileVersion::V4_3),
101
0
            0x04400000 => Ok(ProfileVersion::V4_3),
102
0
            _ => Err(CmsError::InvalidProfile),
103
        }
104
0
    }
105
}
106
107
impl From<ProfileVersion> for u32 {
108
0
    fn from(value: ProfileVersion) -> Self {
109
0
        match value {
110
0
            ProfileVersion::V2_0 => 0x02000000,
111
0
            ProfileVersion::V2_1 => 0x02100000,
112
0
            ProfileVersion::V2_2 => 0x02200000,
113
0
            ProfileVersion::V2_3 => 0x02300000,
114
0
            ProfileVersion::V2_4 => 0x02400000,
115
0
            ProfileVersion::V4_0 => 0x04000000,
116
0
            ProfileVersion::V4_1 => 0x04100000,
117
0
            ProfileVersion::V4_2 => 0x04200000,
118
0
            ProfileVersion::V4_3 => 0x04300000,
119
0
            ProfileVersion::V4_4 => 0x04400000,
120
0
            ProfileVersion::Unknown => 0x02000000,
121
        }
122
0
    }
123
}
124
125
#[repr(u32)]
126
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Hash)]
127
pub 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
156
impl DataColorSpace {
157
    #[inline]
158
0
    pub fn check_layout(self, layout: Layout) -> Result<(), CmsError> {
159
0
        let unsupported: bool = match self {
160
0
            DataColorSpace::Xyz => layout != Layout::Rgb,
161
0
            DataColorSpace::Lab => layout != Layout::Rgb && layout != Layout::Rgba,
162
0
            DataColorSpace::Luv => layout != Layout::Rgb,
163
0
            DataColorSpace::YCbr => layout != Layout::Rgb,
164
0
            DataColorSpace::Yxy => layout != Layout::Rgb,
165
0
            DataColorSpace::Rgb => layout != Layout::Rgb && layout != Layout::Rgba,
166
0
            DataColorSpace::Gray => layout != Layout::Gray && layout != Layout::GrayAlpha,
167
0
            DataColorSpace::Hsv => layout != Layout::Rgb,
168
0
            DataColorSpace::Hls => layout != Layout::Rgb,
169
0
            DataColorSpace::Cmyk => layout != Layout::Rgba,
170
0
            DataColorSpace::Cmy => layout != Layout::Rgb,
171
0
            DataColorSpace::Color2 => layout != Layout::GrayAlpha,
172
0
            DataColorSpace::Color3 => layout != Layout::Rgb,
173
0
            DataColorSpace::Color4 => layout != Layout::Rgba,
174
0
            DataColorSpace::Color5 => layout != Layout::Inks5,
175
0
            DataColorSpace::Color6 => layout != Layout::Inks6,
176
0
            DataColorSpace::Color7 => layout != Layout::Inks7,
177
0
            DataColorSpace::Color8 => layout != Layout::Inks8,
178
0
            DataColorSpace::Color9 => layout != Layout::Inks9,
179
0
            DataColorSpace::Color10 => layout != Layout::Inks10,
180
0
            DataColorSpace::Color11 => layout != Layout::Inks11,
181
0
            DataColorSpace::Color12 => layout != Layout::Inks12,
182
0
            DataColorSpace::Color13 => layout != Layout::Inks13,
183
0
            DataColorSpace::Color14 => layout != Layout::Inks14,
184
0
            DataColorSpace::Color15 => layout != Layout::Inks15,
185
        };
186
0
        if unsupported {
187
0
            Err(CmsError::InvalidLayout)
188
        } else {
189
0
            Ok(())
190
        }
191
0
    }
192
193
0
    pub(crate) fn is_three_channels(self) -> bool {
194
0
        matches!(
195
0
            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
0
    }
208
}
209
210
#[repr(u32)]
211
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)]
212
pub enum ProfileClass {
213
    InputDevice,
214
    #[default]
215
    DisplayDevice,
216
    OutputDevice,
217
    DeviceLink,
218
    ColorSpace,
219
    Abstract,
220
    Named,
221
}
222
223
impl TryFrom<u32> for ProfileClass {
224
    type Error = CmsError;
225
0
    fn try_from(value: u32) -> Result<Self, Self::Error> {
226
0
        if value == u32::from_ne_bytes(*b"scnr").to_be() {
227
0
            return Ok(ProfileClass::InputDevice);
228
0
        } else if value == u32::from_ne_bytes(*b"mntr").to_be() {
229
0
            return Ok(ProfileClass::DisplayDevice);
230
0
        } else if value == u32::from_ne_bytes(*b"prtr").to_be() {
231
0
            return Ok(ProfileClass::OutputDevice);
232
0
        } else if value == u32::from_ne_bytes(*b"link").to_be() {
233
0
            return Ok(ProfileClass::DeviceLink);
234
0
        } else if value == u32::from_ne_bytes(*b"spac").to_be() {
235
0
            return Ok(ProfileClass::ColorSpace);
236
0
        } else if value == u32::from_ne_bytes(*b"abst").to_be() {
237
0
            return Ok(ProfileClass::Abstract);
238
0
        } else if value == u32::from_ne_bytes(*b"nmcl").to_be() {
239
0
            return Ok(ProfileClass::Named);
240
0
        }
241
0
        Err(CmsError::InvalidProfile)
242
0
    }
243
}
244
245
impl From<ProfileClass> for u32 {
246
0
    fn from(val: ProfileClass) -> Self {
247
0
        match val {
248
0
            ProfileClass::InputDevice => u32::from_ne_bytes(*b"scnr").to_be(),
249
0
            ProfileClass::DisplayDevice => u32::from_ne_bytes(*b"mntr").to_be(),
250
0
            ProfileClass::OutputDevice => u32::from_ne_bytes(*b"prtr").to_be(),
251
0
            ProfileClass::DeviceLink => u32::from_ne_bytes(*b"link").to_be(),
252
0
            ProfileClass::ColorSpace => u32::from_ne_bytes(*b"spac").to_be(),
253
0
            ProfileClass::Abstract => u32::from_ne_bytes(*b"abst").to_be(),
254
0
            ProfileClass::Named => u32::from_ne_bytes(*b"nmcl").to_be(),
255
        }
256
0
    }
257
}
258
259
#[derive(Debug, Clone, PartialEq)]
260
pub enum LutStore {
261
    Store8(Vec<u8>),
262
    Store16(Vec<u16>),
263
}
264
265
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
266
pub enum LutType {
267
    Lut8,
268
    Lut16,
269
    LutMab,
270
    LutMba,
271
}
272
273
impl TryFrom<u32> for LutType {
274
    type Error = CmsError;
275
0
    fn try_from(value: u32) -> Result<Self, Self::Error> {
276
0
        if value == u32::from_ne_bytes(*b"mft1").to_be() {
277
0
            return Ok(LutType::Lut8);
278
0
        } else if value == u32::from_ne_bytes(*b"mft2").to_be() {
279
0
            return Ok(LutType::Lut16);
280
0
        } else if value == u32::from_ne_bytes(*b"mAB ").to_be() {
281
0
            return Ok(LutType::LutMab);
282
0
        } else if value == u32::from_ne_bytes(*b"mBA ").to_be() {
283
0
            return Ok(LutType::LutMba);
284
0
        }
285
0
        Err(CmsError::InvalidProfile)
286
0
    }
287
}
288
289
impl From<LutType> for u32 {
290
0
    fn from(val: LutType) -> Self {
291
0
        match val {
292
0
            LutType::Lut8 => u32::from_ne_bytes(*b"mft1").to_be(),
293
0
            LutType::Lut16 => u32::from_ne_bytes(*b"mft2").to_be(),
294
0
            LutType::LutMab => u32::from_ne_bytes(*b"mAB ").to_be(),
295
0
            LutType::LutMba => u32::from_ne_bytes(*b"mBA ").to_be(),
296
        }
297
0
    }
298
}
299
300
impl TryFrom<u32> for DataColorSpace {
301
    type Error = CmsError;
302
0
    fn try_from(value: u32) -> Result<Self, Self::Error> {
303
0
        if value == u32::from_ne_bytes(*b"XYZ ").to_be() {
304
0
            return Ok(DataColorSpace::Xyz);
305
0
        } else if value == u32::from_ne_bytes(*b"Lab ").to_be() {
306
0
            return Ok(DataColorSpace::Lab);
307
0
        } else if value == u32::from_ne_bytes(*b"Luv ").to_be() {
308
0
            return Ok(DataColorSpace::Luv);
309
0
        } else if value == u32::from_ne_bytes(*b"YCbr").to_be() {
310
0
            return Ok(DataColorSpace::YCbr);
311
0
        } else if value == u32::from_ne_bytes(*b"Yxy ").to_be() {
312
0
            return Ok(DataColorSpace::Yxy);
313
0
        } else if value == u32::from_ne_bytes(*b"RGB ").to_be() {
314
0
            return Ok(DataColorSpace::Rgb);
315
0
        } else if value == u32::from_ne_bytes(*b"GRAY").to_be() {
316
0
            return Ok(DataColorSpace::Gray);
317
0
        } else if value == u32::from_ne_bytes(*b"HSV ").to_be() {
318
0
            return Ok(DataColorSpace::Hsv);
319
0
        } else if value == u32::from_ne_bytes(*b"HLS ").to_be() {
320
0
            return Ok(DataColorSpace::Hls);
321
0
        } else if value == u32::from_ne_bytes(*b"CMYK").to_be() {
322
0
            return Ok(DataColorSpace::Cmyk);
323
0
        } else if value == u32::from_ne_bytes(*b"CMY ").to_be() {
324
0
            return Ok(DataColorSpace::Cmy);
325
0
        } else if value == u32::from_ne_bytes(*b"2CLR").to_be() {
326
0
            return Ok(DataColorSpace::Color2);
327
0
        } else if value == u32::from_ne_bytes(*b"3CLR").to_be() {
328
0
            return Ok(DataColorSpace::Color3);
329
0
        } else if value == u32::from_ne_bytes(*b"4CLR").to_be() {
330
0
            return Ok(DataColorSpace::Color4);
331
0
        } else if value == u32::from_ne_bytes(*b"5CLR").to_be() {
332
0
            return Ok(DataColorSpace::Color5);
333
0
        } else if value == u32::from_ne_bytes(*b"6CLR").to_be() {
334
0
            return Ok(DataColorSpace::Color6);
335
0
        } else if value == u32::from_ne_bytes(*b"7CLR").to_be() {
336
0
            return Ok(DataColorSpace::Color7);
337
0
        } else if value == u32::from_ne_bytes(*b"8CLR").to_be() {
338
0
            return Ok(DataColorSpace::Color8);
339
0
        } else if value == u32::from_ne_bytes(*b"9CLR").to_be() {
340
0
            return Ok(DataColorSpace::Color9);
341
0
        } else if value == u32::from_ne_bytes(*b"ACLR").to_be() {
342
0
            return Ok(DataColorSpace::Color10);
343
0
        } else if value == u32::from_ne_bytes(*b"BCLR").to_be() {
344
0
            return Ok(DataColorSpace::Color11);
345
0
        } else if value == u32::from_ne_bytes(*b"CCLR").to_be() {
346
0
            return Ok(DataColorSpace::Color12);
347
0
        } else if value == u32::from_ne_bytes(*b"DCLR").to_be() {
348
0
            return Ok(DataColorSpace::Color13);
349
0
        } else if value == u32::from_ne_bytes(*b"ECLR").to_be() {
350
0
            return Ok(DataColorSpace::Color14);
351
0
        } else if value == u32::from_ne_bytes(*b"FCLR").to_be() {
352
0
            return Ok(DataColorSpace::Color15);
353
0
        }
354
0
        Err(CmsError::InvalidProfile)
355
0
    }
356
}
357
358
impl From<DataColorSpace> for u32 {
359
0
    fn from(val: DataColorSpace) -> Self {
360
0
        match val {
361
0
            DataColorSpace::Xyz => u32::from_ne_bytes(*b"XYZ ").to_be(),
362
0
            DataColorSpace::Lab => u32::from_ne_bytes(*b"Lab ").to_be(),
363
0
            DataColorSpace::Luv => u32::from_ne_bytes(*b"Luv ").to_be(),
364
0
            DataColorSpace::YCbr => u32::from_ne_bytes(*b"YCbr").to_be(),
365
0
            DataColorSpace::Yxy => u32::from_ne_bytes(*b"Yxy ").to_be(),
366
0
            DataColorSpace::Rgb => u32::from_ne_bytes(*b"RGB ").to_be(),
367
0
            DataColorSpace::Gray => u32::from_ne_bytes(*b"GRAY").to_be(),
368
0
            DataColorSpace::Hsv => u32::from_ne_bytes(*b"HSV ").to_be(),
369
0
            DataColorSpace::Hls => u32::from_ne_bytes(*b"HLS ").to_be(),
370
0
            DataColorSpace::Cmyk => u32::from_ne_bytes(*b"CMYK").to_be(),
371
0
            DataColorSpace::Cmy => u32::from_ne_bytes(*b"CMY ").to_be(),
372
0
            DataColorSpace::Color2 => u32::from_ne_bytes(*b"2CLR").to_be(),
373
0
            DataColorSpace::Color3 => u32::from_ne_bytes(*b"3CLR").to_be(),
374
0
            DataColorSpace::Color4 => u32::from_ne_bytes(*b"4CLR").to_be(),
375
0
            DataColorSpace::Color5 => u32::from_ne_bytes(*b"5CLR").to_be(),
376
0
            DataColorSpace::Color6 => u32::from_ne_bytes(*b"6CLR").to_be(),
377
0
            DataColorSpace::Color7 => u32::from_ne_bytes(*b"7CLR").to_be(),
378
0
            DataColorSpace::Color8 => u32::from_ne_bytes(*b"8CLR").to_be(),
379
0
            DataColorSpace::Color9 => u32::from_ne_bytes(*b"9CLR").to_be(),
380
0
            DataColorSpace::Color10 => u32::from_ne_bytes(*b"ACLR").to_be(),
381
0
            DataColorSpace::Color11 => u32::from_ne_bytes(*b"BCLR").to_be(),
382
0
            DataColorSpace::Color12 => u32::from_ne_bytes(*b"CCLR").to_be(),
383
0
            DataColorSpace::Color13 => u32::from_ne_bytes(*b"DCLR").to_be(),
384
0
            DataColorSpace::Color14 => u32::from_ne_bytes(*b"ECLR").to_be(),
385
0
            DataColorSpace::Color15 => u32::from_ne_bytes(*b"FCLR").to_be(),
386
        }
387
0
    }
388
}
389
390
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
391
pub 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
423
impl From<u32> for TechnologySignatures {
424
0
    fn from(value: u32) -> Self {
425
0
        if value == u32::from_ne_bytes(*b"fscn").to_be() {
426
0
            return TechnologySignatures::FilmScanner;
427
0
        } else if value == u32::from_ne_bytes(*b"dcam").to_be() {
428
0
            return TechnologySignatures::DigitalCamera;
429
0
        } else if value == u32::from_ne_bytes(*b"rscn").to_be() {
430
0
            return TechnologySignatures::ReflectiveScanner;
431
0
        } else if value == u32::from_ne_bytes(*b"ijet").to_be() {
432
0
            return TechnologySignatures::InkJetPrinter;
433
0
        } else if value == u32::from_ne_bytes(*b"twax").to_be() {
434
0
            return TechnologySignatures::ThermalWaxPrinter;
435
0
        } else if value == u32::from_ne_bytes(*b"epho").to_be() {
436
0
            return TechnologySignatures::ElectrophotographicPrinter;
437
0
        } else if value == u32::from_ne_bytes(*b"esta").to_be() {
438
0
            return TechnologySignatures::ElectrostaticPrinter;
439
0
        } else if value == u32::from_ne_bytes(*b"dsub").to_be() {
440
0
            return TechnologySignatures::DyeSublimationPrinter;
441
0
        } else if value == u32::from_ne_bytes(*b"rpho").to_be() {
442
0
            return TechnologySignatures::PhotographicPaperPrinter;
443
0
        } else if value == u32::from_ne_bytes(*b"fprn").to_be() {
444
0
            return TechnologySignatures::FilmWriter;
445
0
        } else if value == u32::from_ne_bytes(*b"vidm").to_be() {
446
0
            return TechnologySignatures::VideoMonitor;
447
0
        } else if value == u32::from_ne_bytes(*b"vidc").to_be() {
448
0
            return TechnologySignatures::VideoCamera;
449
0
        } else if value == u32::from_ne_bytes(*b"pjtv").to_be() {
450
0
            return TechnologySignatures::ProjectionTelevision;
451
0
        } else if value == u32::from_ne_bytes(*b"CRT ").to_be() {
452
0
            return TechnologySignatures::CathodeRayTubeDisplay;
453
0
        } else if value == u32::from_ne_bytes(*b"PMD ").to_be() {
454
0
            return TechnologySignatures::PassiveMatrixDisplay;
455
0
        } else if value == u32::from_ne_bytes(*b"AMD ").to_be() {
456
0
            return TechnologySignatures::ActiveMatrixDisplay;
457
0
        } else if value == u32::from_ne_bytes(*b"LCD ").to_be() {
458
0
            return TechnologySignatures::LiquidCrystalDisplay;
459
0
        } else if value == u32::from_ne_bytes(*b"OLED").to_be() {
460
0
            return TechnologySignatures::OrganicLedDisplay;
461
0
        } else if value == u32::from_ne_bytes(*b"KPCD").to_be() {
462
0
            return TechnologySignatures::PhotoCd;
463
0
        } else if value == u32::from_ne_bytes(*b"imgs").to_be() {
464
0
            return TechnologySignatures::PhotographicImageSetter;
465
0
        } else if value == u32::from_ne_bytes(*b"grav").to_be() {
466
0
            return TechnologySignatures::Gravure;
467
0
        } else if value == u32::from_ne_bytes(*b"offs").to_be() {
468
0
            return TechnologySignatures::OffsetLithography;
469
0
        } else if value == u32::from_ne_bytes(*b"silk").to_be() {
470
0
            return TechnologySignatures::Silkscreen;
471
0
        } else if value == u32::from_ne_bytes(*b"flex").to_be() {
472
0
            return TechnologySignatures::Flexography;
473
0
        } else if value == u32::from_ne_bytes(*b"mpfs").to_be() {
474
0
            return TechnologySignatures::MotionPictureFilmScanner;
475
0
        } else if value == u32::from_ne_bytes(*b"mpfr").to_be() {
476
0
            return TechnologySignatures::MotionPictureFilmRecorder;
477
0
        } else if value == u32::from_ne_bytes(*b"dmpc").to_be() {
478
0
            return TechnologySignatures::DigitalMotionPictureCamera;
479
0
        } else if value == u32::from_ne_bytes(*b"dcpj").to_be() {
480
0
            return TechnologySignatures::DigitalCinemaProjector;
481
0
        }
482
0
        TechnologySignatures::Unknown(value)
483
0
    }
484
}
485
486
#[derive(Debug, Clone)]
487
pub enum LutWarehouse {
488
    Lut(LutDataType),
489
    Multidimensional(LutMultidimensionalType),
490
}
491
492
impl PartialEq for LutWarehouse {
493
0
    fn eq(&self, other: &Self) -> bool {
494
0
        match (self, other) {
495
0
            (LutWarehouse::Lut(a), LutWarehouse::Lut(b)) => a == b,
496
0
            (LutWarehouse::Multidimensional(a), LutWarehouse::Multidimensional(b)) => a == b,
497
0
            _ => false, // Different variants are not equal
498
        }
499
0
    }
500
}
501
502
#[derive(Debug, Clone, PartialEq)]
503
pub struct LutDataType {
504
    // used by lut8Type/lut16Type (mft2) only
505
    pub num_input_channels: u8,
506
    pub num_output_channels: u8,
507
    pub num_clut_grid_points: u8,
508
    pub matrix: Matrix3d,
509
    pub num_input_table_entries: u16,
510
    pub num_output_table_entries: u16,
511
    pub input_table: LutStore,
512
    pub clut_table: LutStore,
513
    pub output_table: LutStore,
514
    pub lut_type: LutType,
515
}
516
517
impl LutDataType {
518
0
    pub(crate) fn has_same_kind(&self) -> bool {
519
0
        matches!(
520
0
            (&self.input_table, &self.clut_table, &self.output_table),
521
            (
522
                LutStore::Store8(_),
523
                LutStore::Store8(_),
524
                LutStore::Store8(_)
525
            ) | (
526
                LutStore::Store16(_),
527
                LutStore::Store16(_),
528
                LutStore::Store16(_)
529
            )
530
        )
531
0
    }
532
}
533
534
#[derive(Debug, Clone, PartialEq)]
535
pub struct LutMultidimensionalType {
536
    pub num_input_channels: u8,
537
    pub num_output_channels: u8,
538
    pub grid_points: [u8; 16],
539
    pub clut: Option<LutStore>,
540
    pub a_curves: Vec<ToneReprCurve>,
541
    pub b_curves: Vec<ToneReprCurve>,
542
    pub m_curves: Vec<ToneReprCurve>,
543
    pub matrix: Matrix3d,
544
    pub bias: Vector3d,
545
}
546
547
#[repr(u32)]
548
#[derive(Clone, Copy, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash)]
549
pub enum RenderingIntent {
550
    AbsoluteColorimetric = 3,
551
    Saturation = 2,
552
    RelativeColorimetric = 1,
553
    #[default]
554
    Perceptual = 0,
555
}
556
557
impl TryFrom<u32> for RenderingIntent {
558
    type Error = CmsError;
559
560
    #[inline]
561
0
    fn try_from(value: u32) -> Result<Self, Self::Error> {
562
0
        match value {
563
0
            0 => Ok(RenderingIntent::Perceptual),
564
0
            1 => Ok(RenderingIntent::RelativeColorimetric),
565
0
            2 => Ok(RenderingIntent::Saturation),
566
0
            3 => Ok(RenderingIntent::AbsoluteColorimetric),
567
0
            _ => Err(CmsError::InvalidRenderingIntent),
568
        }
569
0
    }
570
}
571
572
impl From<RenderingIntent> for u32 {
573
    #[inline]
574
0
    fn from(value: RenderingIntent) -> Self {
575
0
        match value {
576
0
            RenderingIntent::AbsoluteColorimetric => 3,
577
0
            RenderingIntent::Saturation => 2,
578
0
            RenderingIntent::RelativeColorimetric => 1,
579
0
            RenderingIntent::Perceptual => 0,
580
        }
581
0
    }
582
}
583
584
/// ICC Header
585
#[repr(C)]
586
#[derive(Debug, Clone, Copy)]
587
pub(crate) struct ProfileHeader {
588
    pub size: u32,                         // Size of the profile (computed)
589
    pub cmm_type: u32,                     // Preferred CMM type (ignored)
590
    pub version: ProfileVersion,           // Version (4.3 or 4.4 if CICP is included)
591
    pub profile_class: ProfileClass,       // Display device profile
592
    pub data_color_space: DataColorSpace,  // RGB input color space
593
    pub pcs: DataColorSpace,               // Profile connection space
594
    pub creation_date_time: ColorDateTime, // Date and time
595
    pub signature: ProfileSignature,       // Profile signature
596
    pub platform: u32,                     // Platform target (ignored)
597
    pub flags: u32,                        // Flags (not embedded, can be used independently)
598
    pub device_manufacturer: u32,          // Device manufacturer (ignored)
599
    pub device_model: u32,                 // Device model (ignored)
600
    pub device_attributes: [u8; 8],        // Device attributes (ignored)
601
    pub rendering_intent: RenderingIntent, // Relative colorimetric rendering intent
602
    pub illuminant: Xyz,                   // D50 standard illuminant X
603
    pub creator: u32,                      // Profile creator (ignored)
604
    pub profile_id: [u8; 16],              // Profile id checksum (ignored)
605
    pub reserved: [u8; 28],                // Reserved (ignored)
606
    pub tag_count: u32,                    // Technically not part of header, but required
607
}
608
609
impl ProfileHeader {
610
    #[allow(dead_code)]
611
0
    pub(crate) fn new(size: u32) -> Self {
612
0
        Self {
613
0
            size,
614
0
            cmm_type: 0,
615
0
            version: ProfileVersion::V4_3,
616
0
            profile_class: ProfileClass::DisplayDevice,
617
0
            data_color_space: DataColorSpace::Rgb,
618
0
            pcs: DataColorSpace::Xyz,
619
0
            creation_date_time: ColorDateTime::default(),
620
0
            signature: ProfileSignature::Acsp,
621
0
            platform: 0,
622
0
            flags: 0x00000000,
623
0
            device_manufacturer: 0,
624
0
            device_model: 0,
625
0
            device_attributes: [0; 8],
626
0
            rendering_intent: RenderingIntent::Perceptual,
627
0
            illuminant: Chromaticity::D50.to_xyz(),
628
0
            creator: 0,
629
0
            profile_id: [0; 16],
630
0
            reserved: [0; 28],
631
0
            tag_count: 0,
632
0
        }
633
0
    }
634
635
    /// Creates profile from the buffer
636
0
    pub(crate) fn new_from_slice(slice: &[u8]) -> Result<Self, CmsError> {
637
0
        if slice.len() < size_of::<ProfileHeader>() {
638
0
            return Err(CmsError::InvalidProfile);
639
0
        }
640
0
        let mut cursor = std::io::Cursor::new(slice);
641
0
        let mut buffer = [0u8; size_of::<ProfileHeader>()];
642
0
        cursor
643
0
            .read_exact(&mut buffer)
644
0
            .map_err(|_| CmsError::InvalidProfile)?;
645
646
0
        let header = Self {
647
0
            size: u32::from_be_bytes(buffer[0..4].try_into().unwrap()),
648
0
            cmm_type: u32::from_be_bytes(buffer[4..8].try_into().unwrap()),
649
0
            version: ProfileVersion::try_from(u32::from_be_bytes(
650
0
                buffer[8..12].try_into().unwrap(),
651
0
            ))?,
652
0
            profile_class: ProfileClass::try_from(u32::from_be_bytes(
653
0
                buffer[12..16].try_into().unwrap(),
654
0
            ))?,
655
0
            data_color_space: DataColorSpace::try_from(u32::from_be_bytes(
656
0
                buffer[16..20].try_into().unwrap(),
657
0
            ))?,
658
0
            pcs: DataColorSpace::try_from(u32::from_be_bytes(buffer[20..24].try_into().unwrap()))?,
659
0
            creation_date_time: ColorDateTime::new_from_slice(buffer[24..36].try_into().unwrap())?,
660
0
            signature: ProfileSignature::try_from(u32::from_be_bytes(
661
0
                buffer[36..40].try_into().unwrap(),
662
0
            ))?,
663
0
            platform: u32::from_be_bytes(buffer[40..44].try_into().unwrap()),
664
0
            flags: u32::from_be_bytes(buffer[44..48].try_into().unwrap()),
665
0
            device_manufacturer: u32::from_be_bytes(buffer[48..52].try_into().unwrap()),
666
0
            device_model: u32::from_be_bytes(buffer[52..56].try_into().unwrap()),
667
0
            device_attributes: buffer[56..64].try_into().unwrap(),
668
0
            rendering_intent: RenderingIntent::try_from(u32::from_be_bytes(
669
0
                buffer[64..68].try_into().unwrap(),
670
0
            ))?,
671
0
            illuminant: Xyz::new(
672
0
                s15_fixed16_number_to_float(i32::from_be_bytes(buffer[68..72].try_into().unwrap())),
673
0
                s15_fixed16_number_to_float(i32::from_be_bytes(buffer[72..76].try_into().unwrap())),
674
0
                s15_fixed16_number_to_float(i32::from_be_bytes(buffer[76..80].try_into().unwrap())),
675
            ),
676
0
            creator: u32::from_be_bytes(buffer[80..84].try_into().unwrap()),
677
0
            profile_id: buffer[84..100].try_into().unwrap(),
678
0
            reserved: buffer[100..128].try_into().unwrap(),
679
0
            tag_count: u32::from_be_bytes(buffer[128..132].try_into().unwrap()),
680
        };
681
0
        Ok(header)
682
0
    }
683
}
684
685
/// A [Coding Independent Code Point](https://en.wikipedia.org/wiki/Coding-independent_code_points).
686
#[repr(C)]
687
#[derive(Debug, Clone, Copy)]
688
pub struct CicpProfile {
689
    pub color_primaries: CicpColorPrimaries,
690
    pub transfer_characteristics: TransferCharacteristics,
691
    pub matrix_coefficients: MatrixCoefficients,
692
    pub full_range: bool,
693
}
694
695
#[derive(Debug, Clone)]
696
pub struct LocalizableString {
697
    /// An ISO 639-1 value is expected; any text w. more than two symbols will be truncated
698
    pub language: String,
699
    /// An ISO 3166-1 value is expected; any text w. more than two symbols will be truncated
700
    pub country: String,
701
    pub value: String,
702
}
703
704
impl LocalizableString {
705
    /// Creates new localizable string
706
    ///
707
    /// # Arguments
708
    ///
709
    /// * `language`: an ISO 639-1 value is expected, any text more than 2 symbols will be truncated
710
    /// * `country`: an ISO 3166-1 value is expected, any text more than 2 symbols will be truncated
711
    /// * `value`: String value
712
    ///
713
0
    pub fn new(language: String, country: String, value: String) -> Self {
714
0
        Self {
715
0
            language,
716
0
            country,
717
0
            value,
718
0
        }
719
0
    }
720
}
721
722
#[derive(Debug, Clone)]
723
pub struct DescriptionString {
724
    pub ascii_string: String,
725
    pub unicode_language_code: u32,
726
    pub unicode_string: String,
727
    pub script_code_code: i8,
728
    pub mac_string: String,
729
}
730
731
#[derive(Debug, Clone)]
732
pub enum ProfileText {
733
    PlainString(String),
734
    Localizable(Vec<LocalizableString>),
735
    Description(DescriptionString),
736
}
737
738
impl ProfileText {
739
0
    pub(crate) fn has_values(&self) -> bool {
740
0
        match self {
741
0
            ProfileText::PlainString(_) => true,
742
0
            ProfileText::Localizable(lc) => !lc.is_empty(),
743
0
            ProfileText::Description(_) => true,
744
        }
745
0
    }
746
}
747
748
#[derive(Debug, Clone, Copy)]
749
pub enum StandardObserver {
750
    D50,
751
    D65,
752
    Unknown,
753
}
754
755
impl From<u32> for StandardObserver {
756
0
    fn from(value: u32) -> Self {
757
0
        if value == 1 {
758
0
            return StandardObserver::D50;
759
0
        } else if value == 2 {
760
0
            return StandardObserver::D65;
761
0
        }
762
0
        StandardObserver::Unknown
763
0
    }
764
}
765
766
impl From<StandardObserver> for u32 {
767
0
    fn from(value: StandardObserver) -> Self {
768
0
        match value {
769
0
            StandardObserver::D50 => 1,
770
0
            StandardObserver::D65 => 2,
771
0
            StandardObserver::Unknown => 0,
772
        }
773
0
    }
774
}
775
776
#[derive(Debug, Clone, Copy)]
777
pub struct ViewingConditions {
778
    pub illuminant: Xyz,
779
    pub surround: Xyz,
780
    pub observer: StandardObserver,
781
}
782
783
#[derive(Debug, Clone, Copy)]
784
pub enum MeasurementGeometry {
785
    Unknown,
786
    /// 0°:45° or 45°:0°
787
    D45to45,
788
    /// 0°:d or d:0°
789
    D0to0,
790
}
791
792
impl From<u32> for MeasurementGeometry {
793
0
    fn from(value: u32) -> Self {
794
0
        if value == 1 {
795
0
            Self::D45to45
796
0
        } else if value == 2 {
797
0
            Self::D0to0
798
        } else {
799
0
            Self::Unknown
800
        }
801
0
    }
802
}
803
804
#[derive(Debug, Clone, Copy)]
805
pub enum StandardIlluminant {
806
    Unknown,
807
    D50,
808
    D65,
809
    D93,
810
    F2,
811
    D55,
812
    A,
813
    EquiPower,
814
    F8,
815
}
816
817
impl From<u32> for StandardIlluminant {
818
0
    fn from(value: u32) -> Self {
819
0
        match value {
820
0
            1 => StandardIlluminant::D50,
821
0
            2 => StandardIlluminant::D65,
822
0
            3 => StandardIlluminant::D93,
823
0
            4 => StandardIlluminant::F2,
824
0
            5 => StandardIlluminant::D55,
825
0
            6 => StandardIlluminant::A,
826
0
            7 => StandardIlluminant::EquiPower,
827
0
            8 => StandardIlluminant::F8,
828
0
            _ => Self::Unknown,
829
        }
830
0
    }
831
}
832
833
impl From<StandardIlluminant> for u32 {
834
0
    fn from(value: StandardIlluminant) -> Self {
835
0
        match value {
836
0
            StandardIlluminant::Unknown => 0u32,
837
0
            StandardIlluminant::D50 => 1u32,
838
0
            StandardIlluminant::D65 => 2u32,
839
0
            StandardIlluminant::D93 => 3,
840
0
            StandardIlluminant::F2 => 4,
841
0
            StandardIlluminant::D55 => 5,
842
0
            StandardIlluminant::A => 6,
843
0
            StandardIlluminant::EquiPower => 7,
844
0
            StandardIlluminant::F8 => 8,
845
        }
846
0
    }
847
}
848
849
#[derive(Debug, Clone, Copy)]
850
pub struct Measurement {
851
    pub observer: StandardObserver,
852
    pub backing: Xyz,
853
    pub geometry: MeasurementGeometry,
854
    pub flare: f32,
855
    pub illuminant: StandardIlluminant,
856
}
857
858
/// ICC Profile representation
859
#[repr(C)]
860
#[derive(Debug, Clone, Default)]
861
pub struct ColorProfile {
862
    pub pcs: DataColorSpace,
863
    pub color_space: DataColorSpace,
864
    pub profile_class: ProfileClass,
865
    pub rendering_intent: RenderingIntent,
866
    pub red_colorant: Xyzd,
867
    pub green_colorant: Xyzd,
868
    pub blue_colorant: Xyzd,
869
    pub white_point: Xyzd,
870
    pub black_point: Option<Xyzd>,
871
    pub media_white_point: Option<Xyzd>,
872
    pub luminance: Option<Xyzd>,
873
    pub measurement: Option<Measurement>,
874
    pub red_trc: Option<ToneReprCurve>,
875
    pub green_trc: Option<ToneReprCurve>,
876
    pub blue_trc: Option<ToneReprCurve>,
877
    pub gray_trc: Option<ToneReprCurve>,
878
    pub cicp: Option<CicpProfile>,
879
    pub chromatic_adaptation: Option<Matrix3d>,
880
    pub lut_a_to_b_perceptual: Option<LutWarehouse>,
881
    pub lut_a_to_b_colorimetric: Option<LutWarehouse>,
882
    pub lut_a_to_b_saturation: Option<LutWarehouse>,
883
    pub lut_b_to_a_perceptual: Option<LutWarehouse>,
884
    pub lut_b_to_a_colorimetric: Option<LutWarehouse>,
885
    pub lut_b_to_a_saturation: Option<LutWarehouse>,
886
    pub gamut: Option<LutWarehouse>,
887
    pub copyright: Option<ProfileText>,
888
    pub description: Option<ProfileText>,
889
    pub device_manufacturer: Option<ProfileText>,
890
    pub device_model: Option<ProfileText>,
891
    pub char_target: Option<ProfileText>,
892
    pub viewing_conditions: Option<ViewingConditions>,
893
    pub viewing_conditions_description: Option<ProfileText>,
894
    pub technology: Option<TechnologySignatures>,
895
    pub calibration_date: Option<ColorDateTime>,
896
    /// Version for internal and viewing purposes only.
897
    /// On encoding added value to profile will always be V4.
898
    pub(crate) version_internal: ProfileVersion,
899
}
900
901
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Hash)]
902
pub struct ParsingOptions {
903
    // Maximum allowed profile size in bytes
904
    pub max_profile_size: usize,
905
    // Maximum allowed CLUT size in bytes
906
    pub max_allowed_clut_size: usize,
907
    // Maximum allowed TRC size in elements count
908
    pub max_allowed_trc_size: usize,
909
}
910
911
impl Default for ParsingOptions {
912
0
    fn default() -> Self {
913
0
        Self {
914
0
            max_profile_size: MAX_PROFILE_SIZE,
915
0
            max_allowed_clut_size: 10_000_000,
916
0
            max_allowed_trc_size: 40_000,
917
0
        }
918
0
    }
919
}
920
921
impl ColorProfile {
922
    /// Returns profile version
923
0
    pub fn version(&self) -> ProfileVersion {
924
0
        self.version_internal
925
0
    }
926
927
0
    pub fn new_from_slice(slice: &[u8]) -> Result<Self, CmsError> {
928
0
        Self::new_from_slice_with_options(slice, Default::default())
929
0
    }
930
931
0
    pub fn new_from_slice_with_options(
932
0
        slice: &[u8],
933
0
        options: ParsingOptions,
934
0
    ) -> Result<Self, CmsError> {
935
0
        let header = ProfileHeader::new_from_slice(slice)?;
936
0
        let tags_count = header.tag_count as usize;
937
0
        if slice.len() >= options.max_profile_size {
938
0
            return Err(CmsError::InvalidProfile);
939
0
        }
940
0
        let tags_end = tags_count
941
0
            .safe_mul(TAG_SIZE)?
942
0
            .safe_add(size_of::<ProfileHeader>())?;
943
0
        if slice.len() < tags_end {
944
0
            return Err(CmsError::InvalidProfile);
945
0
        }
946
0
        let tags_slice = &slice[size_of::<ProfileHeader>()..tags_end];
947
0
        let mut profile = ColorProfile {
948
0
            rendering_intent: header.rendering_intent,
949
0
            pcs: header.pcs,
950
0
            profile_class: header.profile_class,
951
0
            color_space: header.data_color_space,
952
0
            white_point: header.illuminant.to_xyzd(),
953
0
            version_internal: header.version,
954
0
            ..Default::default()
955
0
        };
956
0
        let color_space = profile.color_space;
957
0
        for tag in tags_slice.chunks_exact(TAG_SIZE) {
958
0
            let tag_value = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
959
0
            let tag_entry = u32::from_be_bytes([tag[4], tag[5], tag[6], tag[7]]);
960
0
            let tag_size = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
961
            // Just ignore unknown tags
962
0
            if let Ok(tag) = Tag::try_from(tag_value) {
963
0
                match tag {
964
                    Tag::RedXyz => {
965
0
                        if color_space == DataColorSpace::Rgb {
966
                            profile.red_colorant =
967
0
                                Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
968
0
                        }
969
                    }
970
                    Tag::GreenXyz => {
971
0
                        if color_space == DataColorSpace::Rgb {
972
                            profile.green_colorant =
973
0
                                Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
974
0
                        }
975
                    }
976
                    Tag::BlueXyz => {
977
0
                        if color_space == DataColorSpace::Rgb {
978
                            profile.blue_colorant =
979
0
                                Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
980
0
                        }
981
                    }
982
                    Tag::RedToneReproduction => {
983
0
                        if color_space == DataColorSpace::Rgb {
984
0
                            profile.red_trc = Self::read_trc_tag_s(
985
0
                                slice,
986
0
                                tag_entry as usize,
987
0
                                tag_size,
988
0
                                &options,
989
0
                            )?;
990
0
                        }
991
                    }
992
                    Tag::GreenToneReproduction => {
993
0
                        if color_space == DataColorSpace::Rgb {
994
0
                            profile.green_trc = Self::read_trc_tag_s(
995
0
                                slice,
996
0
                                tag_entry as usize,
997
0
                                tag_size,
998
0
                                &options,
999
0
                            )?;
1000
0
                        }
1001
                    }
1002
                    Tag::BlueToneReproduction => {
1003
0
                        if color_space == DataColorSpace::Rgb {
1004
0
                            profile.blue_trc = Self::read_trc_tag_s(
1005
0
                                slice,
1006
0
                                tag_entry as usize,
1007
0
                                tag_size,
1008
0
                                &options,
1009
0
                            )?;
1010
0
                        }
1011
                    }
1012
                    Tag::GreyToneReproduction => {
1013
0
                        if color_space == DataColorSpace::Gray {
1014
0
                            profile.gray_trc = Self::read_trc_tag_s(
1015
0
                                slice,
1016
0
                                tag_entry as usize,
1017
0
                                tag_size,
1018
0
                                &options,
1019
0
                            )?;
1020
0
                        }
1021
                    }
1022
                    Tag::MediaWhitePoint => {
1023
                        profile.media_white_point =
1024
0
                            Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?;
1025
                    }
1026
                    Tag::Luminance => {
1027
                        profile.luminance =
1028
0
                            Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?;
1029
                    }
1030
                    Tag::Measurement => {
1031
                        profile.measurement =
1032
0
                            Self::read_meas_tag(slice, tag_entry as usize, tag_size)?;
1033
                    }
1034
                    Tag::CodeIndependentPoints => {
1035
                        // This tag may be present when the data colour space in the profile header is RGB, YCbCr, or XYZ, and the
1036
                        // profile class in the profile header is Input or Display. The tag shall not be present for other data colour spaces
1037
                        // or profile classes indicated in the profile header.
1038
0
                        if (profile.profile_class == ProfileClass::InputDevice
1039
0
                            || profile.profile_class == ProfileClass::DisplayDevice)
1040
0
                            && (profile.color_space == DataColorSpace::Rgb
1041
0
                                || profile.color_space == DataColorSpace::YCbr
1042
0
                                || profile.color_space == DataColorSpace::Xyz)
1043
                        {
1044
                            profile.cicp =
1045
0
                                Self::read_cicp_tag(slice, tag_entry as usize, tag_size)?;
1046
0
                        }
1047
                    }
1048
                    Tag::ChromaticAdaptation => {
1049
                        profile.chromatic_adaptation =
1050
0
                            Self::read_chad_tag(slice, tag_entry as usize, tag_size)?;
1051
                    }
1052
                    Tag::BlackPoint => {
1053
                        profile.black_point =
1054
0
                            Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?
1055
                    }
1056
                    Tag::DeviceToPcsLutPerceptual => {
1057
0
                        profile.lut_a_to_b_perceptual =
1058
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1059
                    }
1060
                    Tag::DeviceToPcsLutColorimetric => {
1061
0
                        profile.lut_a_to_b_colorimetric =
1062
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1063
                    }
1064
                    Tag::DeviceToPcsLutSaturation => {
1065
0
                        profile.lut_a_to_b_saturation =
1066
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1067
                    }
1068
                    Tag::PcsToDeviceLutPerceptual => {
1069
0
                        profile.lut_b_to_a_perceptual =
1070
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1071
                    }
1072
                    Tag::PcsToDeviceLutColorimetric => {
1073
0
                        profile.lut_b_to_a_colorimetric =
1074
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1075
                    }
1076
                    Tag::PcsToDeviceLutSaturation => {
1077
0
                        profile.lut_b_to_a_saturation =
1078
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1079
                    }
1080
                    Tag::Gamut => {
1081
0
                        profile.gamut = Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1082
                    }
1083
                    Tag::Copyright => {
1084
0
                        profile.copyright =
1085
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1086
                    }
1087
                    Tag::ProfileDescription => {
1088
0
                        profile.description =
1089
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1090
                    }
1091
                    Tag::ViewingConditionsDescription => {
1092
0
                        profile.viewing_conditions_description =
1093
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1094
                    }
1095
                    Tag::DeviceModel => {
1096
0
                        profile.device_model =
1097
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1098
                    }
1099
                    Tag::DeviceManufacturer => {
1100
0
                        profile.device_manufacturer =
1101
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1102
                    }
1103
                    Tag::CharTarget => {
1104
0
                        profile.char_target =
1105
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1106
                    }
1107
0
                    Tag::Chromaticity => {}
1108
                    Tag::ObserverConditions => {
1109
                        profile.viewing_conditions =
1110
0
                            Self::read_viewing_conditions(slice, tag_entry as usize, tag_size)?;
1111
                    }
1112
                    Tag::Technology => {
1113
                        profile.technology =
1114
0
                            Self::read_tech_tag(slice, tag_entry as usize, tag_size)?;
1115
                    }
1116
                    Tag::CalibrationDateTime => {
1117
                        profile.calibration_date =
1118
0
                            Self::read_date_time_tag(slice, tag_entry as usize, tag_size)?;
1119
                    }
1120
                }
1121
0
            }
1122
        }
1123
1124
0
        Ok(profile)
1125
0
    }
1126
}
1127
1128
impl ColorProfile {
1129
    #[inline]
1130
0
    pub fn colorant_matrix(&self) -> Matrix3d {
1131
0
        Matrix3d {
1132
0
            v: [
1133
0
                [
1134
0
                    self.red_colorant.x,
1135
0
                    self.green_colorant.x,
1136
0
                    self.blue_colorant.x,
1137
0
                ],
1138
0
                [
1139
0
                    self.red_colorant.y,
1140
0
                    self.green_colorant.y,
1141
0
                    self.blue_colorant.y,
1142
0
                ],
1143
0
                [
1144
0
                    self.red_colorant.z,
1145
0
                    self.green_colorant.z,
1146
0
                    self.blue_colorant.z,
1147
0
                ],
1148
0
            ],
1149
0
        }
1150
0
    }
1151
1152
    /// Computes colorants matrix. Returns not transposed matrix.
1153
    ///
1154
    /// To work on `const` context this method does have restrictions.
1155
    /// If invalid values were provided it may return invalid matrix or NaNs.
1156
0
    pub const fn colorants_matrix(white_point: XyY, primaries: ColorPrimaries) -> Matrix3d {
1157
0
        let red_xyz = primaries.red.to_xyzd();
1158
0
        let green_xyz = primaries.green.to_xyzd();
1159
0
        let blue_xyz = primaries.blue.to_xyzd();
1160
1161
0
        let xyz_matrix = Matrix3d {
1162
0
            v: [
1163
0
                [red_xyz.x, green_xyz.x, blue_xyz.x],
1164
0
                [red_xyz.y, green_xyz.y, blue_xyz.y],
1165
0
                [red_xyz.z, green_xyz.z, blue_xyz.z],
1166
0
            ],
1167
0
        };
1168
0
        let colorants = ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyzd());
1169
0
        adapt_to_d50_d(colorants, white_point)
1170
0
    }
1171
1172
    /// Updates RGB triple colorimetry from 3 [Chromaticity] and white point
1173
0
    pub const fn update_rgb_colorimetry(&mut self, white_point: XyY, primaries: ColorPrimaries) {
1174
0
        let red_xyz = primaries.red.to_xyzd();
1175
0
        let green_xyz = primaries.green.to_xyzd();
1176
0
        let blue_xyz = primaries.blue.to_xyzd();
1177
1178
0
        self.chromatic_adaptation = Some(BRADFORD_D);
1179
0
        self.update_rgb_colorimetry_triplet(white_point, red_xyz, green_xyz, blue_xyz)
1180
0
    }
1181
1182
    /// Updates RGB triple colorimetry from 3 [Xyzd] and white point
1183
    ///
1184
    /// To work on `const` context this method does have restrictions.
1185
    /// If invalid values were provided it may return invalid matrix or NaNs.
1186
0
    pub const fn update_rgb_colorimetry_triplet(
1187
0
        &mut self,
1188
0
        white_point: XyY,
1189
0
        red_xyz: Xyzd,
1190
0
        green_xyz: Xyzd,
1191
0
        blue_xyz: Xyzd,
1192
0
    ) {
1193
0
        let xyz_matrix = Matrix3d {
1194
0
            v: [
1195
0
                [red_xyz.x, green_xyz.x, blue_xyz.x],
1196
0
                [red_xyz.y, green_xyz.y, blue_xyz.y],
1197
0
                [red_xyz.z, green_xyz.z, blue_xyz.z],
1198
0
            ],
1199
0
        };
1200
0
        let colorants = ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyzd());
1201
0
        let colorants = adapt_to_d50_d(colorants, white_point);
1202
1203
0
        self.update_colorants(colorants);
1204
0
    }
1205
1206
0
    pub(crate) const fn update_colorants(&mut self, colorants: Matrix3d) {
1207
        // note: there's a transpose type of operation going on here
1208
0
        self.red_colorant.x = colorants.v[0][0];
1209
0
        self.red_colorant.y = colorants.v[1][0];
1210
0
        self.red_colorant.z = colorants.v[2][0];
1211
0
        self.green_colorant.x = colorants.v[0][1];
1212
0
        self.green_colorant.y = colorants.v[1][1];
1213
0
        self.green_colorant.z = colorants.v[2][1];
1214
0
        self.blue_colorant.x = colorants.v[0][2];
1215
0
        self.blue_colorant.y = colorants.v[1][2];
1216
0
        self.blue_colorant.z = colorants.v[2][2];
1217
0
    }
1218
1219
    /// Updates RGB triple colorimetry from CICP
1220
0
    pub fn update_rgb_colorimetry_from_cicp(&mut self, cicp: CicpProfile) -> bool {
1221
0
        self.cicp = Some(cicp);
1222
0
        if !cicp.color_primaries.has_chromaticity()
1223
0
            || !cicp.transfer_characteristics.has_transfer_curve()
1224
        {
1225
0
            return false;
1226
0
        }
1227
0
        let primaries_xy: ColorPrimaries = match cicp.color_primaries.try_into() {
1228
0
            Ok(primaries) => primaries,
1229
0
            Err(_) => return false,
1230
        };
1231
0
        let white_point: Chromaticity = match cicp.color_primaries.white_point() {
1232
0
            Ok(v) => v,
1233
0
            Err(_) => return false,
1234
        };
1235
0
        self.update_rgb_colorimetry(white_point.to_xyyb(), primaries_xy);
1236
1237
0
        let red_trc: ToneReprCurve = match cicp.transfer_characteristics.try_into() {
1238
0
            Ok(trc) => trc,
1239
0
            Err(_) => return false,
1240
        };
1241
0
        self.green_trc = Some(red_trc.clone());
1242
0
        self.blue_trc = Some(red_trc.clone());
1243
0
        self.red_trc = Some(red_trc);
1244
0
        false
1245
0
    }
1246
1247
0
    pub const fn rgb_to_xyz(&self, xyz_matrix: Matrix3f, wp: Xyz) -> Matrix3f {
1248
0
        let xyz_inverse = xyz_matrix.inverse();
1249
0
        let s = xyz_inverse.mul_vector(wp.to_vector());
1250
0
        let mut v = xyz_matrix.mul_row_vector::<0>(s);
1251
0
        v = v.mul_row_vector::<1>(s);
1252
0
        v.mul_row_vector::<2>(s)
1253
0
    }
1254
1255
    ///TODO: make primary instead of [rgb_to_xyz] in the next major version
1256
0
    pub(crate) const fn rgb_to_xyz_static(xyz_matrix: Matrix3f, wp: Xyz) -> Matrix3f {
1257
0
        let xyz_inverse = xyz_matrix.inverse();
1258
0
        let s = xyz_inverse.mul_vector(wp.to_vector());
1259
0
        let mut v = xyz_matrix.mul_row_vector::<0>(s);
1260
0
        v = v.mul_row_vector::<1>(s);
1261
0
        v.mul_row_vector::<2>(s)
1262
0
    }
1263
1264
    /// If Primaries is invalid will return invalid matrix on const context.
1265
    /// This assumes not transposed matrix and returns not transposed matrix.
1266
0
    pub const fn rgb_to_xyz_d(xyz_matrix: Matrix3d, wp: Xyzd) -> Matrix3d {
1267
0
        let xyz_inverse = xyz_matrix.inverse();
1268
0
        let s = xyz_inverse.mul_vector(wp.to_vector_d());
1269
0
        let mut v = xyz_matrix.mul_row_vector::<0>(s);
1270
0
        v = v.mul_row_vector::<1>(s);
1271
0
        v = v.mul_row_vector::<2>(s);
1272
0
        v
1273
0
    }
1274
1275
0
    pub fn rgb_to_xyz_matrix(&self) -> Matrix3d {
1276
0
        let xyz_matrix = self.colorant_matrix();
1277
0
        let white_point = Chromaticity::D50.to_xyzd();
1278
0
        ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point)
1279
0
    }
1280
1281
    /// Computes transform matrix RGB -> XYZ -> RGB
1282
    /// Current profile is used as source, other as destination
1283
0
    pub fn transform_matrix(&self, dest: &ColorProfile) -> Matrix3d {
1284
0
        let source = self.rgb_to_xyz_matrix();
1285
0
        let dst = dest.rgb_to_xyz_matrix();
1286
0
        let dest_inverse = dst.inverse();
1287
0
        dest_inverse.mat_mul(source)
1288
0
    }
1289
1290
    /// Returns volume of colors stored in profile
1291
0
    pub fn profile_volume(&self) -> Option<f32> {
1292
0
        let red_prim = self.red_colorant;
1293
0
        let green_prim = self.green_colorant;
1294
0
        let blue_prim = self.blue_colorant;
1295
0
        let tetrahedral_vertices = Matrix3d {
1296
0
            v: [
1297
0
                [red_prim.x, red_prim.y, red_prim.z],
1298
0
                [green_prim.x, green_prim.y, green_prim.z],
1299
0
                [blue_prim.x, blue_prim.y, blue_prim.z],
1300
0
            ],
1301
0
        };
1302
0
        let det = tetrahedral_vertices.determinant()?;
1303
0
        Some((det / 6.0f64) as f32)
1304
0
    }
1305
1306
0
    pub(crate) fn has_device_to_pcs_lut(&self) -> bool {
1307
0
        self.lut_a_to_b_perceptual.is_some()
1308
0
            || self.lut_a_to_b_saturation.is_some()
1309
0
            || self.lut_a_to_b_colorimetric.is_some()
1310
0
    }
1311
1312
0
    pub(crate) fn has_pcs_to_device_lut(&self) -> bool {
1313
0
        self.lut_b_to_a_perceptual.is_some()
1314
0
            || self.lut_b_to_a_saturation.is_some()
1315
0
            || self.lut_b_to_a_colorimetric.is_some()
1316
0
    }
1317
}
1318
1319
#[cfg(test)]
1320
mod tests {
1321
    use super::*;
1322
    use std::fs;
1323
1324
    #[test]
1325
    fn test_gray() {
1326
        if let Ok(gray_icc) = fs::read("./assets/Generic Gray Gamma 2.2 Profile.icc") {
1327
            let f_p = ColorProfile::new_from_slice(&gray_icc).unwrap();
1328
            assert!(f_p.gray_trc.is_some());
1329
        }
1330
    }
1331
1332
    #[test]
1333
    fn test_perceptual() {
1334
        if let Ok(srgb_perceptual_icc) = fs::read("./assets/srgb_perceptual.icc") {
1335
            let f_p = ColorProfile::new_from_slice(&srgb_perceptual_icc).unwrap();
1336
            assert_eq!(f_p.pcs, DataColorSpace::Lab);
1337
            assert_eq!(f_p.color_space, DataColorSpace::Rgb);
1338
            assert_eq!(f_p.version(), ProfileVersion::V4_2);
1339
            assert!(f_p.lut_a_to_b_perceptual.is_some());
1340
            assert!(f_p.lut_b_to_a_perceptual.is_some());
1341
        }
1342
    }
1343
1344
    #[test]
1345
    fn test_us_swop_coated() {
1346
        if let Ok(us_swop_coated) = fs::read("./assets/us_swop_coated.icc") {
1347
            let f_p = ColorProfile::new_from_slice(&us_swop_coated).unwrap();
1348
            assert_eq!(f_p.pcs, DataColorSpace::Lab);
1349
            assert_eq!(f_p.color_space, DataColorSpace::Cmyk);
1350
            assert_eq!(f_p.version(), ProfileVersion::V2_0);
1351
1352
            assert!(f_p.lut_a_to_b_perceptual.is_some());
1353
            assert!(f_p.lut_b_to_a_perceptual.is_some());
1354
1355
            assert!(f_p.lut_a_to_b_colorimetric.is_some());
1356
            assert!(f_p.lut_b_to_a_colorimetric.is_some());
1357
1358
            assert!(f_p.gamut.is_some());
1359
1360
            assert!(f_p.copyright.is_some());
1361
            assert!(f_p.description.is_some());
1362
        }
1363
    }
1364
1365
    #[test]
1366
    fn test_matrix_shaper() {
1367
        if let Ok(matrix_shaper) = fs::read("./assets/Display P3.icc") {
1368
            let f_p = ColorProfile::new_from_slice(&matrix_shaper).unwrap();
1369
            assert_eq!(f_p.pcs, DataColorSpace::Xyz);
1370
            assert_eq!(f_p.color_space, DataColorSpace::Rgb);
1371
            assert_eq!(f_p.version(), ProfileVersion::V4_0);
1372
1373
            assert!(f_p.red_trc.is_some());
1374
            assert!(f_p.blue_trc.is_some());
1375
            assert!(f_p.green_trc.is_some());
1376
1377
            assert_ne!(f_p.red_colorant, Xyzd::default());
1378
            assert_ne!(f_p.blue_colorant, Xyzd::default());
1379
            assert_ne!(f_p.green_colorant, Xyzd::default());
1380
1381
            assert!(f_p.copyright.is_some());
1382
            assert!(f_p.description.is_some());
1383
        }
1384
    }
1385
}