1 //  qcms
2 //  Copyright (C) 2009 Mozilla Foundation
3 //  Copyright (C) 1998-2007 Marti Maria
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the "Software"),
7 // to deal in the Software without restriction, including without limitation
8 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 // and/or sell copies of the Software, and to permit persons to whom the Software
10 // is furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
17 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 
23 use std::{
24     convert::TryInto,
25     sync::atomic::{AtomicBool, Ordering},
26     sync::Arc,
27 };
28 
29 use crate::{
30     double_to_s15Fixed16Number,
31     transform::{set_rgb_colorants, PrecacheOuput},
32 };
33 use crate::{matrix::Matrix, s15Fixed16Number, s15Fixed16Number_to_float, Intent, Intent::*};
34 
35 pub static SUPPORTS_ICCV4: AtomicBool = AtomicBool::new(cfg!(feature = "iccv4-enabled"));
36 
37 pub const RGB_SIGNATURE: u32 = 0x52474220;
38 pub const GRAY_SIGNATURE: u32 = 0x47524159;
39 pub const XYZ_SIGNATURE: u32 = 0x58595A20;
40 pub const LAB_SIGNATURE: u32 = 0x4C616220;
41 pub const CMYK_SIGNATURE: u32 = 0x434D594B; // 'CMYK'
42 
43 /// A color profile
44 #[derive(Default, Debug)]
45 pub struct Profile {
46     pub(crate) class_type: u32,
47     pub(crate) color_space: u32,
48     pub(crate) pcs: u32,
49     pub(crate) rendering_intent: Intent,
50     pub(crate) redColorant: XYZNumber,
51     pub(crate) blueColorant: XYZNumber,
52     pub(crate) greenColorant: XYZNumber,
53     pub(crate) redTRC: Option<Box<curveType>>,
54     pub(crate) blueTRC: Option<Box<curveType>>,
55     pub(crate) greenTRC: Option<Box<curveType>>,
56     pub(crate) grayTRC: Option<Box<curveType>>,
57     pub(crate) A2B0: Option<Box<lutType>>,
58     pub(crate) B2A0: Option<Box<lutType>>,
59     pub(crate) mAB: Option<Box<lutmABType>>,
60     pub(crate) mBA: Option<Box<lutmABType>>,
61     pub(crate) chromaticAdaption: Option<Matrix>,
62     pub(crate) output_table_r: Option<Arc<PrecacheOuput>>,
63     pub(crate) output_table_g: Option<Arc<PrecacheOuput>>,
64     pub(crate) output_table_b: Option<Arc<PrecacheOuput>>,
65     is_srgb: bool,
66 }
67 
68 #[derive(Debug, Default)]
69 #[allow(clippy::upper_case_acronyms)]
70 pub(crate) struct lutmABType {
71     pub num_in_channels: u8,
72     pub num_out_channels: u8,
73     // 16 is the upperbound, actual is 0..num_in_channels.
74     pub num_grid_points: [u8; 16],
75     pub e00: s15Fixed16Number,
76     pub e01: s15Fixed16Number,
77     pub e02: s15Fixed16Number,
78     pub e03: s15Fixed16Number,
79     pub e10: s15Fixed16Number,
80     pub e11: s15Fixed16Number,
81     pub e12: s15Fixed16Number,
82     pub e13: s15Fixed16Number,
83     pub e20: s15Fixed16Number,
84     pub e21: s15Fixed16Number,
85     pub e22: s15Fixed16Number,
86     pub e23: s15Fixed16Number,
87     // reversed elements (for mBA)
88     pub reversed: bool,
89     pub clut_table: Option<Vec<f32>>,
90     pub a_curves: [Option<Box<curveType>>; MAX_CHANNELS],
91     pub b_curves: [Option<Box<curveType>>; MAX_CHANNELS],
92     pub m_curves: [Option<Box<curveType>>; MAX_CHANNELS],
93 }
94 #[derive(Clone, Debug)]
95 pub(crate) enum curveType {
96     Curve(Vec<uInt16Number>),
97     /// The ICC parametricCurveType is specified in terms of s15Fixed16Number,
98     /// so it's possible to use this variant to specify greater precision than
99     /// any raw ICC profile could
100     Parametric(Vec<f32>),
101 }
102 type uInt16Number = u16;
103 
104 /* should lut8Type and lut16Type be different types? */
105 #[derive(Debug)]
106 pub(crate) struct lutType {
107     // used by lut8Type/lut16Type (mft2) only
108     pub num_input_channels: u8,
109     pub num_output_channels: u8,
110     pub num_clut_grid_points: u8,
111     pub e00: s15Fixed16Number,
112     pub e01: s15Fixed16Number,
113     pub e02: s15Fixed16Number,
114     pub e10: s15Fixed16Number,
115     pub e11: s15Fixed16Number,
116     pub e12: s15Fixed16Number,
117     pub e20: s15Fixed16Number,
118     pub e21: s15Fixed16Number,
119     pub e22: s15Fixed16Number,
120     pub num_input_table_entries: u16,
121     pub num_output_table_entries: u16,
122     pub input_table: Vec<f32>,
123     pub clut_table: Vec<f32>,
124     pub output_table: Vec<f32>,
125 }
126 
127 #[repr(C)]
128 #[derive(Copy, Clone, Debug, Default)]
129 #[allow(clippy::upper_case_acronyms)]
130 pub struct XYZNumber {
131     pub X: s15Fixed16Number,
132     pub Y: s15Fixed16Number,
133     pub Z: s15Fixed16Number,
134 }
135 
136 /// A color in the CIE xyY color space
137 /* the names for the following two types are sort of ugly */
138 #[repr(C)]
139 #[derive(Copy, Clone)]
140 #[allow(clippy::upper_case_acronyms)]
141 pub struct qcms_CIE_xyY {
142     pub x: f64,
143     pub y: f64,
144     pub Y: f64,
145 }
146 
147 /// A more convenient type for specifying primaries and white points where
148 /// luminosity is irrelevant
149 struct qcms_chromaticity {
150     x: f64,
151     y: f64,
152 }
153 
154 impl qcms_chromaticity {
155     const D65: Self = Self {
156         x: 0.3127,
157         y: 0.3290,
158     };
159 }
160 
161 impl From<qcms_chromaticity> for qcms_CIE_xyY {
162     fn from(qcms_chromaticity { x, y }: qcms_chromaticity) -> Self {
163         Self { x, y, Y: 1.0 }
164     }
165 }
166 
167 /// a set of CIE_xyY values that can use to describe the primaries of a color space
168 #[repr(C)]
169 #[derive(Copy, Clone)]
170 #[allow(clippy::upper_case_acronyms)]
171 pub struct qcms_CIE_xyYTRIPLE {
172     pub red: qcms_CIE_xyY,
173     pub green: qcms_CIE_xyY,
174     pub blue: qcms_CIE_xyY,
175 }
176 
177 struct Tag {
178     signature: u32,
179     offset: u32,
180     size: u32,
181 }
182 
183 /* It might be worth having a unified limit on content controlled
184  * allocation per profile. This would remove the need for many
185  * of the arbitrary limits that we used */
186 
187 type TagIndex = [Tag];
188 
189 /* a wrapper around the memory that we are going to parse
190  * into a qcms_profile */
191 struct MemSource<'a> {
192     buf: &'a [u8],
193     valid: bool,
194     invalid_reason: Option<&'static str>,
195 }
196 pub type uInt8Number = u8;
197 #[inline]
uInt8Number_to_float(a: uInt8Number) -> f32198 fn uInt8Number_to_float(a: uInt8Number) -> f32 {
199     a as f32 / 255.0
200 }
201 
202 #[inline]
uInt16Number_to_float(a: uInt16Number) -> f32203 fn uInt16Number_to_float(a: uInt16Number) -> f32 {
204     a as f32 / 65535.0
205 }
206 
invalid_source(mut mem: &mut MemSource, reason: &'static str)207 fn invalid_source(mut mem: &mut MemSource, reason: &'static str) {
208     mem.valid = false;
209     mem.invalid_reason = Some(reason);
210 }
read_u32(mem: &mut MemSource, offset: usize) -> u32211 fn read_u32(mem: &mut MemSource, offset: usize) -> u32 {
212     let val = mem.buf.get(offset..offset + 4);
213     if let Some(val) = val {
214         let val = val.try_into().unwrap();
215         u32::from_be_bytes(val)
216     } else {
217         invalid_source(mem, "Invalid offset");
218         0
219     }
220 }
read_u16(mem: &mut MemSource, offset: usize) -> u16221 fn read_u16(mem: &mut MemSource, offset: usize) -> u16 {
222     let val = mem.buf.get(offset..offset + 2);
223     if let Some(val) = val {
224         let val = val.try_into().unwrap();
225         u16::from_be_bytes(val)
226     } else {
227         invalid_source(mem, "Invalid offset");
228         0
229     }
230 }
read_u8(mem: &mut MemSource, offset: usize) -> u8231 fn read_u8(mem: &mut MemSource, offset: usize) -> u8 {
232     let val = mem.buf.get(offset);
233     if let Some(val) = val {
234         *val
235     } else {
236         invalid_source(mem, "Invalid offset");
237         0
238     }
239 }
read_s15Fixed16Number(mem: &mut MemSource, offset: usize) -> s15Fixed16Number240 fn read_s15Fixed16Number(mem: &mut MemSource, offset: usize) -> s15Fixed16Number {
241     read_u32(mem, offset) as s15Fixed16Number
242 }
read_uInt8Number(mem: &mut MemSource, offset: usize) -> uInt8Number243 fn read_uInt8Number(mem: &mut MemSource, offset: usize) -> uInt8Number {
244     read_u8(mem, offset)
245 }
read_uInt16Number(mem: &mut MemSource, offset: usize) -> uInt16Number246 fn read_uInt16Number(mem: &mut MemSource, offset: usize) -> uInt16Number {
247     read_u16(mem, offset)
248 }
write_u32(mem: &mut [u8], offset: usize, value: u32)249 pub fn write_u32(mem: &mut [u8], offset: usize, value: u32) {
250     // we use get() and expect() instead of [..] so there's only one call to panic
251     // instead of two
252     mem.get_mut(offset..offset + std::mem::size_of_val(&value))
253         .expect("OOB")
254         .copy_from_slice(&value.to_be_bytes());
255 }
write_u16(mem: &mut [u8], offset: usize, value: u16)256 pub fn write_u16(mem: &mut [u8], offset: usize, value: u16) {
257     // we use get() and expect() instead of [..] so there's only one call to panic
258     // intead of two
259     mem.get_mut(offset..offset + std::mem::size_of_val(&value))
260         .expect("OOB")
261         .copy_from_slice(&value.to_be_bytes());
262 }
263 
264 /* An arbitrary 4MB limit on profile size */
265 pub(crate) const MAX_PROFILE_SIZE: usize = 1024 * 1024 * 4;
266 const MAX_TAG_COUNT: u32 = 1024;
267 
check_CMM_type_signature(_src: &mut MemSource)268 fn check_CMM_type_signature(_src: &mut MemSource) {
269     //uint32_t CMM_type_signature = read_u32(src, 4);
270     //TODO: do the check?
271 }
check_profile_version(src: &mut MemSource)272 fn check_profile_version(src: &mut MemSource) {
273     /*
274     uint8_t major_revision = read_u8(src, 8 + 0);
275     uint8_t minor_revision = read_u8(src, 8 + 1);
276     */
277     let reserved1: u8 = read_u8(src, (8 + 2) as usize);
278     let reserved2: u8 = read_u8(src, (8 + 3) as usize);
279     /* Checking the version doesn't buy us anything
280     if (major_revision != 0x4) {
281         if (major_revision > 0x2)
282             invalid_source(src, "Unsupported major revision");
283         if (minor_revision > 0x40)
284             invalid_source(src, "Unsupported minor revision");
285     }
286     */
287     if reserved1 != 0 || reserved2 != 0 {
288         invalid_source(src, "Invalid reserved bytes");
289     };
290 }
291 
292 const INPUT_DEVICE_PROFILE: u32 = 0x73636e72; // 'scnr'
293 pub const DISPLAY_DEVICE_PROFILE: u32 = 0x6d6e7472; // 'mntr'
294 const OUTPUT_DEVICE_PROFILE: u32 = 0x70727472; // 'prtr'
295 const DEVICE_LINK_PROFILE: u32 = 0x6c696e6b; // 'link'
296 const COLOR_SPACE_PROFILE: u32 = 0x73706163; // 'spac'
297 const ABSTRACT_PROFILE: u32 = 0x61627374; // 'abst'
298 const NAMED_COLOR_PROFILE: u32 = 0x6e6d636c; // 'nmcl'
299 
read_class_signature(mut profile: &mut Profile, mem: &mut MemSource)300 fn read_class_signature(mut profile: &mut Profile, mem: &mut MemSource) {
301     profile.class_type = read_u32(mem, 12);
302     match profile.class_type {
303         DISPLAY_DEVICE_PROFILE
304         | INPUT_DEVICE_PROFILE
305         | OUTPUT_DEVICE_PROFILE
306         | COLOR_SPACE_PROFILE => {}
307         _ => {
308             invalid_source(mem, "Invalid  Profile/Device Class signature");
309         }
310     };
311 }
read_color_space(mut profile: &mut Profile, mem: &mut MemSource)312 fn read_color_space(mut profile: &mut Profile, mem: &mut MemSource) {
313     profile.color_space = read_u32(mem, 16);
314     match profile.color_space {
315         RGB_SIGNATURE | GRAY_SIGNATURE => {}
316         #[cfg(feature = "cmyk")]
317         CMYK_SIGNATURE => {}
318         _ => {
319             invalid_source(mem, "Unsupported colorspace");
320         }
321     };
322 }
read_pcs(mut profile: &mut Profile, mem: &mut MemSource)323 fn read_pcs(mut profile: &mut Profile, mem: &mut MemSource) {
324     profile.pcs = read_u32(mem, 20);
325     match profile.pcs {
326         XYZ_SIGNATURE | LAB_SIGNATURE => {}
327         _ => {
328             invalid_source(mem, "Unsupported pcs");
329         }
330     };
331 }
read_tag_table(_profile: &mut Profile, mem: &mut MemSource) -> Vec<Tag>332 fn read_tag_table(_profile: &mut Profile, mem: &mut MemSource) -> Vec<Tag> {
333     let count = read_u32(mem, 128);
334     if count > MAX_TAG_COUNT {
335         invalid_source(mem, "max number of tags exceeded");
336         return Vec::new();
337     }
338     let mut index = Vec::with_capacity(count as usize);
339     for i in 0..count {
340         let tag_start = (128 + 4 + 4 * i * 3) as usize;
341         let offset = read_u32(mem, tag_start + 4);
342         if offset as usize > mem.buf.len() {
343             invalid_source(mem, "tag points beyond the end of the buffer");
344         }
345         index.push(Tag {
346             signature: read_u32(mem, tag_start),
347             offset,
348             size: read_u32(mem, tag_start + 8),
349         });
350     }
351 
352     index
353 }
354 
355 /// Checks a profile for obvious inconsistencies and returns
356 /// true if the profile looks bogus and should probably be
357 /// ignored.
358 #[no_mangle]
qcms_profile_is_bogus(profile: &mut Profile) -> bool359 pub extern "C" fn qcms_profile_is_bogus(profile: &mut Profile) -> bool {
360     let mut sum: [f32; 3] = [0.; 3];
361     let mut target: [f32; 3] = [0.; 3];
362     let mut tolerance: [f32; 3] = [0.; 3];
363     let rX: f32;
364     let rY: f32;
365     let rZ: f32;
366     let gX: f32;
367     let gY: f32;
368     let gZ: f32;
369     let bX: f32;
370     let bY: f32;
371     let bZ: f32;
372     let negative: bool;
373     let mut i: u32;
374     // We currently only check the bogosity of RGB profiles
375     if profile.color_space != RGB_SIGNATURE {
376         return false;
377     }
378     if profile.A2B0.is_some()
379         || profile.B2A0.is_some()
380         || profile.mAB.is_some()
381         || profile.mBA.is_some()
382     {
383         return false;
384     }
385     rX = s15Fixed16Number_to_float(profile.redColorant.X);
386     rY = s15Fixed16Number_to_float(profile.redColorant.Y);
387     rZ = s15Fixed16Number_to_float(profile.redColorant.Z);
388     gX = s15Fixed16Number_to_float(profile.greenColorant.X);
389     gY = s15Fixed16Number_to_float(profile.greenColorant.Y);
390     gZ = s15Fixed16Number_to_float(profile.greenColorant.Z);
391     bX = s15Fixed16Number_to_float(profile.blueColorant.X);
392     bY = s15Fixed16Number_to_float(profile.blueColorant.Y);
393     bZ = s15Fixed16Number_to_float(profile.blueColorant.Z);
394     // Sum the values; they should add up to something close to white
395     sum[0] = rX + gX + bX;
396     sum[1] = rY + gY + bY;
397     sum[2] = rZ + gZ + bZ;
398     // Build our target vector (see mozilla bug 460629)
399     target[0] = 0.96420;
400     target[1] = 1.00000;
401     target[2] = 0.82491;
402     // Our tolerance vector - Recommended by Chris Murphy based on
403     // conversion from the LAB space criterion of no more than 3 in any one
404     // channel. This is similar to, but slightly more tolerant than Adobe's
405     // criterion.
406     tolerance[0] = 0.02;
407     tolerance[1] = 0.02;
408     tolerance[2] = 0.04;
409     // Compare with our tolerance
410     i = 0;
411     while i < 3 {
412         if !(sum[i as usize] - tolerance[i as usize] <= target[i as usize]
413             && sum[i as usize] + tolerance[i as usize] >= target[i as usize])
414         {
415             return true;
416         }
417         i += 1
418     }
419     if !cfg!(target_os = "macos") {
420         negative = (rX < 0.)
421             || (rY < 0.)
422             || (rZ < 0.)
423             || (gX < 0.)
424             || (gY < 0.)
425             || (gZ < 0.)
426             || (bX < 0.)
427             || (bY < 0.)
428             || (bZ < 0.);
429     } else {
430         // Chromatic adaption to D50 can result in negative XYZ, but the white
431         // point D50 tolerance test has passed. Accept negative values herein.
432         // See https://bugzilla.mozilla.org/show_bug.cgi?id=498245#c18 onwards
433         // for discussion about whether profile XYZ can or cannot be negative,
434         // per the spec. Also the https://bugzil.la/450923 user report.
435 
436         // FIXME: allow this relaxation on all ports?
437         negative = false; // bogus
438     }
439     if negative {
440         return true;
441     }
442     // All Good
443     false
444 }
445 
446 pub const TAG_bXYZ: u32 = 0x6258595a;
447 pub const TAG_gXYZ: u32 = 0x6758595a;
448 pub const TAG_rXYZ: u32 = 0x7258595a;
449 pub const TAG_rTRC: u32 = 0x72545243;
450 pub const TAG_bTRC: u32 = 0x62545243;
451 pub const TAG_gTRC: u32 = 0x67545243;
452 pub const TAG_kTRC: u32 = 0x6b545243;
453 pub const TAG_A2B0: u32 = 0x41324230;
454 pub const TAG_B2A0: u32 = 0x42324130;
455 pub const TAG_CHAD: u32 = 0x63686164;
456 
find_tag(index: &TagIndex, tag_id: u32) -> Option<&Tag>457 fn find_tag(index: &TagIndex, tag_id: u32) -> Option<&Tag> {
458     for t in index {
459         if t.signature == tag_id {
460             return Some(t);
461         }
462     }
463     None
464 }
465 
466 pub const XYZ_TYPE: u32 = 0x58595a20; // 'XYZ '
467 pub const CURVE_TYPE: u32 = 0x63757276; // 'curv'
468 pub const PARAMETRIC_CURVE_TYPE: u32 = 0x70617261; // 'para'
469 pub const LUT16_TYPE: u32 = 0x6d667432; // 'mft2'
470 pub const LUT8_TYPE: u32 = 0x6d667431; // 'mft1'
471 pub const LUT_MAB_TYPE: u32 = 0x6d414220; // 'mAB '
472 pub const LUT_MBA_TYPE: u32 = 0x6d424120; // 'mBA '
473 pub const CHROMATIC_TYPE: u32 = 0x73663332; // 'sf32'
474 
read_tag_s15Fixed16ArrayType(src: &mut MemSource, tag: &Tag) -> Matrix475 fn read_tag_s15Fixed16ArrayType(src: &mut MemSource, tag: &Tag) -> Matrix {
476     let mut matrix: Matrix = Matrix { m: [[0.; 3]; 3] };
477     let offset: u32 = tag.offset;
478     let type_0: u32 = read_u32(src, offset as usize);
479     // Check mandatory type signature for s16Fixed16ArrayType
480     if type_0 != CHROMATIC_TYPE {
481         invalid_source(src, "unexpected type, expected \'sf32\'");
482     }
483     for i in 0..=8 {
484         matrix.m[(i / 3) as usize][(i % 3) as usize] = s15Fixed16Number_to_float(
485             read_s15Fixed16Number(src, (offset + 8 + (i * 4) as u32) as usize),
486         );
487     }
488     matrix
489 }
read_tag_XYZType(src: &mut MemSource, index: &TagIndex, tag_id: u32) -> XYZNumber490 fn read_tag_XYZType(src: &mut MemSource, index: &TagIndex, tag_id: u32) -> XYZNumber {
491     let mut num = XYZNumber { X: 0, Y: 0, Z: 0 };
492     let tag = find_tag(&index, tag_id);
493     if let Some(tag) = tag {
494         let offset: u32 = tag.offset;
495         let type_0: u32 = read_u32(src, offset as usize);
496         if type_0 != XYZ_TYPE {
497             invalid_source(src, "unexpected type, expected XYZ");
498         }
499         num.X = read_s15Fixed16Number(src, (offset + 8) as usize);
500         num.Y = read_s15Fixed16Number(src, (offset + 12) as usize);
501         num.Z = read_s15Fixed16Number(src, (offset + 16) as usize)
502     } else {
503         invalid_source(src, "missing xyztag");
504     }
505     num
506 }
507 // Read the tag at a given offset rather then the tag_index.
508 // This method is used when reading mAB tags where nested curveType are
509 // present that are not part of the tag_index.
read_curveType(src: &mut MemSource, offset: u32, len: &mut u32) -> Option<Box<curveType>>510 fn read_curveType(src: &mut MemSource, offset: u32, len: &mut u32) -> Option<Box<curveType>> {
511     const COUNT_TO_LENGTH: [u32; 5] = [1, 3, 4, 5, 7]; //PARAMETRIC_CURVE_TYPE
512     let type_0: u32 = read_u32(src, offset as usize);
513     let count: u32;
514     if type_0 != CURVE_TYPE && type_0 != PARAMETRIC_CURVE_TYPE {
515         invalid_source(src, "unexpected type, expected CURV or PARA");
516         return None;
517     }
518     if type_0 == CURVE_TYPE {
519         count = read_u32(src, (offset + 8) as usize);
520         //arbitrary
521         if count > 40000 {
522             invalid_source(src, "curve size too large");
523             return None;
524         }
525         let mut table = Vec::with_capacity(count as usize);
526         for i in 0..count {
527             table.push(read_u16(src, (offset + 12 + i * 2) as usize));
528         }
529         *len = 12 + count * 2;
530         Some(Box::new(curveType::Curve(table)))
531     } else {
532         count = read_u16(src, (offset + 8) as usize) as u32;
533         if count > 4 {
534             invalid_source(src, "parametric function type not supported.");
535             return None;
536         }
537         let mut params = Vec::with_capacity(count as usize);
538         for i in 0..COUNT_TO_LENGTH[count as usize] {
539             params.push(s15Fixed16Number_to_float(read_s15Fixed16Number(
540                 src,
541                 (offset + 12 + i * 4) as usize,
542             )));
543         }
544         *len = 12 + COUNT_TO_LENGTH[count as usize] * 4;
545         if count == 1 || count == 2 {
546             /* we have a type 1 or type 2 function that has a division by 'a' */
547             let a: f32 = params[1];
548             if a == 0.0 {
549                 invalid_source(src, "parametricCurve definition causes division by zero");
550             }
551         }
552         Some(Box::new(curveType::Parametric(params)))
553     }
554 }
read_tag_curveType( src: &mut MemSource, index: &TagIndex, tag_id: u32, ) -> Option<Box<curveType>>555 fn read_tag_curveType(
556     src: &mut MemSource,
557     index: &TagIndex,
558     tag_id: u32,
559 ) -> Option<Box<curveType>> {
560     let tag = find_tag(index, tag_id);
561     if let Some(tag) = tag {
562         let mut len: u32 = 0;
563         return read_curveType(src, tag.offset, &mut len);
564     } else {
565         invalid_source(src, "missing curvetag");
566     }
567     None
568 }
569 
570 const MAX_LUT_SIZE: u32 = 500000; // arbitrary
571 const MAX_CHANNELS: usize = 10; // arbitrary
read_nested_curveType( src: &mut MemSource, curveArray: &mut [Option<Box<curveType>>; MAX_CHANNELS], num_channels: u8, curve_offset: u32, )572 fn read_nested_curveType(
573     src: &mut MemSource,
574     curveArray: &mut [Option<Box<curveType>>; MAX_CHANNELS],
575     num_channels: u8,
576     curve_offset: u32,
577 ) {
578     let mut channel_offset: u32 = 0;
579     #[allow(clippy::needless_range_loop)]
580     for i in 0..usize::from(num_channels) {
581         let mut tag_len: u32 = 0;
582         curveArray[i] = read_curveType(src, curve_offset + channel_offset, &mut tag_len);
583         if curveArray[i].is_none() {
584             invalid_source(src, "invalid nested curveType curve");
585             break;
586         } else {
587             channel_offset += tag_len;
588             // 4 byte aligned
589             if tag_len % 4 != 0 {
590                 channel_offset += 4 - tag_len % 4
591             }
592         }
593     }
594 }
595 
596 /* See section 10.10 for specs */
read_tag_lutmABType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutmABType>>597 fn read_tag_lutmABType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutmABType>> {
598     let offset: u32 = tag.offset;
599     let mut clut_size: u32 = 1;
600     let type_0: u32 = read_u32(src, offset as usize);
601     if type_0 != LUT_MAB_TYPE && type_0 != LUT_MBA_TYPE {
602         return None;
603     }
604     let num_in_channels = read_u8(src, (offset + 8) as usize);
605     let num_out_channels = read_u8(src, (offset + 9) as usize);
606     if num_in_channels > 10 || num_out_channels > 10 {
607         return None;
608     }
609     // We require 3in/out channels since we only support RGB->XYZ (or RGB->LAB)
610     // XXX: If we remove this restriction make sure that the number of channels
611     //      is less or equal to the maximum number of mAB curves in qcmsint.h
612     //      also check for clut_size overflow. Also make sure it's != 0
613     if num_in_channels != 3 || num_out_channels != 3 {
614         return None;
615     }
616     // some of this data is optional and is denoted by a zero offset
617     // we also use this to track their existance
618     let mut a_curve_offset = read_u32(src, (offset + 28) as usize);
619     let mut clut_offset = read_u32(src, (offset + 24) as usize);
620     let mut m_curve_offset = read_u32(src, (offset + 20) as usize);
621     let mut matrix_offset = read_u32(src, (offset + 16) as usize);
622     let mut b_curve_offset = read_u32(src, (offset + 12) as usize);
623     // Convert offsets relative to the tag to relative to the profile
624     // preserve zero for optional fields
625     if a_curve_offset != 0 {
626         a_curve_offset += offset
627     }
628     if clut_offset != 0 {
629         clut_offset += offset
630     }
631     if m_curve_offset != 0 {
632         m_curve_offset += offset
633     }
634     if matrix_offset != 0 {
635         matrix_offset += offset
636     }
637     if b_curve_offset != 0 {
638         b_curve_offset += offset
639     }
640     if clut_offset != 0 {
641         debug_assert!(num_in_channels == 3);
642         // clut_size can not overflow since lg(256^num_in_channels) = 24 bits.
643         for i in 0..u32::from(num_in_channels) {
644             clut_size *= read_u8(src, (clut_offset + i) as usize) as u32;
645             if clut_size == 0 {
646                 invalid_source(src, "bad clut_size");
647             }
648         }
649     } else {
650         clut_size = 0
651     }
652     // 24bits * 3 won't overflow either
653     clut_size *= num_out_channels as u32;
654     if clut_size > MAX_LUT_SIZE {
655         return None;
656     }
657 
658     let mut lut = Box::new(lutmABType::default());
659 
660     if clut_offset != 0 {
661         for i in 0..usize::from(num_in_channels) {
662             lut.num_grid_points[i] = read_u8(src, clut_offset as usize + i);
663             if lut.num_grid_points[i] == 0 {
664                 invalid_source(src, "bad grid_points");
665             }
666         }
667     }
668     // Reverse the processing of transformation elements for mBA type.
669     lut.reversed = type_0 == LUT_MBA_TYPE;
670     lut.num_in_channels = num_in_channels;
671     lut.num_out_channels = num_out_channels;
672     #[allow(clippy::identity_op, clippy::erasing_op)]
673     if matrix_offset != 0 {
674         // read the matrix if we have it
675         lut.e00 = read_s15Fixed16Number(src, (matrix_offset + (4 * 0) as u32) as usize); // the caller checks that this doesn't happen
676         lut.e01 = read_s15Fixed16Number(src, (matrix_offset + (4 * 1) as u32) as usize);
677         lut.e02 = read_s15Fixed16Number(src, (matrix_offset + (4 * 2) as u32) as usize);
678         lut.e10 = read_s15Fixed16Number(src, (matrix_offset + (4 * 3) as u32) as usize);
679         lut.e11 = read_s15Fixed16Number(src, (matrix_offset + (4 * 4) as u32) as usize);
680         lut.e12 = read_s15Fixed16Number(src, (matrix_offset + (4 * 5) as u32) as usize);
681         lut.e20 = read_s15Fixed16Number(src, (matrix_offset + (4 * 6) as u32) as usize);
682         lut.e21 = read_s15Fixed16Number(src, (matrix_offset + (4 * 7) as u32) as usize);
683         lut.e22 = read_s15Fixed16Number(src, (matrix_offset + (4 * 8) as u32) as usize);
684         lut.e03 = read_s15Fixed16Number(src, (matrix_offset + (4 * 9) as u32) as usize);
685         lut.e13 = read_s15Fixed16Number(src, (matrix_offset + (4 * 10) as u32) as usize);
686         lut.e23 = read_s15Fixed16Number(src, (matrix_offset + (4 * 11) as u32) as usize)
687     }
688     if a_curve_offset != 0 {
689         read_nested_curveType(src, &mut lut.a_curves, num_in_channels, a_curve_offset);
690     }
691     if m_curve_offset != 0 {
692         read_nested_curveType(src, &mut lut.m_curves, num_out_channels, m_curve_offset);
693     }
694     if b_curve_offset != 0 {
695         read_nested_curveType(src, &mut lut.b_curves, num_out_channels, b_curve_offset);
696     } else {
697         invalid_source(src, "B curves required");
698     }
699     if clut_offset != 0 {
700         let clut_precision = read_u8(src, (clut_offset + 16) as usize);
701         let mut clut_table = Vec::with_capacity(clut_size as usize);
702         if clut_precision == 1 {
703             for i in 0..clut_size {
704                 clut_table.push(uInt8Number_to_float(read_uInt8Number(
705                     src,
706                     (clut_offset + 20 + i) as usize,
707                 )));
708             }
709             lut.clut_table = Some(clut_table);
710         } else if clut_precision == 2 {
711             for i in 0..clut_size {
712                 clut_table.push(uInt16Number_to_float(read_uInt16Number(
713                     src,
714                     (clut_offset + 20 + i * 2) as usize,
715                 )));
716             }
717             lut.clut_table = Some(clut_table);
718         } else {
719             invalid_source(src, "Invalid clut precision");
720         }
721     }
722     if !src.valid {
723         return None;
724     }
725     Some(lut)
726 }
read_tag_lutType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutType>>727 fn read_tag_lutType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutType>> {
728     let offset: u32 = tag.offset;
729     let type_0: u32 = read_u32(src, offset as usize);
730     let num_input_table_entries: u16;
731     let num_output_table_entries: u16;
732     let input_offset: u32;
733     let entry_size: usize;
734     if type_0 == LUT8_TYPE {
735         num_input_table_entries = 256u16;
736         num_output_table_entries = 256u16;
737         entry_size = 1;
738         input_offset = 48
739     } else if type_0 == LUT16_TYPE {
740         num_input_table_entries = read_u16(src, (offset + 48) as usize);
741         num_output_table_entries = read_u16(src, (offset + 50) as usize);
742 
743         // these limits come from the spec
744         if !(2..=4096).contains(&num_input_table_entries)
745             || !(2..=4096).contains(&num_output_table_entries)
746         {
747             invalid_source(src, "Bad channel count");
748             return None;
749         }
750         entry_size = 2;
751         input_offset = 52
752     } else {
753         debug_assert!(false);
754         invalid_source(src, "Unexpected lut type");
755         return None;
756     }
757     let in_chan = read_u8(src, (offset + 8) as usize);
758     let out_chan = read_u8(src, (offset + 9) as usize);
759     if !(in_chan == 3 || in_chan == 4) || out_chan != 3 {
760         invalid_source(src, "CLUT only supports RGB and CMYK");
761         return None;
762     }
763 
764     let grid_points = read_u8(src, (offset + 10) as usize);
765     let clut_size = match (grid_points as u32).checked_pow(in_chan as u32) {
766         Some(clut_size) => clut_size,
767         _ => {
768             invalid_source(src, "CLUT size overflow");
769             return None;
770         }
771     };
772     match clut_size {
773         1..=MAX_LUT_SIZE => {} // OK
774         0 => {
775             invalid_source(src, "CLUT must not be empty.");
776             return None;
777         }
778         _ => {
779             invalid_source(src, "CLUT too large");
780             return None;
781         }
782     }
783 
784     let e00 = read_s15Fixed16Number(src, (offset + 12) as usize);
785     let e01 = read_s15Fixed16Number(src, (offset + 16) as usize);
786     let e02 = read_s15Fixed16Number(src, (offset + 20) as usize);
787     let e10 = read_s15Fixed16Number(src, (offset + 24) as usize);
788     let e11 = read_s15Fixed16Number(src, (offset + 28) as usize);
789     let e12 = read_s15Fixed16Number(src, (offset + 32) as usize);
790     let e20 = read_s15Fixed16Number(src, (offset + 36) as usize);
791     let e21 = read_s15Fixed16Number(src, (offset + 40) as usize);
792     let e22 = read_s15Fixed16Number(src, (offset + 44) as usize);
793 
794     let mut input_table = Vec::with_capacity((num_input_table_entries * in_chan as u16) as usize);
795     for i in 0..(num_input_table_entries * in_chan as u16) {
796         if type_0 == LUT8_TYPE {
797             input_table.push(uInt8Number_to_float(read_uInt8Number(
798                 src,
799                 (offset + input_offset) as usize + i as usize * entry_size,
800             )))
801         } else {
802             input_table.push(uInt16Number_to_float(read_uInt16Number(
803                 src,
804                 (offset + input_offset) as usize + i as usize * entry_size,
805             )))
806         }
807     }
808     let clut_offset = ((offset + input_offset) as usize
809         + (num_input_table_entries as i32 * in_chan as i32) as usize * entry_size)
810         as u32;
811 
812     let mut clut_table = Vec::with_capacity((clut_size * out_chan as u32) as usize);
813     for i in 0..clut_size * out_chan as u32 {
814         if type_0 == LUT8_TYPE {
815             clut_table.push(uInt8Number_to_float(read_uInt8Number(
816                 src,
817                 clut_offset as usize + i as usize * entry_size,
818             )));
819         } else if type_0 == LUT16_TYPE {
820             clut_table.push(uInt16Number_to_float(read_uInt16Number(
821                 src,
822                 clut_offset as usize + i as usize * entry_size,
823             )));
824         }
825     }
826 
827     let output_offset =
828         (clut_offset as usize + (clut_size * out_chan as u32) as usize * entry_size) as u32;
829 
830     let mut output_table =
831         Vec::with_capacity((num_output_table_entries * out_chan as u16) as usize);
832     for i in 0..num_output_table_entries as i32 * out_chan as i32 {
833         if type_0 == LUT8_TYPE {
834             output_table.push(uInt8Number_to_float(read_uInt8Number(
835                 src,
836                 output_offset as usize + i as usize * entry_size,
837             )))
838         } else {
839             output_table.push(uInt16Number_to_float(read_uInt16Number(
840                 src,
841                 output_offset as usize + i as usize * entry_size,
842             )))
843         }
844     }
845     Some(Box::new(lutType {
846         num_input_table_entries,
847         num_output_table_entries,
848         num_input_channels: in_chan,
849         num_output_channels: out_chan,
850         num_clut_grid_points: grid_points,
851         e00,
852         e01,
853         e02,
854         e10,
855         e11,
856         e12,
857         e20,
858         e21,
859         e22,
860         input_table,
861         clut_table,
862         output_table,
863     }))
864 }
read_rendering_intent(mut profile: &mut Profile, src: &mut MemSource)865 fn read_rendering_intent(mut profile: &mut Profile, src: &mut MemSource) {
866     let intent = read_u32(src, 64);
867     profile.rendering_intent = match intent {
868         x if x == Perceptual as u32 => Perceptual,
869         x if x == RelativeColorimetric as u32 => RelativeColorimetric,
870         x if x == Saturation as u32 => Saturation,
871         x if x == AbsoluteColorimetric as u32 => AbsoluteColorimetric,
872         _ => {
873             invalid_source(src, "unknown rendering intent");
874             Intent::default()
875         }
876     };
877 }
profile_create() -> Box<Profile>878 fn profile_create() -> Box<Profile> {
879     Box::new(Profile::default())
880 }
881 /* build sRGB gamma table */
882 /* based on cmsBuildParametricGamma() */
883 #[allow(clippy::many_single_char_names)]
build_sRGB_gamma_table(num_entries: i32) -> Vec<u16>884 fn build_sRGB_gamma_table(num_entries: i32) -> Vec<u16> {
885     /* taken from lcms: Build_sRGBGamma() */
886     let gamma: f64 = 2.4;
887     let a: f64 = 1.0 / 1.055;
888     let b: f64 = 0.055 / 1.055;
889     let c: f64 = 1.0 / 12.92;
890     let d: f64 = 0.04045;
891 
892     build_trc_table(
893         num_entries,
894         // IEC 61966-2.1 (sRGB)
895         // Y = (aX + b)^Gamma | X >= d
896         // Y = cX             | X < d
897         |x| {
898             if x >= d {
899                 let e: f64 = a * x + b;
900                 if e > 0. {
901                     e.powf(gamma)
902                 } else {
903                     0.
904                 }
905             } else {
906                 c * x
907             }
908         },
909     )
910 }
911 
912 /// eotf: electro-optical transfer characteristic function, maps from [0, 1]
913 /// in non-linear (voltage) space to [0, 1] in linear (optical) space. Should
914 /// generally be a concave up function.
build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16>915 fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> {
916     let mut table = Vec::with_capacity(num_entries as usize);
917 
918     for i in 0..num_entries {
919         let x: f64 = i as f64 / (num_entries - 1) as f64;
920         let y: f64 = eotf(x);
921         let mut output: f64;
922         // Saturate -- this could likely move to a separate function
923         output = y * 65535.0 + 0.5;
924         if output > 65535.0 {
925             output = 65535.0
926         }
927         if output < 0.0 {
928             output = 0.0
929         }
930         table.push(output.floor() as u16);
931     }
932     table
933 }
curve_from_table(table: &[u16]) -> Box<curveType>934 fn curve_from_table(table: &[u16]) -> Box<curveType> {
935     Box::new(curveType::Curve(table.to_vec()))
936 }
float_to_u8Fixed8Number(a: f32) -> u16937 pub fn float_to_u8Fixed8Number(a: f32) -> u16 {
938     if a > 255.0 + 255.0 / 256f32 {
939         0xffffu16
940     } else if a < 0.0 {
941         0u16
942     } else {
943         (a * 256.0 + 0.5).floor() as u16
944     }
945 }
946 
curve_from_gamma(gamma: f32) -> Box<curveType>947 fn curve_from_gamma(gamma: f32) -> Box<curveType> {
948     Box::new(curveType::Curve(vec![float_to_u8Fixed8Number(gamma)]))
949 }
950 
identity_curve() -> Box<curveType>951 fn identity_curve() -> Box<curveType> {
952     Box::new(curveType::Curve(Vec::new()))
953 }
954 
955 /* from lcms: cmsWhitePointFromTemp */
956 /* tempK must be >= 4000. and <= 25000.
957  * Invalid values of tempK will return
958  * (x,y,Y) = (-1.0, -1.0, -1.0)
959  * similar to argyll: icx_DTEMP2XYZ() */
white_point_from_temp(temp_K: i32) -> qcms_CIE_xyY960 fn white_point_from_temp(temp_K: i32) -> qcms_CIE_xyY {
961     let mut white_point: qcms_CIE_xyY = qcms_CIE_xyY {
962         x: 0.,
963         y: 0.,
964         Y: 0.,
965     };
966     // No optimization provided.
967     let T = temp_K as f64; // Square
968     let T2 = T * T; // Cube
969     let T3 = T2 * T;
970     // For correlated color temperature (T) between 4000K and 7000K:
971     let x = if (4000.0..=7000.0).contains(&T) {
972         -4.6070 * (1E9 / T3) + 2.9678 * (1E6 / T2) + 0.09911 * (1E3 / T) + 0.244063
973     } else if T > 7000.0 && T <= 25000.0 {
974         -2.0064 * (1E9 / T3) + 1.9018 * (1E6 / T2) + 0.24748 * (1E3 / T) + 0.237040
975     } else {
976         // or for correlated color temperature (T) between 7000K and 25000K:
977         // Invalid tempK
978         white_point.x = -1.0;
979         white_point.y = -1.0;
980         white_point.Y = -1.0;
981         debug_assert!(false, "invalid temp");
982         return white_point;
983     };
984     // Obtain y(x)
985     let y = -3.000 * (x * x) + 2.870 * x - 0.275;
986     // wave factors (not used, but here for futures extensions)
987     // let M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y);
988     // let M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y);
989     // Fill white_point struct
990     white_point.x = x;
991     white_point.y = y;
992     white_point.Y = 1.0;
993     white_point
994 }
995 #[no_mangle]
qcms_white_point_sRGB() -> qcms_CIE_xyY996 pub extern "C" fn qcms_white_point_sRGB() -> qcms_CIE_xyY {
997     white_point_from_temp(6504)
998 }
999 
1000 /// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 2
1001 /// Values 0, 3, 13–21, 23–255 are all reserved so all map to the same variant
1002 #[derive(Clone, Copy, Debug, PartialEq)]
1003 pub enum ColourPrimaries {
1004     /// For future use by ITU-T | ISO/IEC
1005     Reserved,
1006     /// Rec. ITU-R BT.709-6<br />
1007     /// Rec. ITU-R BT.1361-0 conventional colour gamut system and extended colour gamut system (historical)<br />
1008     /// IEC 61966-2-1 sRGB or sYCC IEC 61966-2-4<br />
1009     /// Society of Motion Picture and Television Engineers (MPTE) RP 177 (1993) Annex B<br />
1010     Bt709 = 1,
1011     /// Unspecified<br />
1012     /// Image characteristics are unknown or are determined by the application.
1013     Unspecified = 2,
1014     /// Rec. ITU-R BT.470-6 System M (historical)<br />
1015     /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
1016     /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
1017     Bt470M = 4,
1018     /// Rec. ITU-R BT.470-6 System B, G (historical) Rec. ITU-R BT.601-7 625<br />
1019     /// Rec. ITU-R BT.1358-0 625 (historical)<br />
1020     /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
1021     Bt470Bg = 5,
1022     /// Rec. ITU-R BT.601-7 525<br />
1023     /// Rec. ITU-R BT.1358-1 525 or 625 (historical) Rec. ITU-R BT.1700-0 NTSC<br />
1024     /// SMPTE 170M (2004)<br />
1025     /// (functionally the same as the value 7)<br />
1026     Bt601 = 6,
1027     /// SMPTE 240M (1999) (historical) (functionally the same as the value 6)<br />
1028     Smpte240 = 7,
1029     /// Generic film (colour filters using Illuminant C)<br />
1030     Generic_film = 8,
1031     /// Rec. ITU-R BT.2020-2<br />
1032     /// Rec. ITU-R BT.2100-0<br />
1033     Bt2020 = 9,
1034     /// SMPTE ST 428-1<br />
1035     /// (CIE 1931 XYZ as in ISO 11664-1)<br />
1036     Xyz = 10,
1037     /// SMPTE RP 431-2 (2011)<br />
1038     Smpte431 = 11,
1039     /// SMPTE EG 432-1 (2010)<br />
1040     Smpte432 = 12,
1041     /// EBU Tech. 3213-E (1975)<br />
1042     Ebu3213 = 22,
1043 }
1044 
1045 impl From<u8> for ColourPrimaries {
from(value: u8) -> Self1046     fn from(value: u8) -> Self {
1047         match value {
1048             0 | 3 | 13..=21 | 23..=255 => Self::Reserved,
1049             1 => Self::Bt709,
1050             2 => Self::Unspecified,
1051             4 => Self::Bt470M,
1052             5 => Self::Bt470Bg,
1053             6 => Self::Bt601,
1054             7 => Self::Smpte240,
1055             8 => Self::Generic_film,
1056             9 => Self::Bt2020,
1057             10 => Self::Xyz,
1058             11 => Self::Smpte431,
1059             12 => Self::Smpte432,
1060             22 => Self::Ebu3213,
1061         }
1062     }
1063 }
1064 
1065 #[test]
colour_primaries()1066 fn colour_primaries() {
1067     for value in 0..=u8::MAX {
1068         match ColourPrimaries::from(value) {
1069             ColourPrimaries::Reserved => {}
1070             variant => assert_eq!(value, variant as u8),
1071         }
1072     }
1073 }
1074 
1075 impl From<ColourPrimaries> for qcms_CIE_xyYTRIPLE {
from(value: ColourPrimaries) -> Self1076     fn from(value: ColourPrimaries) -> Self {
1077         let red;
1078         let green;
1079         let blue;
1080 
1081         match value {
1082             ColourPrimaries::Reserved => panic!("CP={} is reserved", value as u8),
1083             ColourPrimaries::Bt709 => {
1084                 green = qcms_chromaticity { x: 0.300, y: 0.600 };
1085                 blue = qcms_chromaticity { x: 0.150, y: 0.060 };
1086                 red = qcms_chromaticity { x: 0.640, y: 0.330 };
1087             }
1088             ColourPrimaries::Unspecified => panic!("CP={} is unspecified", value as u8),
1089             ColourPrimaries::Bt470M => {
1090                 green = qcms_chromaticity { x: 0.21, y: 0.71 };
1091                 blue = qcms_chromaticity { x: 0.14, y: 0.08 };
1092                 red = qcms_chromaticity { x: 0.67, y: 0.33 };
1093             }
1094             ColourPrimaries::Bt470Bg => {
1095                 green = qcms_chromaticity { x: 0.29, y: 0.60 };
1096                 blue = qcms_chromaticity { x: 0.15, y: 0.06 };
1097                 red = qcms_chromaticity { x: 0.64, y: 0.33 };
1098             }
1099             ColourPrimaries::Bt601 | ColourPrimaries::Smpte240 => {
1100                 green = qcms_chromaticity { x: 0.310, y: 0.595 };
1101                 blue = qcms_chromaticity { x: 0.155, y: 0.070 };
1102                 red = qcms_chromaticity { x: 0.630, y: 0.340 };
1103             }
1104             ColourPrimaries::Generic_film => {
1105                 green = qcms_chromaticity { x: 0.243, y: 0.692 };
1106                 blue = qcms_chromaticity { x: 0.145, y: 0.049 };
1107                 red = qcms_chromaticity { x: 0.681, y: 0.319 };
1108             }
1109             ColourPrimaries::Bt2020 => {
1110                 green = qcms_chromaticity { x: 0.170, y: 0.797 };
1111                 blue = qcms_chromaticity { x: 0.131, y: 0.046 };
1112                 red = qcms_chromaticity { x: 0.708, y: 0.292 };
1113             }
1114             ColourPrimaries::Xyz => {
1115                 green = qcms_chromaticity { x: 0.0, y: 1.0 };
1116                 blue = qcms_chromaticity { x: 0.0, y: 0.0 };
1117                 red = qcms_chromaticity { x: 1.0, y: 0.0 };
1118             }
1119             // These two share primaries, but have distinct white points
1120             ColourPrimaries::Smpte431 | ColourPrimaries::Smpte432 => {
1121                 green = qcms_chromaticity { x: 0.265, y: 0.690 };
1122                 blue = qcms_chromaticity { x: 0.150, y: 0.060 };
1123                 red = qcms_chromaticity { x: 0.680, y: 0.320 };
1124             }
1125             ColourPrimaries::Ebu3213 => {
1126                 green = qcms_chromaticity { x: 0.295, y: 0.605 };
1127                 blue = qcms_chromaticity { x: 0.155, y: 0.077 };
1128                 red = qcms_chromaticity { x: 0.630, y: 0.340 };
1129             }
1130         }
1131 
1132         Self {
1133             red: red.into(),
1134             green: green.into(),
1135             blue: blue.into(),
1136         }
1137     }
1138 }
1139 
1140 impl ColourPrimaries {
white_point(self) -> qcms_CIE_xyY1141     fn white_point(self) -> qcms_CIE_xyY {
1142         match self {
1143             Self::Reserved => panic!("CP={} is reserved", self as u8),
1144             Self::Bt709
1145             | Self::Bt470Bg
1146             | Self::Bt601
1147             | Self::Smpte240
1148             | Self::Bt2020
1149             | Self::Smpte432
1150             | Self::Ebu3213 => qcms_chromaticity::D65,
1151             Self::Unspecified => panic!("CP={} is unspecified", self as u8),
1152             Self::Bt470M => qcms_chromaticity { x: 0.310, y: 0.316 },
1153             Self::Generic_film => qcms_chromaticity { x: 0.310, y: 0.316 },
1154             Self::Xyz => qcms_chromaticity {
1155                 x: 1. / 3.,
1156                 y: 1. / 3.,
1157             },
1158             Self::Smpte431 => qcms_chromaticity { x: 0.314, y: 0.351 },
1159         }
1160         .into()
1161     }
1162 }
1163 
1164 /// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 3
1165 /// Values 0, 3, 19–255 are all reserved so all map to the same variant
1166 #[derive(Clone, Copy, Debug, PartialEq)]
1167 pub enum TransferCharacteristics {
1168     /// For future use by ITU-T | ISO/IEC
1169     Reserved,
1170     /// Rec. ITU-R BT.709-6<br />
1171     /// Rec. ITU-R BT.1361-0 conventional colour gamut system (historical)<br />
1172     /// (functionally the same as the values 6, 14 and 15)    <br />
1173     Bt709 = 1,
1174     /// Image characteristics are unknown or are determined by the application.<br />
1175     Unspecified = 2,
1176     /// Rec. ITU-R BT.470-6 System M (historical)<br />
1177     /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
1178     /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
1179     /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
1180     Bt470M = 4,
1181     /// Rec. ITU-R BT.470-6 System B, G (historical)<br />
1182     Bt470Bg = 5,
1183     /// Rec. ITU-R BT.601-7 525 or 625<br />
1184     /// Rec. ITU-R BT.1358-1 525 or 625 (historical)<br />
1185     /// Rec. ITU-R BT.1700-0 NTSC SMPTE 170M (2004)<br />
1186     /// (functionally the same as the values 1, 14 and 15)<br />
1187     Bt601 = 6,
1188     /// SMPTE 240M (1999) (historical)<br />
1189     Smpte240 = 7,
1190     /// Linear transfer characteristics<br />
1191     Linear = 8,
1192     /// Logarithmic transfer characteristic (100:1 range)<br />
1193     Log_100 = 9,
1194     /// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range)<br />
1195     Log_100_sqrt10 = 10,
1196     /// IEC 61966-2-4<br />
1197     Iec61966 = 11,
1198     /// Rec. ITU-R BT.1361-0 extended colour gamut system (historical)<br />
1199     Bt_1361 = 12,
1200     /// IEC 61966-2-1 sRGB or sYCC<br />
1201     Srgb = 13,
1202     /// Rec. ITU-R BT.2020-2 (10-bit system)<br />
1203     /// (functionally the same as the values 1, 6 and 15)<br />
1204     Bt2020_10bit = 14,
1205     /// Rec. ITU-R BT.2020-2 (12-bit system)<br />
1206     /// (functionally the same as the values 1, 6 and 14)<br />
1207     Bt2020_12bit = 15,
1208     /// SMPTE ST 2084 for 10-, 12-, 14- and 16-bitsystems<br />
1209     /// Rec. ITU-R BT.2100-0 perceptual quantization (PQ) system<br />
1210     Smpte2084 = 16,
1211     /// SMPTE ST 428-1<br />
1212     Smpte428 = 17,
1213     /// ARIB STD-B67<br />
1214     /// Rec. ITU-R BT.2100-0 hybrid log- gamma (HLG) system<br />
1215     Hlg = 18,
1216 }
1217 
1218 #[test]
transfer_characteristics()1219 fn transfer_characteristics() {
1220     for value in 0..=u8::MAX {
1221         match TransferCharacteristics::from(value) {
1222             TransferCharacteristics::Reserved => {}
1223             variant => assert_eq!(value, variant as u8),
1224         }
1225     }
1226 }
1227 
1228 impl From<u8> for TransferCharacteristics {
from(value: u8) -> Self1229     fn from(value: u8) -> Self {
1230         match value {
1231             0 | 3 | 19..=255 => Self::Reserved,
1232             1 => Self::Bt709,
1233             2 => Self::Unspecified,
1234             4 => Self::Bt470M,
1235             5 => Self::Bt470Bg,
1236             6 => Self::Bt601,
1237             7 => Self::Smpte240, // unimplemented
1238             8 => Self::Linear,
1239             9 => Self::Log_100,
1240             10 => Self::Log_100_sqrt10,
1241             11 => Self::Iec61966, // unimplemented
1242             12 => Self::Bt_1361,  // unimplemented
1243             13 => Self::Srgb,
1244             14 => Self::Bt2020_10bit,
1245             15 => Self::Bt2020_12bit,
1246             16 => Self::Smpte2084,
1247             17 => Self::Smpte428, // unimplemented
1248             18 => Self::Hlg,
1249         }
1250     }
1251 }
1252 
1253 impl From<TransferCharacteristics> for curveType {
1254     /// See [ICC.1:2010](https://www.color.org/specification/ICC1v43_2010-12.pdf)
1255     /// See [Rec. ITU-R BT.2100-2](https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf)
from(value: TransferCharacteristics) -> Self1256     fn from(value: TransferCharacteristics) -> Self {
1257         const NUM_TRC_TABLE_ENTRIES: i32 = 1024;
1258 
1259         match value {
1260             TransferCharacteristics::Reserved => panic!("TC={} is reserved", value as u8),
1261             TransferCharacteristics::Bt709
1262             | TransferCharacteristics::Bt601
1263             | TransferCharacteristics::Bt2020_10bit
1264             | TransferCharacteristics::Bt2020_12bit => {
1265                 // The opto-electronic transfer characteristic function (OETF)
1266                 // as defined in ITU-T H.273 table 3, row 1:
1267                 //
1268                 // V = (α * Lc^0.45) − (α − 1)  for 1 >= Lc >= β
1269                 // V = 4.500 * Lc               for β >  Lc >= 0
1270                 //
1271                 // Inverting gives the electro-optical transfer characteristic
1272                 // function (EOTF) which can be represented as ICC
1273                 // parametricCurveType with 4 parameters (ICC.1:2010 Table 5).
1274                 // Converting between the two (Lc ↔︎ Y, V ↔︎ X):
1275                 //
1276                 // Y = (a * X + b)^g  for (X >= d)
1277                 // Y = c * X          for (X < d)
1278                 //
1279                 // g, a, b, c, d can then be defined in terms of α and β:
1280                 //
1281                 // g = 1 / 0.45
1282                 // a = 1 / α
1283                 // b = 1 - α
1284                 // c = 1 / 4.500
1285                 // d = 4.500 * β
1286                 //
1287                 // α and β are determined by solving the piecewise equations to
1288                 // ensure continuity of both value and slope at the value β.
1289                 // We use the values specified for 10-bit systems in
1290                 // https://www.itu.int/rec/R-REC-BT.2020-2-201510-I Table 4
1291                 // since this results in the similar values as available ICC
1292                 // profiles after converting to s15Fixed16Number, providing us
1293                 // good test coverage.
1294 
1295                 type Float = f32;
1296 
1297                 const alpha: Float = 1.099;
1298                 const beta: Float = 0.018;
1299 
1300                 const linear_coef: Float = 4.500;
1301                 const pow_exp: Float = 0.45;
1302 
1303                 const g: Float = 1. / pow_exp;
1304                 const a: Float = 1. / alpha;
1305                 const b: Float = 1. - a;
1306                 const c: Float = 1. / linear_coef;
1307                 const d: Float = linear_coef * beta;
1308 
1309                 curveType::Parametric(vec![g, a, b, c, d])
1310             }
1311             TransferCharacteristics::Unspecified => panic!("TC={} is unspecified", value as u8),
1312             TransferCharacteristics::Bt470M => *curve_from_gamma(2.2),
1313             TransferCharacteristics::Bt470Bg => *curve_from_gamma(2.8),
1314             TransferCharacteristics::Smpte240 => unimplemented!(),
1315             TransferCharacteristics::Linear => *curve_from_gamma(1.),
1316             TransferCharacteristics::Log_100 => {
1317                 // See log_100_transfer_characteristics() for derivation
1318                 // The opto-electronic transfer characteristic function (OETF)
1319                 // as defined in ITU-T H.273 table 3, row 9:
1320                 //
1321                 // V = 1.0 + Log10(Lc) ÷ 2  for 1    >= Lc >= 0.01
1322                 // V = 0.0                  for 0.01 >  Lc >= 0
1323                 //
1324                 // Inverting this to give the EOTF required for the profile gives
1325                 //
1326                 // Lc = 10^(2*V - 2)  for 1 >= V >= 0
1327                 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2. * v - 2.));
1328                 curveType::Curve(table)
1329             }
1330             TransferCharacteristics::Log_100_sqrt10 => {
1331                 // The opto-electronic transfer characteristic function (OETF)
1332                 // as defined in ITU-T H.273 table 3, row 10:
1333                 //
1334                 // V = 1.0 + Log10(Lc) ÷ 2.5  for               1 >= Lc >= Sqrt(10) ÷ 1000
1335                 // V = 0.0                    for Sqrt(10) ÷ 1000 >  Lc >= 0
1336                 //
1337                 // Inverting this to give the EOTF required for the profile gives
1338                 //
1339                 // Lc = 10^(2.5*V - 2.5)  for 1 >= V >= 0
1340                 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2.5 * v - 2.5));
1341                 curveType::Curve(table)
1342             }
1343             TransferCharacteristics::Iec61966 => unimplemented!(),
1344             TransferCharacteristics::Bt_1361 => unimplemented!(),
1345             TransferCharacteristics::Srgb => {
1346                 // Should we prefer this or curveType::Parametric?
1347                 curveType::Curve(build_sRGB_gamma_table(NUM_TRC_TABLE_ENTRIES))
1348             }
1349 
1350             TransferCharacteristics::Smpte2084 => {
1351                 // Despite using Lo rather than Lc, H.273 gives the OETF:
1352                 //
1353                 // V = ( ( c1 + c2 * (Lo)^n ) ÷ ( 1 + c3 * (Lo)^n ) )^m
1354                 const c1: f64 = 0.8359375;
1355                 const c2: f64 = 18.8515625;
1356                 const c3: f64 = 18.6875;
1357                 const m: f64 = 78.84375;
1358                 const n: f64 = 0.1593017578125;
1359 
1360                 // Inverting this to give the EOTF required for the profile
1361                 // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives
1362                 //
1363                 // Y = ( max[( X^(1/m) - c1 ), 0] ÷ ( c2 - c3 * X^(1/m) ) )^(1/n)
1364                 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| {
1365                     ((x.powf(1. / m) - c1).max(0.) / (c2 - c3 * x.powf(1. / m))).powf(1. / n)
1366                 });
1367                 curveType::Curve(table)
1368             }
1369             TransferCharacteristics::Smpte428 => unimplemented!(),
1370             TransferCharacteristics::Hlg => {
1371                 // The opto-electronic transfer characteristic function (OETF)
1372                 // as defined in ITU-T H.273 table 3, row 18:
1373                 //
1374                 // V = a * Ln(12 * Lc - b) + c  for 1      >= Lc >  1 ÷ 12
1375                 // V = Sqrt(3) * Lc^0.5         for 1 ÷ 12 >= Lc >= 0
1376                 const a: f64 = 0.17883277;
1377                 const b: f64 = 0.28466892;
1378                 const c: f64 = 0.55991073;
1379 
1380                 // Inverting this to give the EOTF required for the profile
1381                 // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives
1382                 //
1383                 // Y = (X^2) / 3             for 0   <= X <= 0.5
1384                 // Y = ((e^((X-c)/a))+b)/12  for 0.5 <  X <= 1
1385                 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| {
1386                     if x <= 0.5 {
1387                         let y1 = x.powf(2.) / 3.;
1388                         assert!((0. ..=1. / 12.).contains(&y1));
1389                         y1
1390                     } else {
1391                         (std::f64::consts::E.powf((x - c) / a) + b) / 12.
1392                     }
1393                 });
1394                 curveType::Curve(table)
1395             }
1396         }
1397     }
1398 }
1399 
1400 #[cfg(test)]
check_transfer_characteristics(cicp: TransferCharacteristics, icc_path: &str)1401 fn check_transfer_characteristics(cicp: TransferCharacteristics, icc_path: &str) {
1402     let mut cicp_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE];
1403     let mut icc_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE];
1404     let cicp_tc = curveType::from(cicp);
1405     let icc = Profile::new_from_path(icc_path).unwrap();
1406     let icc_tc = icc.redTRC.as_ref().unwrap();
1407 
1408     eprintln!("cicp_tc: {:?}", cicp_tc);
1409     eprintln!("icc_tc: {:?}", icc_tc);
1410 
1411     crate::transform_util::compute_precache(icc_tc, &mut icc_out);
1412     crate::transform_util::compute_precache(&cicp_tc, &mut cicp_out);
1413 
1414     let mut off_by_one = 0;
1415     for i in 0..cicp_out.len() {
1416         match (cicp_out[i] as i16) - (icc_out[i] as i16) {
1417             0 => {}
1418             1 | -1 => {
1419                 off_by_one += 1;
1420             }
1421             _ => assert_eq!(cicp_out[i], icc_out[i], "difference at index {}", i),
1422         }
1423     }
1424     eprintln!("{} / {} off by one", off_by_one, cicp_out.len());
1425 }
1426 
1427 #[test]
srgb_transfer_characteristics()1428 fn srgb_transfer_characteristics() {
1429     check_transfer_characteristics(TransferCharacteristics::Srgb, "sRGB_lcms.icc");
1430 }
1431 
1432 #[test]
bt709_transfer_characteristics()1433 fn bt709_transfer_characteristics() {
1434     check_transfer_characteristics(TransferCharacteristics::Bt709, "ITU-709.icc");
1435 }
1436 
1437 #[test]
bt2020_10bit_transfer_characteristics()1438 fn bt2020_10bit_transfer_characteristics() {
1439     check_transfer_characteristics(TransferCharacteristics::Bt2020_10bit, "ITU-2020.icc");
1440 }
1441 
1442 #[test]
bt2020_12bit_transfer_characteristics()1443 fn bt2020_12bit_transfer_characteristics() {
1444     check_transfer_characteristics(TransferCharacteristics::Bt2020_12bit, "ITU-2020.icc");
1445 }
1446 
1447 impl Profile {
1448     //XXX: it would be nice if we had a way of ensuring
1449     // everything in a profile was initialized regardless of how it was created
1450     //XXX: should this also be taking a black_point?
1451     /* similar to CGColorSpaceCreateCalibratedRGB */
new_rgb_with_table( white_point: qcms_CIE_xyY, primaries: qcms_CIE_xyYTRIPLE, table: &[u16], ) -> Option<Box<Profile>>1452     pub fn new_rgb_with_table(
1453         white_point: qcms_CIE_xyY,
1454         primaries: qcms_CIE_xyYTRIPLE,
1455         table: &[u16],
1456     ) -> Option<Box<Profile>> {
1457         let mut profile = profile_create();
1458         //XXX: should store the whitepoint
1459         if !set_rgb_colorants(&mut profile, white_point, primaries) {
1460             return None;
1461         }
1462         profile.redTRC = Some(curve_from_table(table));
1463         profile.blueTRC = Some(curve_from_table(table));
1464         profile.greenTRC = Some(curve_from_table(table));
1465         profile.class_type = DISPLAY_DEVICE_PROFILE;
1466         profile.rendering_intent = Perceptual;
1467         profile.color_space = RGB_SIGNATURE;
1468         profile.pcs = XYZ_TYPE;
1469         Some(profile)
1470     }
new_sRGB() -> Box<Profile>1471     pub fn new_sRGB() -> Box<Profile> {
1472         let D65 = qcms_white_point_sRGB();
1473         let table = build_sRGB_gamma_table(1024);
1474 
1475         let mut srgb = Profile::new_rgb_with_table(
1476             D65,
1477             qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709),
1478             &table,
1479         )
1480         .unwrap();
1481         srgb.is_srgb = true;
1482         srgb
1483     }
1484 
1485     /// Returns true if this profile is sRGB
is_sRGB(&self) -> bool1486     pub fn is_sRGB(&self) -> bool {
1487         self.is_srgb
1488     }
1489 
new_sRGB_parametric() -> Box<Profile>1490     pub(crate) fn new_sRGB_parametric() -> Box<Profile> {
1491         let primaries = qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709);
1492         let white_point = qcms_white_point_sRGB();
1493         let mut profile = profile_create();
1494         set_rgb_colorants(&mut profile, white_point, primaries);
1495 
1496         let curve = Box::new(curveType::Parametric(vec![
1497             2.4,
1498             1. / 1.055,
1499             0.055 / 1.055,
1500             1. / 12.92,
1501             0.04045,
1502         ]));
1503         profile.redTRC = Some(curve.clone());
1504         profile.blueTRC = Some(curve.clone());
1505         profile.greenTRC = Some(curve);
1506         profile.class_type = DISPLAY_DEVICE_PROFILE;
1507         profile.rendering_intent = Perceptual;
1508         profile.color_space = RGB_SIGNATURE;
1509         profile.pcs = XYZ_TYPE;
1510         profile.is_srgb = true;
1511         profile
1512     }
1513 
1514     /// Create a new profile with D50 adopted white and identity transform functions
new_XYZD50() -> Box<Profile>1515     pub fn new_XYZD50() -> Box<Profile> {
1516         let mut profile = profile_create();
1517         profile.redColorant.X = double_to_s15Fixed16Number(1.);
1518         profile.redColorant.Y = double_to_s15Fixed16Number(0.);
1519         profile.redColorant.Z = double_to_s15Fixed16Number(0.);
1520         profile.greenColorant.X = double_to_s15Fixed16Number(0.);
1521         profile.greenColorant.Y = double_to_s15Fixed16Number(1.);
1522         profile.greenColorant.Z = double_to_s15Fixed16Number(0.);
1523         profile.blueColorant.X = double_to_s15Fixed16Number(0.);
1524         profile.blueColorant.Y = double_to_s15Fixed16Number(0.);
1525         profile.blueColorant.Z = double_to_s15Fixed16Number(1.);
1526         profile.redTRC = Some(identity_curve());
1527         profile.blueTRC = Some(identity_curve());
1528         profile.greenTRC = Some(identity_curve());
1529 
1530         profile.class_type = DISPLAY_DEVICE_PROFILE;
1531         profile.rendering_intent = Perceptual;
1532         profile.color_space = RGB_SIGNATURE;
1533         profile.pcs = XYZ_TYPE;
1534         profile
1535     }
1536 
new_cicp(cp: ColourPrimaries, tc: TransferCharacteristics) -> Option<Box<Profile>>1537     pub fn new_cicp(cp: ColourPrimaries, tc: TransferCharacteristics) -> Option<Box<Profile>> {
1538         let mut profile = profile_create();
1539         //XXX: should store the whitepoint
1540         if !set_rgb_colorants(&mut profile, cp.white_point(), qcms_CIE_xyYTRIPLE::from(cp)) {
1541             return None;
1542         }
1543         let curve = curveType::from(tc);
1544         profile.redTRC = Some(Box::new(curve.clone()));
1545         profile.blueTRC = Some(Box::new(curve.clone()));
1546         profile.greenTRC = Some(Box::new(curve));
1547         profile.class_type = DISPLAY_DEVICE_PROFILE;
1548         profile.rendering_intent = Perceptual;
1549         profile.color_space = RGB_SIGNATURE;
1550         profile.pcs = XYZ_TYPE;
1551 
1552         profile.is_srgb = (cp, tc) == (ColourPrimaries::Bt709, TransferCharacteristics::Srgb);
1553         Some(profile)
1554     }
1555 
new_gray_with_gamma(gamma: f32) -> Box<Profile>1556     pub fn new_gray_with_gamma(gamma: f32) -> Box<Profile> {
1557         let mut profile = profile_create();
1558 
1559         profile.grayTRC = Some(curve_from_gamma(gamma));
1560         profile.class_type = DISPLAY_DEVICE_PROFILE;
1561         profile.rendering_intent = Perceptual;
1562         profile.color_space = GRAY_SIGNATURE;
1563         profile.pcs = XYZ_TYPE;
1564         profile
1565     }
1566 
new_rgb_with_gamma_set( white_point: qcms_CIE_xyY, primaries: qcms_CIE_xyYTRIPLE, redGamma: f32, greenGamma: f32, blueGamma: f32, ) -> Option<Box<Profile>>1567     pub fn new_rgb_with_gamma_set(
1568         white_point: qcms_CIE_xyY,
1569         primaries: qcms_CIE_xyYTRIPLE,
1570         redGamma: f32,
1571         greenGamma: f32,
1572         blueGamma: f32,
1573     ) -> Option<Box<Profile>> {
1574         let mut profile = profile_create();
1575 
1576         //XXX: should store the whitepoint
1577         if !set_rgb_colorants(&mut profile, white_point, primaries) {
1578             return None;
1579         }
1580         profile.redTRC = Some(curve_from_gamma(redGamma));
1581         profile.blueTRC = Some(curve_from_gamma(blueGamma));
1582         profile.greenTRC = Some(curve_from_gamma(greenGamma));
1583         profile.class_type = DISPLAY_DEVICE_PROFILE;
1584         profile.rendering_intent = Perceptual;
1585         profile.color_space = RGB_SIGNATURE;
1586         profile.pcs = XYZ_TYPE;
1587         Some(profile)
1588     }
1589 
new_from_path(file: &str) -> Option<Box<Profile>>1590     pub fn new_from_path(file: &str) -> Option<Box<Profile>> {
1591         Profile::new_from_slice(&std::fs::read(file).ok()?)
1592     }
1593 
new_from_slice(mem: &[u8]) -> Option<Box<Profile>>1594     pub fn new_from_slice(mem: &[u8]) -> Option<Box<Profile>> {
1595         let length: u32;
1596         let mut source: MemSource = MemSource {
1597             buf: mem,
1598             valid: false,
1599             invalid_reason: None,
1600         };
1601         let index;
1602         source.valid = true;
1603         let mut src: &mut MemSource = &mut source;
1604         if mem.len() < 4 {
1605             return None;
1606         }
1607         length = read_u32(src, 0);
1608         if length as usize <= mem.len() {
1609             // shrink the area that we can read if appropriate
1610             src.buf = &src.buf[0..length as usize];
1611         } else {
1612             return None;
1613         }
1614         /* ensure that the profile size is sane so it's easier to reason about */
1615         if src.buf.len() <= 64 || src.buf.len() >= MAX_PROFILE_SIZE {
1616             return None;
1617         }
1618         let mut profile = profile_create();
1619 
1620         check_CMM_type_signature(src);
1621         check_profile_version(src);
1622         read_class_signature(&mut profile, src);
1623         read_rendering_intent(&mut profile, src);
1624         read_color_space(&mut profile, src);
1625         read_pcs(&mut profile, src);
1626         //TODO read rest of profile stuff
1627         if !src.valid {
1628             return None;
1629         }
1630 
1631         index = read_tag_table(&mut profile, src);
1632         if !src.valid || index.is_empty() {
1633             return None;
1634         }
1635 
1636         if let Some(chad) = find_tag(&index, TAG_CHAD) {
1637             profile.chromaticAdaption = Some(read_tag_s15Fixed16ArrayType(src, chad))
1638         } else {
1639             profile.chromaticAdaption = None; //Signal the data is not present
1640         }
1641 
1642         if profile.class_type == DISPLAY_DEVICE_PROFILE
1643             || profile.class_type == INPUT_DEVICE_PROFILE
1644             || profile.class_type == OUTPUT_DEVICE_PROFILE
1645             || profile.class_type == COLOR_SPACE_PROFILE
1646         {
1647             if profile.color_space == RGB_SIGNATURE {
1648                 if let Some(A2B0) = find_tag(&index, TAG_A2B0) {
1649                     let lut_type = read_u32(src, A2B0.offset as usize);
1650                     if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
1651                         profile.A2B0 = read_tag_lutType(src, A2B0)
1652                     } else if lut_type == LUT_MAB_TYPE {
1653                         profile.mAB = read_tag_lutmABType(src, A2B0)
1654                     }
1655                 }
1656                 if let Some(B2A0) = find_tag(&index, TAG_B2A0) {
1657                     let lut_type = read_u32(src, B2A0.offset as usize);
1658                     if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
1659                         profile.B2A0 = read_tag_lutType(src, B2A0)
1660                     } else if lut_type == LUT_MBA_TYPE {
1661                         profile.mBA = read_tag_lutmABType(src, B2A0)
1662                     }
1663                 }
1664                 if find_tag(&index, TAG_rXYZ).is_some() || !SUPPORTS_ICCV4.load(Ordering::Relaxed) {
1665                     profile.redColorant = read_tag_XYZType(src, &index, TAG_rXYZ);
1666                     profile.greenColorant = read_tag_XYZType(src, &index, TAG_gXYZ);
1667                     profile.blueColorant = read_tag_XYZType(src, &index, TAG_bXYZ)
1668                 }
1669                 if !src.valid {
1670                     return None;
1671                 }
1672 
1673                 if find_tag(&index, TAG_rTRC).is_some() || !SUPPORTS_ICCV4.load(Ordering::Relaxed) {
1674                     profile.redTRC = read_tag_curveType(src, &index, TAG_rTRC);
1675                     profile.greenTRC = read_tag_curveType(src, &index, TAG_gTRC);
1676                     profile.blueTRC = read_tag_curveType(src, &index, TAG_bTRC);
1677                     if profile.redTRC.is_none()
1678                         || profile.blueTRC.is_none()
1679                         || profile.greenTRC.is_none()
1680                     {
1681                         return None;
1682                     }
1683                 }
1684             } else if profile.color_space == GRAY_SIGNATURE {
1685                 profile.grayTRC = read_tag_curveType(src, &index, TAG_kTRC);
1686                 profile.grayTRC.as_ref()?;
1687             } else if profile.color_space == CMYK_SIGNATURE {
1688                 if let Some(A2B0) = find_tag(&index, TAG_A2B0) {
1689                     let lut_type = read_u32(src, A2B0.offset as usize);
1690                     if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
1691                         profile.A2B0 = read_tag_lutType(src, A2B0)
1692                     } else if lut_type == LUT_MBA_TYPE {
1693                         profile.mAB = read_tag_lutmABType(src, A2B0)
1694                     }
1695                 }
1696             } else {
1697                 debug_assert!(false, "read_color_space protects against entering here");
1698                 return None;
1699             }
1700         } else {
1701             return None;
1702         }
1703 
1704         if !src.valid {
1705             return None;
1706         }
1707         Some(profile)
1708     }
1709     /// Precomputes the information needed for this profile to be
1710     /// used as the output profile when constructing a `Transform`.
precache_output_transform(&mut self)1711     pub fn precache_output_transform(&mut self) {
1712         crate::transform::qcms_profile_precache_output_transform(self);
1713     }
1714 }
1715