Coverage Report

Created: 2025-10-28 08:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.9/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,
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)]
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
#[derive(Debug, Clone)]
493
pub struct LutDataType {
494
    // used by lut8Type/lut16Type (mft2) only
495
    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
507
impl LutDataType {
508
0
    pub(crate) fn has_same_kind(&self) -> bool {
509
0
        matches!(
510
0
            (&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
0
    }
522
}
523
524
#[derive(Debug, Clone)]
525
pub 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)]
539
pub enum RenderingIntent {
540
    AbsoluteColorimetric = 3,
541
    Saturation = 2,
542
    RelativeColorimetric = 1,
543
    #[default]
544
    Perceptual = 0,
545
}
546
547
impl TryFrom<u32> for RenderingIntent {
548
    type Error = CmsError;
549
550
    #[inline]
551
0
    fn try_from(value: u32) -> Result<Self, Self::Error> {
552
0
        match value {
553
0
            0 => Ok(RenderingIntent::Perceptual),
554
0
            1 => Ok(RenderingIntent::RelativeColorimetric),
555
0
            2 => Ok(RenderingIntent::Saturation),
556
0
            3 => Ok(RenderingIntent::AbsoluteColorimetric),
557
0
            _ => Err(CmsError::InvalidRenderingIntent),
558
        }
559
0
    }
560
}
561
562
impl From<RenderingIntent> for u32 {
563
    #[inline]
564
0
    fn from(value: RenderingIntent) -> Self {
565
0
        match value {
566
0
            RenderingIntent::AbsoluteColorimetric => 3,
567
0
            RenderingIntent::Saturation => 2,
568
0
            RenderingIntent::RelativeColorimetric => 1,
569
0
            RenderingIntent::Perceptual => 0,
570
        }
571
0
    }
572
}
573
574
/// ICC Header
575
#[repr(C)]
576
#[derive(Debug, Clone, Copy)]
577
pub(crate) struct ProfileHeader {
578
    pub size: u32,                         // Size of the profile (computed)
579
    pub cmm_type: u32,                     // Preferred CMM type (ignored)
580
    pub version: ProfileVersion,           // Version (4.3 or 4.4 if CICP is included)
581
    pub profile_class: ProfileClass,       // Display device profile
582
    pub data_color_space: DataColorSpace,  // RGB input color space
583
    pub pcs: DataColorSpace,               // Profile connection space
584
    pub creation_date_time: ColorDateTime, // Date and time
585
    pub signature: ProfileSignature,       // Profile signature
586
    pub platform: u32,                     // Platform target (ignored)
587
    pub flags: u32,                        // Flags (not embedded, can be used independently)
588
    pub device_manufacturer: u32,          // Device manufacturer (ignored)
589
    pub device_model: u32,                 // Device model (ignored)
590
    pub device_attributes: [u8; 8],        // Device attributes (ignored)
591
    pub rendering_intent: RenderingIntent, // Relative colorimetric rendering intent
592
    pub illuminant: Xyz,                   // D50 standard illuminant X
593
    pub creator: u32,                      // Profile creator (ignored)
594
    pub profile_id: [u8; 16],              // Profile id checksum (ignored)
595
    pub reserved: [u8; 28],                // Reserved (ignored)
596
    pub tag_count: u32,                    // Technically not part of header, but required
597
}
598
599
impl ProfileHeader {
600
    #[allow(dead_code)]
601
0
    pub(crate) fn new(size: u32) -> Self {
602
0
        Self {
603
0
            size,
604
0
            cmm_type: 0,
605
0
            version: ProfileVersion::V4_3,
606
0
            profile_class: ProfileClass::DisplayDevice,
607
0
            data_color_space: DataColorSpace::Rgb,
608
0
            pcs: DataColorSpace::Xyz,
609
0
            creation_date_time: ColorDateTime::default(),
610
0
            signature: ProfileSignature::Acsp,
611
0
            platform: 0,
612
0
            flags: 0x00000000,
613
0
            device_manufacturer: 0,
614
0
            device_model: 0,
615
0
            device_attributes: [0; 8],
616
0
            rendering_intent: RenderingIntent::Perceptual,
617
0
            illuminant: Chromaticity::D50.to_xyz(),
618
0
            creator: 0,
619
0
            profile_id: [0; 16],
620
0
            reserved: [0; 28],
621
0
            tag_count: 0,
622
0
        }
623
0
    }
624
625
    /// Creates profile from the buffer
626
0
    pub(crate) fn new_from_slice(slice: &[u8]) -> Result<Self, CmsError> {
627
0
        if slice.len() < size_of::<ProfileHeader>() {
628
0
            return Err(CmsError::InvalidProfile);
629
0
        }
630
0
        let mut cursor = std::io::Cursor::new(slice);
631
0
        let mut buffer = [0u8; size_of::<ProfileHeader>()];
632
0
        cursor
633
0
            .read_exact(&mut buffer)
634
0
            .map_err(|_| CmsError::InvalidProfile)?;
635
636
0
        let header = Self {
637
0
            size: u32::from_be_bytes(buffer[0..4].try_into().unwrap()),
638
0
            cmm_type: u32::from_be_bytes(buffer[4..8].try_into().unwrap()),
639
0
            version: ProfileVersion::try_from(u32::from_be_bytes(
640
0
                buffer[8..12].try_into().unwrap(),
641
0
            ))?,
642
0
            profile_class: ProfileClass::try_from(u32::from_be_bytes(
643
0
                buffer[12..16].try_into().unwrap(),
644
0
            ))?,
645
0
            data_color_space: DataColorSpace::try_from(u32::from_be_bytes(
646
0
                buffer[16..20].try_into().unwrap(),
647
0
            ))?,
648
0
            pcs: DataColorSpace::try_from(u32::from_be_bytes(buffer[20..24].try_into().unwrap()))?,
649
0
            creation_date_time: ColorDateTime::new_from_slice(buffer[24..36].try_into().unwrap())?,
650
0
            signature: ProfileSignature::try_from(u32::from_be_bytes(
651
0
                buffer[36..40].try_into().unwrap(),
652
0
            ))?,
653
0
            platform: u32::from_be_bytes(buffer[40..44].try_into().unwrap()),
654
0
            flags: u32::from_be_bytes(buffer[44..48].try_into().unwrap()),
655
0
            device_manufacturer: u32::from_be_bytes(buffer[48..52].try_into().unwrap()),
656
0
            device_model: u32::from_be_bytes(buffer[52..56].try_into().unwrap()),
657
0
            device_attributes: buffer[56..64].try_into().unwrap(),
658
0
            rendering_intent: RenderingIntent::try_from(u32::from_be_bytes(
659
0
                buffer[64..68].try_into().unwrap(),
660
0
            ))?,
661
0
            illuminant: Xyz::new(
662
0
                s15_fixed16_number_to_float(i32::from_be_bytes(buffer[68..72].try_into().unwrap())),
663
0
                s15_fixed16_number_to_float(i32::from_be_bytes(buffer[72..76].try_into().unwrap())),
664
0
                s15_fixed16_number_to_float(i32::from_be_bytes(buffer[76..80].try_into().unwrap())),
665
            ),
666
0
            creator: u32::from_be_bytes(buffer[80..84].try_into().unwrap()),
667
0
            profile_id: buffer[84..100].try_into().unwrap(),
668
0
            reserved: buffer[100..128].try_into().unwrap(),
669
0
            tag_count: u32::from_be_bytes(buffer[128..132].try_into().unwrap()),
670
        };
671
0
        Ok(header)
672
0
    }
673
}
674
675
/// A [Coding Independent Code Point](https://en.wikipedia.org/wiki/Coding-independent_code_points).
676
#[repr(C)]
677
#[derive(Debug, Clone, Copy)]
678
pub 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)]
686
pub struct LocalizableString {
687
    /// An ISO 639-1 value is expected; any text w. more than two symbols will be truncated
688
    pub language: String,
689
    /// An ISO 3166-1 value is expected; any text w. more than two symbols will be truncated
690
    pub country: String,
691
    pub value: String,
692
}
693
694
impl LocalizableString {
695
    /// Creates new localizable string
696
    ///
697
    /// # Arguments
698
    ///
699
    /// * `language`: an ISO 639-1 value is expected, any text more than 2 symbols will be truncated
700
    /// * `country`: an ISO 3166-1 value is expected, any text more than 2 symbols will be truncated
701
    /// * `value`: String value
702
    ///
703
0
    pub fn new(language: String, country: String, value: String) -> Self {
704
0
        Self {
705
0
            language,
706
0
            country,
707
0
            value,
708
0
        }
709
0
    }
710
}
711
712
#[derive(Debug, Clone)]
713
pub 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)]
722
pub enum ProfileText {
723
    PlainString(String),
724
    Localizable(Vec<LocalizableString>),
725
    Description(DescriptionString),
726
}
727
728
impl ProfileText {
729
0
    pub(crate) fn has_values(&self) -> bool {
730
0
        match self {
731
0
            ProfileText::PlainString(_) => true,
732
0
            ProfileText::Localizable(lc) => !lc.is_empty(),
733
0
            ProfileText::Description(_) => true,
734
        }
735
0
    }
736
}
737
738
#[derive(Debug, Clone, Copy)]
739
pub enum StandardObserver {
740
    D50,
741
    D65,
742
    Unknown,
743
}
744
745
impl From<u32> for StandardObserver {
746
0
    fn from(value: u32) -> Self {
747
0
        if value == 1 {
748
0
            return StandardObserver::D50;
749
0
        } else if value == 2 {
750
0
            return StandardObserver::D65;
751
0
        }
752
0
        StandardObserver::Unknown
753
0
    }
754
}
755
756
#[derive(Debug, Clone, Copy)]
757
pub struct ViewingConditions {
758
    pub illuminant: Xyz,
759
    pub surround: Xyz,
760
    pub observer: StandardObserver,
761
}
762
763
#[derive(Debug, Clone, Copy)]
764
pub enum MeasurementGeometry {
765
    Unknown,
766
    /// 0°:45° or 45°:0°
767
    D45to45,
768
    /// 0°:d or d:0°
769
    D0to0,
770
}
771
772
impl From<u32> for MeasurementGeometry {
773
0
    fn from(value: u32) -> Self {
774
0
        if value == 1 {
775
0
            Self::D45to45
776
0
        } else if value == 2 {
777
0
            Self::D0to0
778
        } else {
779
0
            Self::Unknown
780
        }
781
0
    }
782
}
783
784
#[derive(Debug, Clone, Copy)]
785
pub enum StandardIlluminant {
786
    Unknown,
787
    D50,
788
    D65,
789
    D93,
790
    F2,
791
    D55,
792
    A,
793
    EquiPower,
794
    F8,
795
}
796
797
impl From<u32> for StandardIlluminant {
798
0
    fn from(value: u32) -> Self {
799
0
        match value {
800
0
            1 => StandardIlluminant::D50,
801
0
            2 => StandardIlluminant::D65,
802
0
            3 => StandardIlluminant::D93,
803
0
            4 => StandardIlluminant::F2,
804
0
            5 => StandardIlluminant::D55,
805
0
            6 => StandardIlluminant::A,
806
0
            7 => StandardIlluminant::EquiPower,
807
0
            8 => StandardIlluminant::F8,
808
0
            _ => Self::Unknown,
809
        }
810
0
    }
811
}
812
813
impl From<StandardIlluminant> for u32 {
814
0
    fn from(value: StandardIlluminant) -> Self {
815
0
        match value {
816
0
            StandardIlluminant::Unknown => 0u32,
817
0
            StandardIlluminant::D50 => 1u32,
818
0
            StandardIlluminant::D65 => 2u32,
819
0
            StandardIlluminant::D93 => 3,
820
0
            StandardIlluminant::F2 => 4,
821
0
            StandardIlluminant::D55 => 5,
822
0
            StandardIlluminant::A => 6,
823
0
            StandardIlluminant::EquiPower => 7,
824
0
            StandardIlluminant::F8 => 8,
825
        }
826
0
    }
827
}
828
829
#[derive(Debug, Clone, Copy)]
830
pub 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
/// ICC Profile representation
839
#[repr(C)]
840
#[derive(Debug, Clone, Default)]
841
pub 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
    /// Version for internal and viewing purposes only.
877
    /// On encoding added value to profile will always be V4.
878
    pub(crate) version_internal: ProfileVersion,
879
}
880
881
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Hash)]
882
pub struct ParsingOptions {
883
    // Maximum allowed profile size in bytes
884
    pub max_profile_size: usize,
885
    // Maximum allowed CLUT size in bytes
886
    pub max_allowed_clut_size: usize,
887
    // Maximum allowed TRC size in elements count
888
    pub max_allowed_trc_size: usize,
889
}
890
891
impl Default for ParsingOptions {
892
0
    fn default() -> Self {
893
0
        Self {
894
0
            max_profile_size: MAX_PROFILE_SIZE,
895
0
            max_allowed_clut_size: 10_000_000,
896
0
            max_allowed_trc_size: 40_000,
897
0
        }
898
0
    }
899
}
900
901
impl ColorProfile {
902
    /// Returns profile version
903
0
    pub fn version(&self) -> ProfileVersion {
904
0
        self.version_internal
905
0
    }
906
907
0
    pub fn new_from_slice(slice: &[u8]) -> Result<Self, CmsError> {
908
0
        Self::new_from_slice_with_options(slice, Default::default())
909
0
    }
910
911
0
    pub fn new_from_slice_with_options(
912
0
        slice: &[u8],
913
0
        options: ParsingOptions,
914
0
    ) -> Result<Self, CmsError> {
915
0
        let header = ProfileHeader::new_from_slice(slice)?;
916
0
        let tags_count = header.tag_count as usize;
917
0
        if slice.len() >= options.max_profile_size {
918
0
            return Err(CmsError::InvalidProfile);
919
0
        }
920
0
        let tags_end = tags_count
921
0
            .safe_mul(TAG_SIZE)?
922
0
            .safe_add(size_of::<ProfileHeader>())?;
923
0
        if slice.len() < tags_end {
924
0
            return Err(CmsError::InvalidProfile);
925
0
        }
926
0
        let tags_slice = &slice[size_of::<ProfileHeader>()..tags_end];
927
0
        let mut profile = ColorProfile {
928
0
            rendering_intent: header.rendering_intent,
929
0
            pcs: header.pcs,
930
0
            profile_class: header.profile_class,
931
0
            color_space: header.data_color_space,
932
0
            white_point: header.illuminant.to_xyzd(),
933
0
            version_internal: header.version,
934
0
            ..Default::default()
935
0
        };
936
0
        let color_space = profile.color_space;
937
0
        for tag in tags_slice.chunks_exact(TAG_SIZE) {
938
0
            let tag_value = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
939
0
            let tag_entry = u32::from_be_bytes([tag[4], tag[5], tag[6], tag[7]]);
940
0
            let tag_size = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
941
            // Just ignore unknown tags
942
0
            if let Ok(tag) = Tag::try_from(tag_value) {
943
0
                match tag {
944
                    Tag::RedXyz => {
945
0
                        if color_space == DataColorSpace::Rgb {
946
                            profile.red_colorant =
947
0
                                Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
948
0
                        }
949
                    }
950
                    Tag::GreenXyz => {
951
0
                        if color_space == DataColorSpace::Rgb {
952
                            profile.green_colorant =
953
0
                                Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
954
0
                        }
955
                    }
956
                    Tag::BlueXyz => {
957
0
                        if color_space == DataColorSpace::Rgb {
958
                            profile.blue_colorant =
959
0
                                Self::read_xyz_tag(slice, tag_entry as usize, tag_size)?;
960
0
                        }
961
                    }
962
                    Tag::RedToneReproduction => {
963
0
                        if color_space == DataColorSpace::Rgb {
964
0
                            profile.red_trc = Self::read_trc_tag_s(
965
0
                                slice,
966
0
                                tag_entry as usize,
967
0
                                tag_size,
968
0
                                &options,
969
0
                            )?;
970
0
                        }
971
                    }
972
                    Tag::GreenToneReproduction => {
973
0
                        if color_space == DataColorSpace::Rgb {
974
0
                            profile.green_trc = Self::read_trc_tag_s(
975
0
                                slice,
976
0
                                tag_entry as usize,
977
0
                                tag_size,
978
0
                                &options,
979
0
                            )?;
980
0
                        }
981
                    }
982
                    Tag::BlueToneReproduction => {
983
0
                        if color_space == DataColorSpace::Rgb {
984
0
                            profile.blue_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::GreyToneReproduction => {
993
0
                        if color_space == DataColorSpace::Gray {
994
0
                            profile.gray_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::MediaWhitePoint => {
1003
                        profile.media_white_point =
1004
0
                            Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?;
1005
                    }
1006
                    Tag::Luminance => {
1007
                        profile.luminance =
1008
0
                            Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?;
1009
                    }
1010
                    Tag::Measurement => {
1011
                        profile.measurement =
1012
0
                            Self::read_meas_tag(slice, tag_entry as usize, tag_size)?;
1013
                    }
1014
                    Tag::CodeIndependentPoints => {
1015
                        // This tag may be present when the data colour space in the profile header is RGB, YCbCr, or XYZ, and the
1016
                        // profile class in the profile header is Input or Display. The tag shall not be present for other data colour spaces
1017
                        // or profile classes indicated in the profile header.
1018
0
                        if (profile.profile_class == ProfileClass::InputDevice
1019
0
                            || profile.profile_class == ProfileClass::DisplayDevice)
1020
0
                            && (profile.color_space == DataColorSpace::Rgb
1021
0
                                || profile.color_space == DataColorSpace::YCbr
1022
0
                                || profile.color_space == DataColorSpace::Xyz)
1023
                        {
1024
                            profile.cicp =
1025
0
                                Self::read_cicp_tag(slice, tag_entry as usize, tag_size)?;
1026
0
                        }
1027
                    }
1028
                    Tag::ChromaticAdaptation => {
1029
                        profile.chromatic_adaptation =
1030
0
                            Self::read_chad_tag(slice, tag_entry as usize, tag_size)?;
1031
                    }
1032
                    Tag::BlackPoint => {
1033
                        profile.black_point =
1034
0
                            Self::read_xyz_tag(slice, tag_entry as usize, tag_size).map(Some)?
1035
                    }
1036
                    Tag::DeviceToPcsLutPerceptual => {
1037
0
                        profile.lut_a_to_b_perceptual =
1038
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1039
                    }
1040
                    Tag::DeviceToPcsLutColorimetric => {
1041
0
                        profile.lut_a_to_b_colorimetric =
1042
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1043
                    }
1044
                    Tag::DeviceToPcsLutSaturation => {
1045
0
                        profile.lut_a_to_b_saturation =
1046
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1047
                    }
1048
                    Tag::PcsToDeviceLutPerceptual => {
1049
0
                        profile.lut_b_to_a_perceptual =
1050
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1051
                    }
1052
                    Tag::PcsToDeviceLutColorimetric => {
1053
0
                        profile.lut_b_to_a_colorimetric =
1054
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1055
                    }
1056
                    Tag::PcsToDeviceLutSaturation => {
1057
0
                        profile.lut_b_to_a_saturation =
1058
0
                            Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1059
                    }
1060
                    Tag::Gamut => {
1061
0
                        profile.gamut = Self::read_lut_tag(slice, tag_entry, tag_size, &options)?;
1062
                    }
1063
                    Tag::Copyright => {
1064
0
                        profile.copyright =
1065
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1066
                    }
1067
                    Tag::ProfileDescription => {
1068
0
                        profile.description =
1069
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1070
                    }
1071
                    Tag::ViewingConditionsDescription => {
1072
0
                        profile.viewing_conditions_description =
1073
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1074
                    }
1075
                    Tag::DeviceModel => {
1076
0
                        profile.device_model =
1077
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1078
                    }
1079
                    Tag::DeviceManufacturer => {
1080
0
                        profile.device_manufacturer =
1081
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1082
                    }
1083
                    Tag::CharTarget => {
1084
0
                        profile.char_target =
1085
0
                            Self::read_string_tag(slice, tag_entry as usize, tag_size)?;
1086
                    }
1087
0
                    Tag::Chromaticity => {}
1088
                    Tag::ObserverConditions => {
1089
                        profile.viewing_conditions =
1090
0
                            Self::read_viewing_conditions(slice, tag_entry as usize, tag_size)?;
1091
                    }
1092
                    Tag::Technology => {
1093
                        profile.technology =
1094
0
                            Self::read_tech_tag(slice, tag_entry as usize, tag_size)?;
1095
                    }
1096
                    Tag::CalibrationDateTime => {
1097
                        profile.calibration_date =
1098
0
                            Self::read_date_time_tag(slice, tag_entry as usize, tag_size)?;
1099
                    }
1100
                }
1101
0
            }
1102
        }
1103
1104
0
        Ok(profile)
1105
0
    }
1106
}
1107
1108
impl ColorProfile {
1109
    #[inline]
1110
0
    pub fn colorant_matrix(&self) -> Matrix3d {
1111
0
        Matrix3d {
1112
0
            v: [
1113
0
                [
1114
0
                    self.red_colorant.x,
1115
0
                    self.green_colorant.x,
1116
0
                    self.blue_colorant.x,
1117
0
                ],
1118
0
                [
1119
0
                    self.red_colorant.y,
1120
0
                    self.green_colorant.y,
1121
0
                    self.blue_colorant.y,
1122
0
                ],
1123
0
                [
1124
0
                    self.red_colorant.z,
1125
0
                    self.green_colorant.z,
1126
0
                    self.blue_colorant.z,
1127
0
                ],
1128
0
            ],
1129
0
        }
1130
0
    }
1131
1132
    /// Computes colorants matrix. Returns not transposed matrix.
1133
    ///
1134
    /// To work on `const` context this method does have restrictions.
1135
    /// If invalid values were provided it may return invalid matrix or NaNs.
1136
0
    pub const fn colorants_matrix(white_point: XyY, primaries: ColorPrimaries) -> Matrix3d {
1137
0
        let red_xyz = primaries.red.to_xyzd();
1138
0
        let green_xyz = primaries.green.to_xyzd();
1139
0
        let blue_xyz = primaries.blue.to_xyzd();
1140
1141
0
        let xyz_matrix = Matrix3d {
1142
0
            v: [
1143
0
                [red_xyz.x, green_xyz.x, blue_xyz.x],
1144
0
                [red_xyz.y, green_xyz.y, blue_xyz.y],
1145
0
                [red_xyz.z, green_xyz.z, blue_xyz.z],
1146
0
            ],
1147
0
        };
1148
0
        let colorants = ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyzd());
1149
0
        adapt_to_d50_d(colorants, white_point)
1150
0
    }
1151
1152
    /// Updates RGB triple colorimetry from 3 [Chromaticity] and white point
1153
0
    pub const fn update_rgb_colorimetry(&mut self, white_point: XyY, primaries: ColorPrimaries) {
1154
0
        let red_xyz = primaries.red.to_xyzd();
1155
0
        let green_xyz = primaries.green.to_xyzd();
1156
0
        let blue_xyz = primaries.blue.to_xyzd();
1157
1158
0
        self.chromatic_adaptation = Some(BRADFORD_D);
1159
0
        self.update_rgb_colorimetry_triplet(white_point, red_xyz, green_xyz, blue_xyz)
1160
0
    }
1161
1162
    /// Updates RGB triple colorimetry from 3 [Xyzd] and white point
1163
    ///
1164
    /// To work on `const` context this method does have restrictions.
1165
    /// If invalid values were provided it may return invalid matrix or NaNs.
1166
0
    pub const fn update_rgb_colorimetry_triplet(
1167
0
        &mut self,
1168
0
        white_point: XyY,
1169
0
        red_xyz: Xyzd,
1170
0
        green_xyz: Xyzd,
1171
0
        blue_xyz: Xyzd,
1172
0
    ) {
1173
0
        let xyz_matrix = Matrix3d {
1174
0
            v: [
1175
0
                [red_xyz.x, green_xyz.x, blue_xyz.x],
1176
0
                [red_xyz.y, green_xyz.y, blue_xyz.y],
1177
0
                [red_xyz.z, green_xyz.z, blue_xyz.z],
1178
0
            ],
1179
0
        };
1180
0
        let colorants = ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyzd());
1181
0
        let colorants = adapt_to_d50_d(colorants, white_point);
1182
1183
0
        self.update_colorants(colorants);
1184
0
    }
1185
1186
0
    pub(crate) const fn update_colorants(&mut self, colorants: Matrix3d) {
1187
        // note: there's a transpose type of operation going on here
1188
0
        self.red_colorant.x = colorants.v[0][0];
1189
0
        self.red_colorant.y = colorants.v[1][0];
1190
0
        self.red_colorant.z = colorants.v[2][0];
1191
0
        self.green_colorant.x = colorants.v[0][1];
1192
0
        self.green_colorant.y = colorants.v[1][1];
1193
0
        self.green_colorant.z = colorants.v[2][1];
1194
0
        self.blue_colorant.x = colorants.v[0][2];
1195
0
        self.blue_colorant.y = colorants.v[1][2];
1196
0
        self.blue_colorant.z = colorants.v[2][2];
1197
0
    }
1198
1199
    /// Updates RGB triple colorimetry from CICP
1200
0
    pub fn update_rgb_colorimetry_from_cicp(&mut self, cicp: CicpProfile) -> bool {
1201
0
        self.cicp = Some(cicp);
1202
0
        if !cicp.color_primaries.has_chromaticity()
1203
0
            || !cicp.transfer_characteristics.has_transfer_curve()
1204
        {
1205
0
            return false;
1206
0
        }
1207
0
        let primaries_xy: ColorPrimaries = match cicp.color_primaries.try_into() {
1208
0
            Ok(primaries) => primaries,
1209
0
            Err(_) => return false,
1210
        };
1211
0
        let white_point: Chromaticity = match cicp.color_primaries.white_point() {
1212
0
            Ok(v) => v,
1213
0
            Err(_) => return false,
1214
        };
1215
0
        self.update_rgb_colorimetry(white_point.to_xyyb(), primaries_xy);
1216
1217
0
        let red_trc: ToneReprCurve = match cicp.transfer_characteristics.try_into() {
1218
0
            Ok(trc) => trc,
1219
0
            Err(_) => return false,
1220
        };
1221
0
        self.green_trc = Some(red_trc.clone());
1222
0
        self.blue_trc = Some(red_trc.clone());
1223
0
        self.red_trc = Some(red_trc);
1224
0
        false
1225
0
    }
1226
1227
0
    pub const fn rgb_to_xyz(&self, xyz_matrix: Matrix3f, wp: Xyz) -> Matrix3f {
1228
0
        let xyz_inverse = xyz_matrix.inverse();
1229
0
        let s = xyz_inverse.mul_vector(wp.to_vector());
1230
0
        let mut v = xyz_matrix.mul_row_vector::<0>(s);
1231
0
        v = v.mul_row_vector::<1>(s);
1232
0
        v.mul_row_vector::<2>(s)
1233
0
    }
1234
1235
    ///TODO: make primary instead of [rgb_to_xyz] in the next major version
1236
0
    pub(crate) const fn rgb_to_xyz_static(xyz_matrix: Matrix3f, wp: Xyz) -> Matrix3f {
1237
0
        let xyz_inverse = xyz_matrix.inverse();
1238
0
        let s = xyz_inverse.mul_vector(wp.to_vector());
1239
0
        let mut v = xyz_matrix.mul_row_vector::<0>(s);
1240
0
        v = v.mul_row_vector::<1>(s);
1241
0
        v.mul_row_vector::<2>(s)
1242
0
    }
1243
1244
    /// If Primaries is invalid will return invalid matrix on const context.
1245
    /// This assumes not transposed matrix and returns not transposed matrix.
1246
0
    pub const fn rgb_to_xyz_d(xyz_matrix: Matrix3d, wp: Xyzd) -> Matrix3d {
1247
0
        let xyz_inverse = xyz_matrix.inverse();
1248
0
        let s = xyz_inverse.mul_vector(wp.to_vector_d());
1249
0
        let mut v = xyz_matrix.mul_row_vector::<0>(s);
1250
0
        v = v.mul_row_vector::<1>(s);
1251
0
        v = v.mul_row_vector::<2>(s);
1252
0
        v
1253
0
    }
1254
1255
0
    pub fn rgb_to_xyz_matrix(&self) -> Matrix3d {
1256
0
        let xyz_matrix = self.colorant_matrix();
1257
0
        let white_point = Chromaticity::D50.to_xyzd();
1258
0
        ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point)
1259
0
    }
1260
1261
    /// Computes transform matrix RGB -> XYZ -> RGB
1262
    /// Current profile is used as source, other as destination
1263
0
    pub fn transform_matrix(&self, dest: &ColorProfile) -> Matrix3d {
1264
0
        let source = self.rgb_to_xyz_matrix();
1265
0
        let dst = dest.rgb_to_xyz_matrix();
1266
0
        let dest_inverse = dst.inverse();
1267
0
        dest_inverse.mat_mul(source)
1268
0
    }
1269
1270
    /// Returns volume of colors stored in profile
1271
0
    pub fn profile_volume(&self) -> Option<f32> {
1272
0
        let red_prim = self.red_colorant;
1273
0
        let green_prim = self.green_colorant;
1274
0
        let blue_prim = self.blue_colorant;
1275
0
        let tetrahedral_vertices = Matrix3d {
1276
0
            v: [
1277
0
                [red_prim.x, red_prim.y, red_prim.z],
1278
0
                [green_prim.x, green_prim.y, green_prim.z],
1279
0
                [blue_prim.x, blue_prim.y, blue_prim.z],
1280
0
            ],
1281
0
        };
1282
0
        let det = tetrahedral_vertices.determinant()?;
1283
0
        Some((det / 6.0f64) as f32)
1284
0
    }
1285
1286
0
    pub(crate) fn has_device_to_pcs_lut(&self) -> bool {
1287
0
        self.lut_a_to_b_perceptual.is_some()
1288
0
            || self.lut_a_to_b_saturation.is_some()
1289
0
            || self.lut_a_to_b_colorimetric.is_some()
1290
0
    }
1291
1292
0
    pub(crate) fn has_pcs_to_device_lut(&self) -> bool {
1293
0
        self.lut_b_to_a_perceptual.is_some()
1294
0
            || self.lut_b_to_a_saturation.is_some()
1295
0
            || self.lut_b_to_a_colorimetric.is_some()
1296
0
    }
1297
}
1298
1299
#[cfg(test)]
1300
mod 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
}