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