iccread.rs (65252B)
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::{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 // "TRC" is EOTF, e.g. gamma->linear transfer function. 54 // Because ICC profiles are phrased as decodings to the xyzd50-linear PCS. 55 pub(crate) redTRC: Option<Box<curveType>>, 56 pub(crate) blueTRC: Option<Box<curveType>>, 57 pub(crate) greenTRC: Option<Box<curveType>>, 58 pub(crate) grayTRC: Option<Box<curveType>>, 59 pub(crate) A2B0: Option<Box<lutType>>, 60 pub(crate) B2A0: Option<Box<lutType>>, 61 pub(crate) mAB: Option<Box<lutmABType>>, 62 pub(crate) mBA: Option<Box<lutmABType>>, 63 pub(crate) chromaticAdaption: Option<Matrix>, 64 pub(crate) precache_output: 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>), // len=0 => Linear, len=1 => Gamma(v[0]), _ => lut 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] 198 fn uInt8Number_to_float(a: uInt8Number) -> f32 { 199 a as f32 / 255.0 200 } 201 202 #[inline] 203 fn uInt16Number_to_float(a: uInt16Number) -> f32 { 204 a as f32 / 65535.0 205 } 206 207 fn invalid_source(mem: &mut MemSource, reason: &'static str) { 208 mem.valid = false; 209 mem.invalid_reason = Some(reason); 210 } 211 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 } 221 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 } 231 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 } 240 fn read_s15Fixed16Number(mem: &mut MemSource, offset: usize) -> s15Fixed16Number { 241 read_u32(mem, offset) as s15Fixed16Number 242 } 243 fn read_uInt8Number(mem: &mut MemSource, offset: usize) -> uInt8Number { 244 read_u8(mem, offset) 245 } 246 fn read_uInt16Number(mem: &mut MemSource, offset: usize) -> uInt16Number { 247 read_u16(mem, offset) 248 } 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 } 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 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 } 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 300 fn read_class_signature(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 } 312 fn read_color_space(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 } 323 fn read_pcs(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 } 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] 359 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 false { 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 // Also: https://bugzil.la/1799391 and https://bugzil.la/1792469 436 negative = false; // bogus 437 } 438 if negative { 439 return true; 440 } 441 // All Good 442 false 443 } 444 445 pub const TAG_bXYZ: u32 = 0x6258595a; 446 pub const TAG_gXYZ: u32 = 0x6758595a; 447 pub const TAG_rXYZ: u32 = 0x7258595a; 448 pub const TAG_rTRC: u32 = 0x72545243; 449 pub const TAG_bTRC: u32 = 0x62545243; 450 pub const TAG_gTRC: u32 = 0x67545243; 451 pub const TAG_kTRC: u32 = 0x6b545243; 452 pub const TAG_A2B0: u32 = 0x41324230; 453 pub const TAG_B2A0: u32 = 0x42324130; 454 pub const TAG_CHAD: u32 = 0x63686164; 455 456 fn find_tag(index: &TagIndex, tag_id: u32) -> Option<&Tag> { 457 for t in index { 458 if t.signature == tag_id { 459 return Some(t); 460 } 461 } 462 None 463 } 464 465 pub const XYZ_TYPE: u32 = 0x58595a20; // 'XYZ ' 466 pub const CURVE_TYPE: u32 = 0x63757276; // 'curv' 467 pub const PARAMETRIC_CURVE_TYPE: u32 = 0x70617261; // 'para' 468 pub const LUT16_TYPE: u32 = 0x6d667432; // 'mft2' 469 pub const LUT8_TYPE: u32 = 0x6d667431; // 'mft1' 470 pub const LUT_MAB_TYPE: u32 = 0x6d414220; // 'mAB ' 471 pub const LUT_MBA_TYPE: u32 = 0x6d424120; // 'mBA ' 472 pub const CHROMATIC_TYPE: u32 = 0x73663332; // 'sf32' 473 474 fn read_tag_s15Fixed16ArrayType(src: &mut MemSource, tag: &Tag) -> Matrix { 475 let mut matrix: Matrix = Matrix { m: [[0.; 3]; 3] }; 476 let offset: u32 = tag.offset; 477 let type_0: u32 = read_u32(src, offset as usize); 478 // Check mandatory type signature for s16Fixed16ArrayType 479 if type_0 != CHROMATIC_TYPE { 480 invalid_source(src, "unexpected type, expected \'sf32\'"); 481 } 482 for i in 0..=8 { 483 matrix.m[(i / 3) as usize][(i % 3) as usize] = s15Fixed16Number_to_float( 484 read_s15Fixed16Number(src, (offset + 8 + (i * 4) as u32) as usize), 485 ); 486 } 487 matrix 488 } 489 fn read_tag_XYZType(src: &mut MemSource, index: &TagIndex, tag_id: u32) -> XYZNumber { 490 let mut num = XYZNumber { X: 0, Y: 0, Z: 0 }; 491 let tag = find_tag(&index, tag_id); 492 if let Some(tag) = tag { 493 let offset: u32 = tag.offset; 494 let type_0: u32 = read_u32(src, offset as usize); 495 if type_0 != XYZ_TYPE { 496 invalid_source(src, "unexpected type, expected XYZ"); 497 } 498 num.X = read_s15Fixed16Number(src, (offset + 8) as usize); 499 num.Y = read_s15Fixed16Number(src, (offset + 12) as usize); 500 num.Z = read_s15Fixed16Number(src, (offset + 16) as usize) 501 } else { 502 invalid_source(src, "missing xyztag"); 503 } 504 num 505 } 506 // Read the tag at a given offset rather then the tag_index. 507 // This method is used when reading mAB tags where nested curveType are 508 // present that are not part of the tag_index. 509 fn read_curveType(src: &mut MemSource, offset: u32, len: &mut u32) -> Option<Box<curveType>> { 510 const COUNT_TO_LENGTH: [u32; 5] = [1, 3, 4, 5, 7]; //PARAMETRIC_CURVE_TYPE 511 let type_0: u32 = read_u32(src, offset as usize); 512 let count: u32; 513 if type_0 != CURVE_TYPE && type_0 != PARAMETRIC_CURVE_TYPE { 514 invalid_source(src, "unexpected type, expected CURV or PARA"); 515 return None; 516 } 517 if type_0 == CURVE_TYPE { 518 count = read_u32(src, (offset + 8) as usize); 519 //arbitrary 520 if count > 40000 { 521 invalid_source(src, "curve size too large"); 522 return None; 523 } 524 let mut table = Vec::with_capacity(count as usize); 525 for i in 0..count { 526 table.push(read_u16(src, (offset + 12 + i * 2) as usize)); 527 } 528 *len = 12 + count * 2; 529 Some(Box::new(curveType::Curve(table))) 530 } else { 531 count = read_u16(src, (offset + 8) as usize) as u32; 532 if count > 4 { 533 invalid_source(src, "parametric function type not supported."); 534 return None; 535 } 536 let mut params = Vec::with_capacity(count as usize); 537 for i in 0..COUNT_TO_LENGTH[count as usize] { 538 params.push(s15Fixed16Number_to_float(read_s15Fixed16Number( 539 src, 540 (offset + 12 + i * 4) as usize, 541 ))); 542 } 543 *len = 12 + COUNT_TO_LENGTH[count as usize] * 4; 544 if count == 1 || count == 2 { 545 /* we have a type 1 or type 2 function that has a division by 'a' */ 546 let a: f32 = params[1]; 547 if a == 0.0 { 548 invalid_source(src, "parametricCurve definition causes division by zero"); 549 } 550 } 551 Some(Box::new(curveType::Parametric(params))) 552 } 553 } 554 fn read_tag_curveType( 555 src: &mut MemSource, 556 index: &TagIndex, 557 tag_id: u32, 558 ) -> Option<Box<curveType>> { 559 let tag = find_tag(index, tag_id); 560 if let Some(tag) = tag { 561 let mut len: u32 = 0; 562 return read_curveType(src, tag.offset, &mut len); 563 } else { 564 invalid_source(src, "missing curvetag"); 565 } 566 None 567 } 568 569 const MAX_LUT_SIZE: u32 = 500000; // arbitrary 570 const MAX_CHANNELS: usize = 10; // arbitrary 571 fn read_nested_curveType( 572 src: &mut MemSource, 573 curveArray: &mut [Option<Box<curveType>>; MAX_CHANNELS], 574 num_channels: u8, 575 curve_offset: u32, 576 ) { 577 let mut channel_offset: u32 = 0; 578 #[allow(clippy::needless_range_loop)] 579 for i in 0..usize::from(num_channels) { 580 let mut tag_len: u32 = 0; 581 curveArray[i] = read_curveType(src, curve_offset + channel_offset, &mut tag_len); 582 if curveArray[i].is_none() { 583 invalid_source(src, "invalid nested curveType curve"); 584 break; 585 } else { 586 channel_offset += tag_len; 587 // 4 byte aligned 588 if tag_len % 4 != 0 { 589 channel_offset += 4 - tag_len % 4 590 } 591 } 592 } 593 } 594 595 /* See section 10.10 for specs */ 596 fn read_tag_lutmABType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutmABType>> { 597 let offset: u32 = tag.offset; 598 let mut clut_size: u32 = 1; 599 let type_0: u32 = read_u32(src, offset as usize); 600 if type_0 != LUT_MAB_TYPE && type_0 != LUT_MBA_TYPE { 601 return None; 602 } 603 let num_in_channels = read_u8(src, (offset + 8) as usize); 604 let num_out_channels = read_u8(src, (offset + 9) as usize); 605 if num_in_channels > 10 || num_out_channels > 10 { 606 return None; 607 } 608 // We require 3in/out channels since we only support RGB->XYZ (or RGB->LAB) 609 // XXX: If we remove this restriction make sure that the number of channels 610 // is less or equal to the maximum number of mAB curves in qcmsint.h 611 // also check for clut_size overflow. Also make sure it's != 0 612 if num_in_channels != 3 || num_out_channels != 3 { 613 return None; 614 } 615 // some of this data is optional and is denoted by a zero offset 616 // we also use this to track their existance 617 let mut a_curve_offset = read_u32(src, (offset + 28) as usize); 618 let mut clut_offset = read_u32(src, (offset + 24) as usize); 619 let mut m_curve_offset = read_u32(src, (offset + 20) as usize); 620 let mut matrix_offset = read_u32(src, (offset + 16) as usize); 621 let mut b_curve_offset = read_u32(src, (offset + 12) as usize); 622 // Convert offsets relative to the tag to relative to the profile 623 // preserve zero for optional fields 624 if a_curve_offset != 0 { 625 a_curve_offset += offset 626 } 627 if clut_offset != 0 { 628 clut_offset += offset 629 } 630 if m_curve_offset != 0 { 631 m_curve_offset += offset 632 } 633 if matrix_offset != 0 { 634 matrix_offset += offset 635 } 636 if b_curve_offset != 0 { 637 b_curve_offset += offset 638 } 639 if clut_offset != 0 { 640 debug_assert!(num_in_channels == 3); 641 // clut_size can not overflow since lg(256^num_in_channels) = 24 bits. 642 for i in 0..u32::from(num_in_channels) { 643 clut_size *= read_u8(src, (clut_offset + i) as usize) as u32; 644 if clut_size == 0 { 645 invalid_source(src, "bad clut_size"); 646 } 647 } 648 } else { 649 clut_size = 0 650 } 651 // 24bits * 3 won't overflow either 652 clut_size *= num_out_channels as u32; 653 if clut_size > MAX_LUT_SIZE { 654 return None; 655 } 656 657 let mut lut = Box::new(lutmABType::default()); 658 659 if clut_offset != 0 { 660 for i in 0..usize::from(num_in_channels) { 661 lut.num_grid_points[i] = read_u8(src, clut_offset as usize + i); 662 if lut.num_grid_points[i] == 0 { 663 invalid_source(src, "bad grid_points"); 664 } 665 } 666 } 667 // Reverse the processing of transformation elements for mBA type. 668 lut.reversed = type_0 == LUT_MBA_TYPE; 669 lut.num_in_channels = num_in_channels; 670 lut.num_out_channels = num_out_channels; 671 #[allow(clippy::identity_op, clippy::erasing_op)] 672 if matrix_offset != 0 { 673 // read the matrix if we have it 674 lut.e00 = read_s15Fixed16Number(src, (matrix_offset + (4 * 0) as u32) as usize); // the caller checks that this doesn't happen 675 lut.e01 = read_s15Fixed16Number(src, (matrix_offset + (4 * 1) as u32) as usize); 676 lut.e02 = read_s15Fixed16Number(src, (matrix_offset + (4 * 2) as u32) as usize); 677 lut.e10 = read_s15Fixed16Number(src, (matrix_offset + (4 * 3) as u32) as usize); 678 lut.e11 = read_s15Fixed16Number(src, (matrix_offset + (4 * 4) as u32) as usize); 679 lut.e12 = read_s15Fixed16Number(src, (matrix_offset + (4 * 5) as u32) as usize); 680 lut.e20 = read_s15Fixed16Number(src, (matrix_offset + (4 * 6) as u32) as usize); 681 lut.e21 = read_s15Fixed16Number(src, (matrix_offset + (4 * 7) as u32) as usize); 682 lut.e22 = read_s15Fixed16Number(src, (matrix_offset + (4 * 8) as u32) as usize); 683 lut.e03 = read_s15Fixed16Number(src, (matrix_offset + (4 * 9) as u32) as usize); 684 lut.e13 = read_s15Fixed16Number(src, (matrix_offset + (4 * 10) as u32) as usize); 685 lut.e23 = read_s15Fixed16Number(src, (matrix_offset + (4 * 11) as u32) as usize) 686 } 687 if a_curve_offset != 0 { 688 read_nested_curveType(src, &mut lut.a_curves, num_in_channels, a_curve_offset); 689 } 690 if m_curve_offset != 0 { 691 read_nested_curveType(src, &mut lut.m_curves, num_out_channels, m_curve_offset); 692 } 693 if b_curve_offset != 0 { 694 read_nested_curveType(src, &mut lut.b_curves, num_out_channels, b_curve_offset); 695 } else { 696 invalid_source(src, "B curves required"); 697 } 698 if clut_offset != 0 { 699 let clut_precision = read_u8(src, (clut_offset + 16) as usize); 700 let mut clut_table = Vec::with_capacity(clut_size as usize); 701 if clut_precision == 1 { 702 for i in 0..clut_size { 703 clut_table.push(uInt8Number_to_float(read_uInt8Number( 704 src, 705 (clut_offset + 20 + i) as usize, 706 ))); 707 } 708 lut.clut_table = Some(clut_table); 709 } else if clut_precision == 2 { 710 for i in 0..clut_size { 711 clut_table.push(uInt16Number_to_float(read_uInt16Number( 712 src, 713 (clut_offset + 20 + i * 2) as usize, 714 ))); 715 } 716 lut.clut_table = Some(clut_table); 717 } else { 718 invalid_source(src, "Invalid clut precision"); 719 } 720 } 721 if !src.valid { 722 return None; 723 } 724 Some(lut) 725 } 726 fn read_tag_lutType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutType>> { 727 let offset: u32 = tag.offset; 728 let type_0: u32 = read_u32(src, offset as usize); 729 let num_input_table_entries: u16; 730 let num_output_table_entries: u16; 731 let input_offset: u32; 732 let entry_size: usize; 733 if type_0 == LUT8_TYPE { 734 num_input_table_entries = 256u16; 735 num_output_table_entries = 256u16; 736 entry_size = 1; 737 input_offset = 48 738 } else if type_0 == LUT16_TYPE { 739 num_input_table_entries = read_u16(src, (offset + 48) as usize); 740 num_output_table_entries = read_u16(src, (offset + 50) as usize); 741 742 // these limits come from the spec 743 if !(2..=4096).contains(&num_input_table_entries) 744 || !(2..=4096).contains(&num_output_table_entries) 745 { 746 invalid_source(src, "Bad channel count"); 747 return None; 748 } 749 entry_size = 2; 750 input_offset = 52 751 } else { 752 debug_assert!(false); 753 invalid_source(src, "Unexpected lut type"); 754 return None; 755 } 756 let in_chan = read_u8(src, (offset + 8) as usize); 757 let out_chan = read_u8(src, (offset + 9) as usize); 758 if !(in_chan == 3 || in_chan == 4) || out_chan != 3 { 759 invalid_source(src, "CLUT only supports RGB and CMYK"); 760 return None; 761 } 762 763 let grid_points = read_u8(src, (offset + 10) as usize); 764 let clut_size = match (grid_points as u32).checked_pow(in_chan as u32) { 765 Some(clut_size) => clut_size, 766 _ => { 767 invalid_source(src, "CLUT size overflow"); 768 return None; 769 } 770 }; 771 match clut_size { 772 1..=MAX_LUT_SIZE => {} // OK 773 0 => { 774 invalid_source(src, "CLUT must not be empty."); 775 return None; 776 } 777 _ => { 778 invalid_source(src, "CLUT too large"); 779 return None; 780 } 781 } 782 783 let e00 = read_s15Fixed16Number(src, (offset + 12) as usize); 784 let e01 = read_s15Fixed16Number(src, (offset + 16) as usize); 785 let e02 = read_s15Fixed16Number(src, (offset + 20) as usize); 786 let e10 = read_s15Fixed16Number(src, (offset + 24) as usize); 787 let e11 = read_s15Fixed16Number(src, (offset + 28) as usize); 788 let e12 = read_s15Fixed16Number(src, (offset + 32) as usize); 789 let e20 = read_s15Fixed16Number(src, (offset + 36) as usize); 790 let e21 = read_s15Fixed16Number(src, (offset + 40) as usize); 791 let e22 = read_s15Fixed16Number(src, (offset + 44) as usize); 792 793 let mut input_table = Vec::with_capacity((num_input_table_entries * in_chan as u16) as usize); 794 for i in 0..(num_input_table_entries * in_chan as u16) { 795 if type_0 == LUT8_TYPE { 796 input_table.push(uInt8Number_to_float(read_uInt8Number( 797 src, 798 (offset + input_offset) as usize + i as usize * entry_size, 799 ))) 800 } else { 801 input_table.push(uInt16Number_to_float(read_uInt16Number( 802 src, 803 (offset + input_offset) as usize + i as usize * entry_size, 804 ))) 805 } 806 } 807 let clut_offset = ((offset + input_offset) as usize 808 + (num_input_table_entries as i32 * in_chan as i32) as usize * entry_size) 809 as u32; 810 811 let mut clut_table = Vec::with_capacity((clut_size * out_chan as u32) as usize); 812 for i in 0..clut_size * out_chan as u32 { 813 if type_0 == LUT8_TYPE { 814 clut_table.push(uInt8Number_to_float(read_uInt8Number( 815 src, 816 clut_offset as usize + i as usize * entry_size, 817 ))); 818 } else if type_0 == LUT16_TYPE { 819 clut_table.push(uInt16Number_to_float(read_uInt16Number( 820 src, 821 clut_offset as usize + i as usize * entry_size, 822 ))); 823 } 824 } 825 826 let output_offset = 827 (clut_offset as usize + (clut_size * out_chan as u32) as usize * entry_size) as u32; 828 829 let mut output_table = 830 Vec::with_capacity((num_output_table_entries * out_chan as u16) as usize); 831 for i in 0..num_output_table_entries as i32 * out_chan as i32 { 832 if type_0 == LUT8_TYPE { 833 output_table.push(uInt8Number_to_float(read_uInt8Number( 834 src, 835 output_offset as usize + i as usize * entry_size, 836 ))) 837 } else { 838 output_table.push(uInt16Number_to_float(read_uInt16Number( 839 src, 840 output_offset as usize + i as usize * entry_size, 841 ))) 842 } 843 } 844 Some(Box::new(lutType { 845 num_input_table_entries, 846 num_output_table_entries, 847 num_input_channels: in_chan, 848 num_output_channels: out_chan, 849 num_clut_grid_points: grid_points, 850 e00, 851 e01, 852 e02, 853 e10, 854 e11, 855 e12, 856 e20, 857 e21, 858 e22, 859 input_table, 860 clut_table, 861 output_table, 862 })) 863 } 864 fn read_rendering_intent(profile: &mut Profile, src: &mut MemSource) { 865 let intent = read_u32(src, 64); 866 profile.rendering_intent = match intent { 867 x if x == Perceptual as u32 => Perceptual, 868 x if x == RelativeColorimetric as u32 => RelativeColorimetric, 869 x if x == Saturation as u32 => Saturation, 870 x if x == AbsoluteColorimetric as u32 => AbsoluteColorimetric, 871 _ => { 872 invalid_source(src, "unknown rendering intent"); 873 Intent::default() 874 } 875 }; 876 } 877 fn profile_create() -> Box<Profile> { 878 Box::new(Profile::default()) 879 } 880 /* build sRGB gamma table */ 881 /* based on cmsBuildParametricGamma() */ 882 #[allow(clippy::many_single_char_names)] 883 fn build_sRGB_gamma_table(num_entries: i32) -> Vec<u16> { 884 /* taken from lcms: Build_sRGBGamma() */ 885 let gamma: f64 = 2.4; 886 let a: f64 = 1.0 / 1.055; 887 let b: f64 = 0.055 / 1.055; 888 let c: f64 = 1.0 / 12.92; 889 let d: f64 = 0.04045; 890 891 build_trc_table( 892 num_entries, 893 // IEC 61966-2.1 (sRGB) 894 // Y = (aX + b)^Gamma | X >= d 895 // Y = cX | X < d 896 |x| { 897 if x >= d { 898 let e: f64 = a * x + b; 899 if e > 0. { 900 e.powf(gamma) 901 } else { 902 0. 903 } 904 } else { 905 c * x 906 } 907 }, 908 ) 909 } 910 911 /// eotf: electro-optical transfer characteristic function, maps from [0, 1] 912 /// in non-linear (voltage) space to [0, 1] in linear (optical) space. Should 913 /// generally be a concave up function. 914 fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> { 915 let mut table = Vec::with_capacity(num_entries as usize); 916 917 for i in 0..num_entries { 918 let x: f64 = i as f64 / (num_entries - 1) as f64; 919 let y: f64 = eotf(x); 920 let mut output: f64; 921 // Saturate -- this could likely move to a separate function 922 output = y * 65535.0 + 0.5; 923 if output > 65535.0 { 924 output = 65535.0 925 } 926 if output < 0.0 { 927 output = 0.0 928 } 929 table.push(output.floor() as u16); 930 } 931 table 932 } 933 fn curve_from_table(table: &[u16]) -> Box<curveType> { 934 Box::new(curveType::Curve(table.to_vec())) 935 } 936 pub fn float_to_u8Fixed8Number(a: f32) -> u16 { 937 if a > 255.0 + 255.0 / 256f32 { 938 0xffffu16 939 } else if a < 0.0 { 940 0u16 941 } else { 942 (a * 256.0 + 0.5).floor() as u16 943 } 944 } 945 946 fn curve_from_gamma(gamma: f32) -> Box<curveType> { 947 Box::new(curveType::Curve(vec![float_to_u8Fixed8Number(gamma)])) 948 } 949 950 fn identity_curve() -> Box<curveType> { 951 Box::new(curveType::Curve(Vec::new())) 952 } 953 954 /* from lcms: cmsWhitePointFromTemp */ 955 /* tempK must be >= 4000. and <= 25000. 956 * Invalid values of tempK will return 957 * (x,y,Y) = (-1.0, -1.0, -1.0) 958 * similar to argyll: icx_DTEMP2XYZ() */ 959 fn white_point_from_temp(temp_K: i32) -> qcms_CIE_xyY { 960 let mut white_point: qcms_CIE_xyY = qcms_CIE_xyY { 961 x: 0., 962 y: 0., 963 Y: 0., 964 }; 965 // No optimization provided. 966 let T = temp_K as f64; // Square 967 let T2 = T * T; // Cube 968 let T3 = T2 * T; 969 // For correlated color temperature (T) between 4000K and 7000K: 970 let x = if (4000.0..=7000.0).contains(&T) { 971 -4.6070 * (1E9 / T3) + 2.9678 * (1E6 / T2) + 0.09911 * (1E3 / T) + 0.244063 972 } else if T > 7000.0 && T <= 25000.0 { 973 -2.0064 * (1E9 / T3) + 1.9018 * (1E6 / T2) + 0.24748 * (1E3 / T) + 0.237040 974 } else { 975 // or for correlated color temperature (T) between 7000K and 25000K: 976 // Invalid tempK 977 white_point.x = -1.0; 978 white_point.y = -1.0; 979 white_point.Y = -1.0; 980 debug_assert!(false, "invalid temp"); 981 return white_point; 982 }; 983 // Obtain y(x) 984 let y = -3.000 * (x * x) + 2.870 * x - 0.275; 985 // wave factors (not used, but here for futures extensions) 986 // let M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y); 987 // let M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y); 988 // Fill white_point struct 989 white_point.x = x; 990 white_point.y = y; 991 white_point.Y = 1.0; 992 white_point 993 } 994 #[no_mangle] 995 pub extern "C" fn qcms_white_point_sRGB() -> qcms_CIE_xyY { 996 white_point_from_temp(6504) 997 } 998 999 /// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 2 1000 /// Values 0, 3, 13–21, 23–255 are all reserved so all map to the same variant 1001 #[derive(Clone, Copy, Debug, PartialEq)] 1002 pub enum ColourPrimaries { 1003 /// For future use by ITU-T | ISO/IEC 1004 Reserved, 1005 /// Rec. ITU-R BT.709-6<br /> 1006 /// Rec. ITU-R BT.1361-0 conventional colour gamut system and extended colour gamut system (historical)<br /> 1007 /// IEC 61966-2-1 sRGB or sYCC IEC 61966-2-4<br /> 1008 /// Society of Motion Picture and Television Engineers (MPTE) RP 177 (1993) Annex B<br /> 1009 Bt709 = 1, 1010 /// Unspecified<br /> 1011 /// Image characteristics are unknown or are determined by the application. 1012 Unspecified = 2, 1013 /// Rec. ITU-R BT.470-6 System M (historical)<br /> 1014 /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br /> 1015 /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br /> 1016 Bt470M = 4, 1017 /// Rec. ITU-R BT.470-6 System B, G (historical) Rec. ITU-R BT.601-7 625<br /> 1018 /// Rec. ITU-R BT.1358-0 625 (historical)<br /> 1019 /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br /> 1020 Bt470Bg = 5, 1021 /// Rec. ITU-R BT.601-7 525<br /> 1022 /// Rec. ITU-R BT.1358-1 525 or 625 (historical) Rec. ITU-R BT.1700-0 NTSC<br /> 1023 /// SMPTE 170M (2004)<br /> 1024 /// (functionally the same as the value 7)<br /> 1025 Bt601 = 6, 1026 /// SMPTE 240M (1999) (historical) (functionally the same as the value 6)<br /> 1027 Smpte240 = 7, 1028 /// Generic film (colour filters using Illuminant C)<br /> 1029 Generic_film = 8, 1030 /// Rec. ITU-R BT.2020-2<br /> 1031 /// Rec. ITU-R BT.2100-0<br /> 1032 Bt2020 = 9, 1033 /// SMPTE ST 428-1<br /> 1034 /// (CIE 1931 XYZ as in ISO 11664-1)<br /> 1035 Xyz = 10, 1036 /// SMPTE RP 431-2 (2011)<br /> 1037 Smpte431 = 11, 1038 /// SMPTE EG 432-1 (2010)<br /> 1039 Smpte432 = 12, 1040 /// EBU Tech. 3213-E (1975)<br /> 1041 Ebu3213 = 22, 1042 } 1043 1044 impl From<u8> for ColourPrimaries { 1045 fn from(value: u8) -> Self { 1046 match value { 1047 0 | 3 | 13..=21 | 23..=255 => Self::Reserved, 1048 1 => Self::Bt709, 1049 2 => Self::Unspecified, 1050 4 => Self::Bt470M, 1051 5 => Self::Bt470Bg, 1052 6 => Self::Bt601, 1053 7 => Self::Smpte240, 1054 8 => Self::Generic_film, 1055 9 => Self::Bt2020, 1056 10 => Self::Xyz, 1057 11 => Self::Smpte431, 1058 12 => Self::Smpte432, 1059 22 => Self::Ebu3213, 1060 } 1061 } 1062 } 1063 1064 #[test] 1065 fn colour_primaries() { 1066 for value in 0..=u8::MAX { 1067 match ColourPrimaries::from(value) { 1068 ColourPrimaries::Reserved => {} 1069 variant => assert_eq!(value, variant as u8), 1070 } 1071 } 1072 } 1073 1074 impl From<ColourPrimaries> for qcms_CIE_xyYTRIPLE { 1075 fn from(value: ColourPrimaries) -> Self { 1076 let red; 1077 let green; 1078 let blue; 1079 1080 match value { 1081 ColourPrimaries::Reserved => panic!("CP={} is reserved", value as u8), 1082 ColourPrimaries::Bt709 => { 1083 green = qcms_chromaticity { x: 0.300, y: 0.600 }; 1084 blue = qcms_chromaticity { x: 0.150, y: 0.060 }; 1085 red = qcms_chromaticity { x: 0.640, y: 0.330 }; 1086 } 1087 ColourPrimaries::Unspecified => panic!("CP={} is unspecified", value as u8), 1088 ColourPrimaries::Bt470M => { 1089 green = qcms_chromaticity { x: 0.21, y: 0.71 }; 1090 blue = qcms_chromaticity { x: 0.14, y: 0.08 }; 1091 red = qcms_chromaticity { x: 0.67, y: 0.33 }; 1092 } 1093 ColourPrimaries::Bt470Bg => { 1094 green = qcms_chromaticity { x: 0.29, y: 0.60 }; 1095 blue = qcms_chromaticity { x: 0.15, y: 0.06 }; 1096 red = qcms_chromaticity { x: 0.64, y: 0.33 }; 1097 } 1098 ColourPrimaries::Bt601 | ColourPrimaries::Smpte240 => { 1099 green = qcms_chromaticity { x: 0.310, y: 0.595 }; 1100 blue = qcms_chromaticity { x: 0.155, y: 0.070 }; 1101 red = qcms_chromaticity { x: 0.630, y: 0.340 }; 1102 } 1103 ColourPrimaries::Generic_film => { 1104 green = qcms_chromaticity { x: 0.243, y: 0.692 }; 1105 blue = qcms_chromaticity { x: 0.145, y: 0.049 }; 1106 red = qcms_chromaticity { x: 0.681, y: 0.319 }; 1107 } 1108 ColourPrimaries::Bt2020 => { 1109 green = qcms_chromaticity { x: 0.170, y: 0.797 }; 1110 blue = qcms_chromaticity { x: 0.131, y: 0.046 }; 1111 red = qcms_chromaticity { x: 0.708, y: 0.292 }; 1112 } 1113 ColourPrimaries::Xyz => { 1114 green = qcms_chromaticity { x: 0.0, y: 1.0 }; 1115 blue = qcms_chromaticity { x: 0.0, y: 0.0 }; 1116 red = qcms_chromaticity { x: 1.0, y: 0.0 }; 1117 } 1118 // These two share primaries, but have distinct white points 1119 ColourPrimaries::Smpte431 | ColourPrimaries::Smpte432 => { 1120 green = qcms_chromaticity { x: 0.265, y: 0.690 }; 1121 blue = qcms_chromaticity { x: 0.150, y: 0.060 }; 1122 red = qcms_chromaticity { x: 0.680, y: 0.320 }; 1123 } 1124 ColourPrimaries::Ebu3213 => { 1125 green = qcms_chromaticity { x: 0.295, y: 0.605 }; 1126 blue = qcms_chromaticity { x: 0.155, y: 0.077 }; 1127 red = qcms_chromaticity { x: 0.630, y: 0.340 }; 1128 } 1129 } 1130 1131 Self { 1132 red: red.into(), 1133 green: green.into(), 1134 blue: blue.into(), 1135 } 1136 } 1137 } 1138 1139 impl ColourPrimaries { 1140 fn white_point(self) -> qcms_CIE_xyY { 1141 match self { 1142 Self::Reserved => panic!("CP={} is reserved", self as u8), 1143 Self::Bt709 1144 | Self::Bt470Bg 1145 | Self::Bt601 1146 | Self::Smpte240 1147 | Self::Bt2020 1148 | Self::Smpte432 1149 | Self::Ebu3213 => qcms_chromaticity::D65, 1150 Self::Unspecified => panic!("CP={} is unspecified", self as u8), 1151 Self::Bt470M => qcms_chromaticity { x: 0.310, y: 0.316 }, 1152 Self::Generic_film => qcms_chromaticity { x: 0.310, y: 0.316 }, 1153 Self::Xyz => qcms_chromaticity { 1154 x: 1. / 3., 1155 y: 1. / 3., 1156 }, 1157 Self::Smpte431 => qcms_chromaticity { x: 0.314, y: 0.351 }, 1158 } 1159 .into() 1160 } 1161 1162 fn is_usable(self) -> bool { 1163 match self { 1164 Self::Reserved | Self::Unspecified => false, 1165 _ => true 1166 } 1167 } 1168 } 1169 1170 /// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 3 1171 /// Values 0, 3, 19–255 are all reserved so all map to the same variant 1172 #[derive(Clone, Copy, Debug, PartialEq)] 1173 pub enum TransferCharacteristics { 1174 /// For future use by ITU-T | ISO/IEC 1175 Reserved, 1176 /// Rec. ITU-R BT.709-6<br /> 1177 /// Rec. ITU-R BT.1361-0 conventional colour gamut system (historical)<br /> 1178 /// (functionally the same as the values 6, 14 and 15) <br /> 1179 Bt709 = 1, 1180 /// Image characteristics are unknown or are determined by the application.<br /> 1181 Unspecified = 2, 1182 /// Rec. ITU-R BT.470-6 System M (historical)<br /> 1183 /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br /> 1184 /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br /> 1185 /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br /> 1186 Bt470M = 4, 1187 /// Rec. ITU-R BT.470-6 System B, G (historical)<br /> 1188 Bt470Bg = 5, 1189 /// Rec. ITU-R BT.601-7 525 or 625<br /> 1190 /// Rec. ITU-R BT.1358-1 525 or 625 (historical)<br /> 1191 /// Rec. ITU-R BT.1700-0 NTSC SMPTE 170M (2004)<br /> 1192 /// (functionally the same as the values 1, 14 and 15)<br /> 1193 Bt601 = 6, 1194 /// SMPTE 240M (1999) (historical)<br /> 1195 Smpte240 = 7, 1196 /// Linear transfer characteristics<br /> 1197 Linear = 8, 1198 /// Logarithmic transfer characteristic (100:1 range)<br /> 1199 Log_100 = 9, 1200 /// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range)<br /> 1201 Log_100_sqrt10 = 10, 1202 /// IEC 61966-2-4<br /> 1203 Iec61966 = 11, 1204 /// Rec. ITU-R BT.1361-0 extended colour gamut system (historical)<br /> 1205 Bt_1361 = 12, 1206 /// IEC 61966-2-1 sRGB or sYCC<br /> 1207 Srgb = 13, 1208 /// Rec. ITU-R BT.2020-2 (10-bit system)<br /> 1209 /// (functionally the same as the values 1, 6 and 15)<br /> 1210 Bt2020_10bit = 14, 1211 /// Rec. ITU-R BT.2020-2 (12-bit system)<br /> 1212 /// (functionally the same as the values 1, 6 and 14)<br /> 1213 Bt2020_12bit = 15, 1214 /// SMPTE ST 2084 for 10-, 12-, 14- and 16-bitsystems<br /> 1215 /// Rec. ITU-R BT.2100-0 perceptual quantization (PQ) system<br /> 1216 Smpte2084 = 16, 1217 /// SMPTE ST 428-1<br /> 1218 Smpte428 = 17, 1219 /// ARIB STD-B67<br /> 1220 /// Rec. ITU-R BT.2100-0 hybrid log- gamma (HLG) system<br /> 1221 Hlg = 18, 1222 } 1223 1224 #[test] 1225 fn transfer_characteristics() { 1226 for value in 0..=u8::MAX { 1227 match TransferCharacteristics::from(value) { 1228 TransferCharacteristics::Reserved => {} 1229 variant => assert_eq!(value, variant as u8), 1230 } 1231 } 1232 } 1233 1234 impl From<u8> for TransferCharacteristics { 1235 fn from(value: u8) -> Self { 1236 match value { 1237 0 | 3 | 19..=255 => Self::Reserved, 1238 1 => Self::Bt709, 1239 2 => Self::Unspecified, 1240 4 => Self::Bt470M, 1241 5 => Self::Bt470Bg, 1242 6 => Self::Bt601, 1243 7 => Self::Smpte240, // unimplemented 1244 8 => Self::Linear, 1245 9 => Self::Log_100, 1246 10 => Self::Log_100_sqrt10, 1247 11 => Self::Iec61966, // unimplemented 1248 12 => Self::Bt_1361, // unimplemented 1249 13 => Self::Srgb, 1250 14 => Self::Bt2020_10bit, 1251 15 => Self::Bt2020_12bit, 1252 16 => Self::Smpte2084, 1253 17 => Self::Smpte428, // unimplemented 1254 18 => Self::Hlg, 1255 } 1256 } 1257 } 1258 1259 impl TryFrom<TransferCharacteristics> for curveType { 1260 type Error = (); 1261 /// See [ICC.1:2010](https://www.color.org/specification/ICC1v43_2010-12.pdf) 1262 /// 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) 1263 fn try_from(value: TransferCharacteristics) -> Result<Self, Self::Error> { 1264 const NUM_TRC_TABLE_ENTRIES: i32 = 1024; 1265 1266 Ok(match value { 1267 TransferCharacteristics::Reserved => panic!("TC={} is reserved", value as u8), 1268 TransferCharacteristics::Bt709 1269 | TransferCharacteristics::Bt601 1270 | TransferCharacteristics::Bt2020_10bit 1271 | TransferCharacteristics::Bt2020_12bit => { 1272 // The opto-electronic transfer characteristic function (OETF) 1273 // as defined in ITU-T H.273 table 3, row 1: 1274 // 1275 // V = (α * Lc^0.45) − (α − 1) for 1 >= Lc >= β 1276 // V = 4.500 * Lc for β > Lc >= 0 1277 // 1278 // Inverting gives the electro-optical transfer characteristic 1279 // function (EOTF) which can be represented as ICC 1280 // parametricCurveType with 4 parameters (ICC.1:2010 Table 65). 1281 // Converting between the two (Lc ↔︎ Y, V ↔︎ X): 1282 // 1283 // Y = (a * X + b)^g for (X >= d) 1284 // Y = c * X for (X < d) 1285 // 1286 // g, a, b, c, d can then be defined in terms of α and β: 1287 // 1288 // g = 1 / 0.45 1289 // a = 1 / α 1290 // b = 1 - a 1291 // c = 1 / 4.500 1292 // d = 4.500 * β 1293 // 1294 // α and β are determined by solving the piecewise equations to 1295 // ensure continuity of both value and slope at the value β. 1296 // We use the values specified for 10-bit systems in 1297 // https://www.itu.int/rec/R-REC-BT.2020-2-201510-I Table 4 1298 // since this results in the similar values as available ICC 1299 // profiles after converting to s15Fixed16Number, providing us 1300 // good test coverage. 1301 1302 type Float = f32; 1303 1304 const alpha: Float = 1.099; 1305 const beta: Float = 0.018; 1306 1307 const linear_coef: Float = 4.500; 1308 const pow_exp: Float = 0.45; 1309 1310 const g: Float = 1. / pow_exp; 1311 const a: Float = 1. / alpha; 1312 const b: Float = 1. - a; 1313 const c: Float = 1. / linear_coef; 1314 const d: Float = linear_coef * beta; 1315 1316 curveType::Parametric(vec![g, a, b, c, d]) 1317 } 1318 TransferCharacteristics::Unspecified => panic!("TC={} is unspecified", value as u8), 1319 TransferCharacteristics::Bt470M => *curve_from_gamma(2.2), 1320 TransferCharacteristics::Bt470Bg => *curve_from_gamma(2.8), 1321 TransferCharacteristics::Smpte240 => return Err(()), 1322 TransferCharacteristics::Linear => *curve_from_gamma(1.), 1323 TransferCharacteristics::Log_100 => { 1324 // See log_100_transfer_characteristics() for derivation 1325 // The opto-electronic transfer characteristic function (OETF) 1326 // as defined in ITU-T H.273 table 3, row 9: 1327 // 1328 // V = 1.0 + Log10(Lc) ÷ 2 for 1 >= Lc >= 0.01 1329 // V = 0.0 for 0.01 > Lc >= 0 1330 // 1331 // Inverting this to give the EOTF required for the profile gives 1332 // 1333 // Lc = 10^(2*V - 2) for 1 >= V >= 0 1334 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2. * v - 2.)); 1335 curveType::Curve(table) 1336 } 1337 TransferCharacteristics::Log_100_sqrt10 => { 1338 // The opto-electronic transfer characteristic function (OETF) 1339 // as defined in ITU-T H.273 table 3, row 10: 1340 // 1341 // V = 1.0 + Log10(Lc) ÷ 2.5 for 1 >= Lc >= Sqrt(10) ÷ 1000 1342 // V = 0.0 for Sqrt(10) ÷ 1000 > Lc >= 0 1343 // 1344 // Inverting this to give the EOTF required for the profile gives 1345 // 1346 // Lc = 10^(2.5*V - 2.5) for 1 >= V >= 0 1347 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2.5 * v - 2.5)); 1348 curveType::Curve(table) 1349 } 1350 TransferCharacteristics::Iec61966 => return Err(()), 1351 TransferCharacteristics::Bt_1361 => return Err(()), 1352 TransferCharacteristics::Srgb => { 1353 // Should we prefer this or curveType::Parametric? 1354 curveType::Curve(build_sRGB_gamma_table(NUM_TRC_TABLE_ENTRIES)) 1355 } 1356 1357 TransferCharacteristics::Smpte2084 => { 1358 // Despite using Lo rather than Lc, H.273 gives the OETF: 1359 // 1360 // V = ( ( c1 + c2 * (Lo)^n ) ÷ ( 1 + c3 * (Lo)^n ) )^m 1361 const c1: f64 = 0.8359375; 1362 const c2: f64 = 18.8515625; 1363 const c3: f64 = 18.6875; 1364 const m: f64 = 78.84375; 1365 const n: f64 = 0.1593017578125; 1366 1367 // Inverting this to give the EOTF required for the profile 1368 // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives 1369 // 1370 // Y = ( max[( X^(1/m) - c1 ), 0] ÷ ( c2 - c3 * X^(1/m) ) )^(1/n) 1371 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| { 1372 ((x.powf(1. / m) - c1).max(0.) / (c2 - c3 * x.powf(1. / m))).powf(1. / n) 1373 }); 1374 curveType::Curve(table) 1375 } 1376 TransferCharacteristics::Smpte428 => return Err(()), 1377 TransferCharacteristics::Hlg => { 1378 // The opto-electronic transfer characteristic function (OETF) 1379 // as defined in ITU-T H.273 table 3, row 18: 1380 // 1381 // V = a * Ln(12 * Lc - b) + c for 1 >= Lc > 1 ÷ 12 1382 // V = Sqrt(3) * Lc^0.5 for 1 ÷ 12 >= Lc >= 0 1383 const a: f64 = 0.17883277; 1384 const b: f64 = 0.28466892; 1385 const c: f64 = 0.55991073; 1386 1387 // Inverting this to give the EOTF required for the profile 1388 // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives 1389 // 1390 // Y = (X^2) / 3 for 0 <= X <= 0.5 1391 // Y = ((e^((X-c)/a))+b)/12 for 0.5 < X <= 1 1392 let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| { 1393 if x <= 0.5 { 1394 let y1 = x.powf(2.) / 3.; 1395 assert!((0. ..=1. / 12.).contains(&y1)); 1396 y1 1397 } else { 1398 (std::f64::consts::E.powf((x - c) / a) + b) / 12. 1399 } 1400 }); 1401 curveType::Curve(table) 1402 } 1403 }) 1404 } 1405 } 1406 1407 impl TransferCharacteristics { 1408 fn is_usable(self) -> bool { 1409 match self { 1410 Self::Reserved | Self::Unspecified => false, 1411 _ => true 1412 } 1413 } 1414 } 1415 1416 #[cfg(test)] 1417 fn check_transfer_characteristics(cicp: TransferCharacteristics, icc_path: &str) { 1418 let mut cicp_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE]; 1419 let mut icc_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE]; 1420 let cicp_tc = curveType::try_from(cicp).unwrap(); 1421 let icc = Profile::new_from_path(icc_path).unwrap(); 1422 let icc_tc = icc.redTRC.as_ref().unwrap(); 1423 1424 eprintln!("cicp_tc: {:?}", cicp_tc); 1425 eprintln!("icc_tc: {:?}", icc_tc); 1426 1427 crate::transform_util::compute_precache(icc_tc, &mut icc_out); 1428 crate::transform_util::compute_precache(&cicp_tc, &mut cicp_out); 1429 1430 let mut off_by_one = 0; 1431 for i in 0..cicp_out.len() { 1432 match (cicp_out[i] as i16) - (icc_out[i] as i16) { 1433 0 => {} 1434 1 | -1 => { 1435 off_by_one += 1; 1436 } 1437 _ => assert_eq!(cicp_out[i], icc_out[i], "difference at index {}", i), 1438 } 1439 } 1440 eprintln!("{} / {} off by one", off_by_one, cicp_out.len()); 1441 } 1442 1443 #[test] 1444 fn srgb_transfer_characteristics() { 1445 check_transfer_characteristics(TransferCharacteristics::Srgb, "sRGB_lcms.icc"); 1446 } 1447 1448 #[test] 1449 fn bt709_transfer_characteristics() { 1450 check_transfer_characteristics(TransferCharacteristics::Bt709, "ITU-709.icc"); 1451 } 1452 1453 #[test] 1454 fn bt2020_10bit_transfer_characteristics() { 1455 check_transfer_characteristics(TransferCharacteristics::Bt2020_10bit, "ITU-2020.icc"); 1456 } 1457 1458 #[test] 1459 fn bt2020_12bit_transfer_characteristics() { 1460 check_transfer_characteristics(TransferCharacteristics::Bt2020_12bit, "ITU-2020.icc"); 1461 } 1462 1463 impl Profile { 1464 //XXX: it would be nice if we had a way of ensuring 1465 // everything in a profile was initialized regardless of how it was created 1466 //XXX: should this also be taking a black_point? 1467 /* similar to CGColorSpaceCreateCalibratedRGB */ 1468 pub fn new_rgb_with_table( 1469 white_point: qcms_CIE_xyY, 1470 primaries: qcms_CIE_xyYTRIPLE, 1471 table: &[u16], 1472 ) -> Option<Box<Profile>> { 1473 let mut profile = profile_create(); 1474 //XXX: should store the whitepoint 1475 if !set_rgb_colorants(&mut profile, white_point, primaries) { 1476 return None; 1477 } 1478 profile.redTRC = Some(curve_from_table(table)); 1479 profile.blueTRC = Some(curve_from_table(table)); 1480 profile.greenTRC = Some(curve_from_table(table)); 1481 profile.class_type = DISPLAY_DEVICE_PROFILE; 1482 profile.rendering_intent = Perceptual; 1483 profile.color_space = RGB_SIGNATURE; 1484 profile.pcs = XYZ_TYPE; 1485 Some(profile) 1486 } 1487 pub fn new_sRGB() -> Box<Profile> { 1488 let D65 = qcms_white_point_sRGB(); 1489 let table = build_sRGB_gamma_table(1024); 1490 1491 let mut srgb = Profile::new_rgb_with_table( 1492 D65, 1493 qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709), 1494 &table, 1495 ) 1496 .unwrap(); 1497 srgb.is_srgb = true; 1498 srgb 1499 } 1500 1501 /// Returns true if this profile is sRGB 1502 pub fn is_sRGB(&self) -> bool { 1503 self.is_srgb 1504 } 1505 1506 pub(crate) fn new_sRGB_parametric() -> Box<Profile> { 1507 let primaries = qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709); 1508 let white_point = qcms_white_point_sRGB(); 1509 let mut profile = profile_create(); 1510 set_rgb_colorants(&mut profile, white_point, primaries); 1511 1512 let curve = Box::new(curveType::Parametric(vec![ 1513 2.4, 1514 1. / 1.055, 1515 0.055 / 1.055, 1516 1. / 12.92, 1517 0.04045, 1518 ])); 1519 profile.redTRC = Some(curve.clone()); 1520 profile.blueTRC = Some(curve.clone()); 1521 profile.greenTRC = Some(curve); 1522 profile.class_type = DISPLAY_DEVICE_PROFILE; 1523 profile.rendering_intent = Perceptual; 1524 profile.color_space = RGB_SIGNATURE; 1525 profile.pcs = XYZ_TYPE; 1526 profile.is_srgb = true; 1527 profile 1528 } 1529 1530 pub(crate) fn new_displayP3() -> Box<Profile> { 1531 let primaries = qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Smpte432); 1532 let white_point = qcms_white_point_sRGB(); 1533 let mut profile = profile_create(); 1534 set_rgb_colorants(&mut profile, white_point, primaries); 1535 1536 let curve = Box::new(curveType::Parametric(vec![ 1537 2.4, 1538 1. / 1.055, 1539 0.055 / 1.055, 1540 1. / 12.92, 1541 0.04045, 1542 ])); 1543 profile.redTRC = Some(curve.clone()); 1544 profile.blueTRC = Some(curve.clone()); 1545 profile.greenTRC = Some(curve); 1546 profile.class_type = DISPLAY_DEVICE_PROFILE; 1547 profile.rendering_intent = Perceptual; 1548 profile.color_space = RGB_SIGNATURE; 1549 profile.pcs = XYZ_TYPE; 1550 profile.is_srgb = false; 1551 profile 1552 } 1553 1554 /// Create a new profile with D50 adopted white and identity transform functions 1555 pub fn new_XYZD50() -> Box<Profile> { 1556 let mut profile = profile_create(); 1557 profile.redColorant.X = double_to_s15Fixed16Number(1.); 1558 profile.redColorant.Y = double_to_s15Fixed16Number(0.); 1559 profile.redColorant.Z = double_to_s15Fixed16Number(0.); 1560 profile.greenColorant.X = double_to_s15Fixed16Number(0.); 1561 profile.greenColorant.Y = double_to_s15Fixed16Number(1.); 1562 profile.greenColorant.Z = double_to_s15Fixed16Number(0.); 1563 profile.blueColorant.X = double_to_s15Fixed16Number(0.); 1564 profile.blueColorant.Y = double_to_s15Fixed16Number(0.); 1565 profile.blueColorant.Z = double_to_s15Fixed16Number(1.); 1566 profile.redTRC = Some(identity_curve()); 1567 profile.blueTRC = Some(identity_curve()); 1568 profile.greenTRC = Some(identity_curve()); 1569 1570 profile.class_type = DISPLAY_DEVICE_PROFILE; 1571 profile.rendering_intent = Perceptual; 1572 profile.color_space = RGB_SIGNATURE; 1573 profile.pcs = XYZ_TYPE; 1574 profile 1575 } 1576 1577 pub fn new_cicp(cp: ColourPrimaries, tc: TransferCharacteristics) -> Option<Box<Profile>> { 1578 let mut profile = profile_create(); 1579 if !cp.is_usable() || !tc.is_usable() { 1580 return None; 1581 } 1582 //XXX: should store the whitepoint 1583 if !set_rgb_colorants(&mut profile, cp.white_point(), qcms_CIE_xyYTRIPLE::from(cp)) { 1584 return None; 1585 } 1586 let curve = curveType::try_from(tc).ok()?; 1587 profile.redTRC = Some(Box::new(curve.clone())); 1588 profile.blueTRC = Some(Box::new(curve.clone())); 1589 profile.greenTRC = Some(Box::new(curve)); 1590 profile.class_type = DISPLAY_DEVICE_PROFILE; 1591 profile.rendering_intent = Perceptual; 1592 profile.color_space = RGB_SIGNATURE; 1593 profile.pcs = XYZ_TYPE; 1594 1595 profile.is_srgb = (cp, tc) == (ColourPrimaries::Bt709, TransferCharacteristics::Srgb); 1596 Some(profile) 1597 } 1598 1599 pub fn new_gray_with_gamma(gamma: f32) -> Box<Profile> { 1600 let mut profile = profile_create(); 1601 1602 profile.grayTRC = Some(curve_from_gamma(gamma)); 1603 profile.class_type = DISPLAY_DEVICE_PROFILE; 1604 profile.rendering_intent = Perceptual; 1605 profile.color_space = GRAY_SIGNATURE; 1606 profile.pcs = XYZ_TYPE; 1607 profile 1608 } 1609 1610 pub fn new_rgb_with_gamma_set( 1611 white_point: qcms_CIE_xyY, 1612 primaries: qcms_CIE_xyYTRIPLE, 1613 redGamma: f32, 1614 greenGamma: f32, 1615 blueGamma: f32, 1616 ) -> Option<Box<Profile>> { 1617 let mut profile = profile_create(); 1618 1619 //XXX: should store the whitepoint 1620 if !set_rgb_colorants(&mut profile, white_point, primaries) { 1621 return None; 1622 } 1623 profile.redTRC = Some(curve_from_gamma(redGamma)); 1624 profile.blueTRC = Some(curve_from_gamma(blueGamma)); 1625 profile.greenTRC = Some(curve_from_gamma(greenGamma)); 1626 profile.class_type = DISPLAY_DEVICE_PROFILE; 1627 profile.rendering_intent = Perceptual; 1628 profile.color_space = RGB_SIGNATURE; 1629 profile.pcs = XYZ_TYPE; 1630 Some(profile) 1631 } 1632 1633 pub fn new_from_path(file: &str) -> Option<Box<Profile>> { 1634 Profile::new_from_slice(&std::fs::read(file).ok()?, false) 1635 } 1636 1637 pub fn new_from_slice(mem: &[u8], curves_only: bool) -> Option<Box<Profile>> { 1638 let length: u32; 1639 let mut source: MemSource = MemSource { 1640 buf: mem, 1641 valid: false, 1642 invalid_reason: None, 1643 }; 1644 let index; 1645 source.valid = true; 1646 let src: &mut MemSource = &mut source; 1647 if mem.len() < 4 { 1648 return None; 1649 } 1650 length = read_u32(src, 0); 1651 if length as usize <= mem.len() { 1652 // shrink the area that we can read if appropriate 1653 src.buf = &src.buf[0..length as usize]; 1654 } else { 1655 return None; 1656 } 1657 /* ensure that the profile size is sane so it's easier to reason about */ 1658 if src.buf.len() <= 64 || src.buf.len() >= MAX_PROFILE_SIZE { 1659 return None; 1660 } 1661 let mut profile = profile_create(); 1662 1663 check_CMM_type_signature(src); 1664 check_profile_version(src); 1665 read_class_signature(&mut profile, src); 1666 read_rendering_intent(&mut profile, src); 1667 read_color_space(&mut profile, src); 1668 read_pcs(&mut profile, src); 1669 //TODO read rest of profile stuff 1670 if !src.valid { 1671 return None; 1672 } 1673 1674 index = read_tag_table(&mut profile, src); 1675 if !src.valid || index.is_empty() { 1676 return None; 1677 } 1678 1679 if let Some(chad) = find_tag(&index, TAG_CHAD) { 1680 profile.chromaticAdaption = Some(read_tag_s15Fixed16ArrayType(src, chad)) 1681 } else { 1682 profile.chromaticAdaption = None; //Signal the data is not present 1683 } 1684 1685 if profile.class_type == DISPLAY_DEVICE_PROFILE 1686 || profile.class_type == INPUT_DEVICE_PROFILE 1687 || profile.class_type == OUTPUT_DEVICE_PROFILE 1688 || profile.class_type == COLOR_SPACE_PROFILE 1689 { 1690 if profile.color_space == RGB_SIGNATURE { 1691 if !curves_only { 1692 if let Some(A2B0) = find_tag(&index, TAG_A2B0) { 1693 let lut_type = read_u32(src, A2B0.offset as usize); 1694 if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE { 1695 profile.A2B0 = read_tag_lutType(src, A2B0) 1696 } else if lut_type == LUT_MAB_TYPE { 1697 profile.mAB = read_tag_lutmABType(src, A2B0) 1698 } 1699 } 1700 if let Some(B2A0) = find_tag(&index, TAG_B2A0) { 1701 let lut_type = read_u32(src, B2A0.offset as usize); 1702 if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE { 1703 profile.B2A0 = read_tag_lutType(src, B2A0) 1704 } else if lut_type == LUT_MBA_TYPE { 1705 profile.mBA = read_tag_lutmABType(src, B2A0) 1706 } 1707 } 1708 } 1709 if find_tag(&index, TAG_rXYZ).is_some() || curves_only { 1710 profile.redColorant = read_tag_XYZType(src, &index, TAG_rXYZ); 1711 profile.greenColorant = read_tag_XYZType(src, &index, TAG_gXYZ); 1712 profile.blueColorant = read_tag_XYZType(src, &index, TAG_bXYZ) 1713 } 1714 if !src.valid { 1715 return None; 1716 } 1717 1718 if find_tag(&index, TAG_rTRC).is_some() || curves_only { 1719 profile.redTRC = read_tag_curveType(src, &index, TAG_rTRC); 1720 profile.greenTRC = read_tag_curveType(src, &index, TAG_gTRC); 1721 profile.blueTRC = read_tag_curveType(src, &index, TAG_bTRC); 1722 if profile.redTRC.is_none() 1723 || profile.blueTRC.is_none() 1724 || profile.greenTRC.is_none() 1725 { 1726 return None; 1727 } 1728 } 1729 } else if profile.color_space == GRAY_SIGNATURE { 1730 profile.grayTRC = read_tag_curveType(src, &index, TAG_kTRC); 1731 profile.grayTRC.as_ref()?; 1732 } else if profile.color_space == CMYK_SIGNATURE { 1733 if let Some(A2B0) = find_tag(&index, TAG_A2B0) { 1734 let lut_type = read_u32(src, A2B0.offset as usize); 1735 if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE { 1736 profile.A2B0 = read_tag_lutType(src, A2B0) 1737 } else if lut_type == LUT_MBA_TYPE { 1738 profile.mAB = read_tag_lutmABType(src, A2B0) 1739 } 1740 } 1741 } else { 1742 debug_assert!(false, "read_color_space protects against entering here"); 1743 return None; 1744 } 1745 } else { 1746 return None; 1747 } 1748 1749 if !src.valid { 1750 return None; 1751 } 1752 Some(profile) 1753 } 1754 /// Precomputes the information needed for this profile to be 1755 /// used as the output profile when constructing a `Transform`. 1756 pub fn precache_output_transform(&mut self) { 1757 crate::transform::qcms_profile_precache_output_transform(self); 1758 } 1759 }