transform.rs (59184B)
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 #![allow(clippy::missing_safety_doc)] 24 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))] 25 use crate::transform_neon::{ 26 qcms_transform_data_bgra_out_lut_neon, qcms_transform_data_rgb_out_lut_neon, 27 qcms_transform_data_rgba_out_lut_neon, 28 }; 29 use crate::{ 30 chain::chain_transform, 31 double_to_s15Fixed16Number, 32 iccread::SUPPORTS_ICCV4, 33 matrix::*, 34 transform_util::{ 35 build_colorant_matrix, build_input_gamma_table, build_output_lut, compute_precache, 36 lut_interp_linear, 37 }, 38 }; 39 use crate::{ 40 iccread::{qcms_CIE_xyY, qcms_CIE_xyYTRIPLE, Profile, GRAY_SIGNATURE, RGB_SIGNATURE}, 41 transform_util::clamp_float, 42 Intent, 43 }; 44 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 45 use crate::{ 46 transform_avx::{ 47 qcms_transform_data_bgra_out_lut_avx, qcms_transform_data_rgb_out_lut_avx, 48 qcms_transform_data_rgba_out_lut_avx, 49 }, 50 transform_sse2::{ 51 qcms_transform_data_bgra_out_lut_sse2, qcms_transform_data_rgb_out_lut_sse2, 52 qcms_transform_data_rgba_out_lut_sse2, 53 }, 54 }; 55 56 use std::sync::atomic::Ordering; 57 use std::sync::Arc; 58 #[cfg(all(target_arch = "arm", feature = "neon"))] 59 use std::arch::is_arm_feature_detected; 60 #[cfg(all(target_arch = "aarch64", feature = "neon"))] 61 use std::arch::is_aarch64_feature_detected; 62 63 pub const PRECACHE_OUTPUT_SIZE: usize = 8192; 64 pub const PRECACHE_OUTPUT_MAX: usize = PRECACHE_OUTPUT_SIZE - 1; 65 pub const FLOATSCALE: f32 = PRECACHE_OUTPUT_SIZE as f32; 66 pub const CLAMPMAXVAL: f32 = ((PRECACHE_OUTPUT_SIZE - 1) as f32) / PRECACHE_OUTPUT_SIZE as f32; 67 68 #[repr(C)] 69 #[derive(Debug)] 70 pub struct PrecacheOuput { 71 /* We previously used a count of 65536 here but that seems like more 72 * precision than we actually need. By reducing the size we can 73 * improve startup performance and reduce memory usage. ColorSync on 74 * 10.5 uses 4097 which is perhaps because they use a fixed point 75 * representation where 1. is represented by 0x1000. */ 76 pub lut_r: [u8; PRECACHE_OUTPUT_SIZE], 77 pub lut_g: [u8; PRECACHE_OUTPUT_SIZE], 78 pub lut_b: [u8; PRECACHE_OUTPUT_SIZE], 79 } 80 81 impl Default for PrecacheOuput { 82 fn default() -> PrecacheOuput { 83 PrecacheOuput { 84 lut_r: [0; PRECACHE_OUTPUT_SIZE], 85 lut_g: [0; PRECACHE_OUTPUT_SIZE], 86 lut_b: [0; PRECACHE_OUTPUT_SIZE], 87 } 88 } 89 } 90 91 /* used as a lookup table for the output transformation. 92 * we refcount them so we only need to have one around per output 93 * profile, instead of duplicating them per transform */ 94 95 #[repr(C)] 96 #[repr(align(16))] 97 #[derive(Clone, Default)] 98 pub struct qcms_transform { 99 pub matrix: [[f32; 4]; 3], 100 pub input_gamma_table_r: Option<Box<[f32; 256]>>, 101 pub input_gamma_table_g: Option<Box<[f32; 256]>>, 102 pub input_gamma_table_b: Option<Box<[f32; 256]>>, 103 pub input_clut_table_length: u16, 104 pub clut: Option<Vec<f32>>, 105 pub grid_size: u16, 106 pub output_clut_table_length: u16, 107 pub input_gamma_table_gray: Option<Box<[f32; 256]>>, 108 pub out_gamma_r: f32, 109 pub out_gamma_g: f32, 110 pub out_gamma_b: f32, 111 pub out_gamma_gray: f32, 112 pub output_gamma_lut_r: Option<Vec<u16>>, 113 pub output_gamma_lut_g: Option<Vec<u16>>, 114 pub output_gamma_lut_b: Option<Vec<u16>>, 115 pub output_gamma_lut_gray: Option<Vec<u16>>, 116 pub output_gamma_lut_r_length: usize, 117 pub output_gamma_lut_g_length: usize, 118 pub output_gamma_lut_b_length: usize, 119 pub output_gamma_lut_gray_length: usize, 120 pub precache_output: Option<Arc<PrecacheOuput>>, 121 pub transform_fn: transform_fn_t, 122 } 123 124 pub type transform_fn_t = 125 Option<unsafe fn(_: &qcms_transform, _: *const u8, _: *mut u8, _: usize) -> ()>; 126 /// The format of pixel data 127 #[repr(u32)] 128 #[derive(PartialEq, Eq, Clone, Copy)] 129 #[allow(clippy::upper_case_acronyms)] 130 pub enum DataType { 131 RGB8 = 0, 132 RGBA8 = 1, 133 BGRA8 = 2, 134 Gray8 = 3, 135 GrayA8 = 4, 136 CMYK = 5, 137 } 138 139 impl DataType { 140 pub fn bytes_per_pixel(&self) -> usize { 141 match self { 142 RGB8 => 3, 143 RGBA8 => 4, 144 BGRA8 => 4, 145 Gray8 => 1, 146 GrayA8 => 2, 147 CMYK => 4, 148 } 149 } 150 } 151 152 use DataType::*; 153 154 #[repr(C)] 155 #[derive(Copy, Clone)] 156 #[allow(clippy::upper_case_acronyms)] 157 pub struct CIE_XYZ { 158 pub X: f64, 159 pub Y: f64, 160 pub Z: f64, 161 } 162 163 pub trait Format { 164 const kRIndex: usize; 165 const kGIndex: usize; 166 const kBIndex: usize; 167 const kAIndex: usize; 168 } 169 170 #[allow(clippy::upper_case_acronyms)] 171 pub struct BGRA; 172 impl Format for BGRA { 173 const kBIndex: usize = 0; 174 const kGIndex: usize = 1; 175 const kRIndex: usize = 2; 176 const kAIndex: usize = 3; 177 } 178 179 #[allow(clippy::upper_case_acronyms)] 180 pub struct RGBA; 181 impl Format for RGBA { 182 const kRIndex: usize = 0; 183 const kGIndex: usize = 1; 184 const kBIndex: usize = 2; 185 const kAIndex: usize = 3; 186 } 187 188 #[allow(clippy::upper_case_acronyms)] 189 pub struct RGB; 190 impl Format for RGB { 191 const kRIndex: usize = 0; 192 const kGIndex: usize = 1; 193 const kBIndex: usize = 2; 194 const kAIndex: usize = 0xFF; 195 } 196 197 pub trait GrayFormat { 198 const has_alpha: bool; 199 } 200 201 pub struct Gray; 202 impl GrayFormat for Gray { 203 const has_alpha: bool = false; 204 } 205 206 pub struct GrayAlpha; 207 impl GrayFormat for GrayAlpha { 208 const has_alpha: bool = true; 209 } 210 211 #[inline] 212 fn clamp_u8(v: f32) -> u8 { 213 if v > 255. { 214 255 215 } else if v < 0. { 216 0 217 } else { 218 (v + 0.5).floor() as u8 219 } 220 } 221 222 // Build a White point, primary chromas transfer matrix from RGB to CIE XYZ 223 // This is just an approximation, I am not handling all the non-linear 224 // aspects of the RGB to XYZ process, and assumming that the gamma correction 225 // has transitive property in the tranformation chain. 226 // 227 // the alghoritm: 228 // 229 // - First I build the absolute conversion matrix using 230 // primaries in XYZ. This matrix is next inverted 231 // - Then I eval the source white point across this matrix 232 // obtaining the coeficients of the transformation 233 // - Then, I apply these coeficients to the original matrix 234 fn build_RGB_to_XYZ_transfer_matrix( 235 white: qcms_CIE_xyY, 236 primrs: qcms_CIE_xyYTRIPLE, 237 ) -> Option<Matrix> { 238 let xn = white.x; 239 let yn = white.y; 240 if yn == 0.0 { 241 return None; 242 } 243 244 let xr = primrs.red.x; 245 let yr = primrs.red.y; 246 let xg = primrs.green.x; 247 let yg = primrs.green.y; 248 let xb = primrs.blue.x; 249 let yb = primrs.blue.y; 250 let primaries = Matrix { 251 m: [ 252 [xr as f32, xg as f32, xb as f32], 253 [yr as f32, yg as f32, yb as f32], 254 [ 255 (1. - xr - yr) as f32, 256 (1. - xg - yg) as f32, 257 (1. - xb - yb) as f32, 258 ], 259 ], 260 }; 261 let white_point = Vector { 262 v: [(xn / yn) as f32, 1., ((1. - xn - yn) / yn) as f32], 263 }; 264 let primaries_invert = primaries.invert()?; 265 266 let coefs = primaries_invert.eval(white_point); 267 Some(Matrix { 268 m: [ 269 [ 270 (coefs.v[0] as f64 * xr) as f32, 271 (coefs.v[1] as f64 * xg) as f32, 272 (coefs.v[2] as f64 * xb) as f32, 273 ], 274 [ 275 (coefs.v[0] as f64 * yr) as f32, 276 (coefs.v[1] as f64 * yg) as f32, 277 (coefs.v[2] as f64 * yb) as f32, 278 ], 279 [ 280 (coefs.v[0] as f64 * (1. - xr - yr)) as f32, 281 (coefs.v[1] as f64 * (1. - xg - yg)) as f32, 282 (coefs.v[2] as f64 * (1. - xb - yb)) as f32, 283 ], 284 ], 285 }) 286 } 287 288 /* CIE Illuminant D50 */ 289 const D50_XYZ: CIE_XYZ = CIE_XYZ { 290 X: 0.9642, 291 Y: 1.0000, 292 Z: 0.8249, 293 }; 294 295 /* from lcms: xyY2XYZ() 296 * corresponds to argyll: icmYxy2XYZ() */ 297 fn xyY2XYZ(source: qcms_CIE_xyY) -> CIE_XYZ { 298 CIE_XYZ { 299 X: source.x / source.y * source.Y, 300 Y: source.Y, 301 Z: (1. - source.x - source.y) / source.y * source.Y, 302 } 303 } 304 305 /* from lcms: ComputeChromaticAdaption */ 306 // Compute chromatic adaption matrix using chad as cone matrix 307 fn compute_chromatic_adaption( 308 source_white_point: CIE_XYZ, 309 dest_white_point: CIE_XYZ, 310 chad: Matrix, 311 ) -> Option<Matrix> { 312 let cone_source_XYZ = Vector { 313 v: [ 314 source_white_point.X as f32, 315 source_white_point.Y as f32, 316 source_white_point.Z as f32, 317 ], 318 }; 319 let cone_source_rgb = chad.eval(cone_source_XYZ); 320 321 let cone_dest_XYZ = Vector { 322 v: [ 323 dest_white_point.X as f32, 324 dest_white_point.Y as f32, 325 dest_white_point.Z as f32, 326 ], 327 }; 328 let cone_dest_rgb = chad.eval(cone_dest_XYZ); 329 330 let cone = Matrix { 331 m: [ 332 [cone_dest_rgb.v[0] / cone_source_rgb.v[0], 0., 0.], 333 [0., cone_dest_rgb.v[1] / cone_source_rgb.v[1], 0.], 334 [0., 0., cone_dest_rgb.v[2] / cone_source_rgb.v[2]], 335 ], 336 }; 337 338 let chad_inv = chad.invert()?; 339 340 // Normalize 341 Some(Matrix::multiply(chad_inv, Matrix::multiply(cone, chad))) 342 } 343 344 /* from lcms: cmsAdaptionMatrix */ 345 // Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll 346 // Bradford is assumed 347 fn adaption_matrix(source_illumination: CIE_XYZ, target_illumination: CIE_XYZ) -> Option<Matrix> { 348 let lam_rigg = { 349 Matrix { 350 m: [ 351 [0.8951, 0.2664, -0.1614], 352 [-0.7502, 1.7135, 0.0367], 353 [0.0389, -0.0685, 1.0296], 354 ], 355 } 356 }; 357 compute_chromatic_adaption(source_illumination, target_illumination, lam_rigg) 358 } 359 /* from lcms: cmsAdaptMatrixToD50 */ 360 fn adapt_matrix_to_D50(r: Option<Matrix>, source_white_pt: qcms_CIE_xyY) -> Option<Matrix> { 361 if source_white_pt.y == 0.0 { 362 return None; 363 } 364 365 let Dn: CIE_XYZ = xyY2XYZ(source_white_pt); 366 let Bradford = adaption_matrix(Dn, D50_XYZ)?; 367 Some(Matrix::multiply(Bradford, r?)) 368 } 369 pub(crate) fn set_rgb_colorants( 370 profile: &mut Profile, 371 white_point: qcms_CIE_xyY, 372 primaries: qcms_CIE_xyYTRIPLE, 373 ) -> bool { 374 let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries); 375 let colorants = match adapt_matrix_to_D50(colorants, white_point) { 376 Some(colorants) => colorants, 377 None => return false, 378 }; 379 380 /* note: there's a transpose type of operation going on here */ 381 profile.redColorant.X = double_to_s15Fixed16Number(colorants.m[0][0] as f64); 382 profile.redColorant.Y = double_to_s15Fixed16Number(colorants.m[1][0] as f64); 383 profile.redColorant.Z = double_to_s15Fixed16Number(colorants.m[2][0] as f64); 384 profile.greenColorant.X = double_to_s15Fixed16Number(colorants.m[0][1] as f64); 385 profile.greenColorant.Y = double_to_s15Fixed16Number(colorants.m[1][1] as f64); 386 profile.greenColorant.Z = double_to_s15Fixed16Number(colorants.m[2][1] as f64); 387 profile.blueColorant.X = double_to_s15Fixed16Number(colorants.m[0][2] as f64); 388 profile.blueColorant.Y = double_to_s15Fixed16Number(colorants.m[1][2] as f64); 389 profile.blueColorant.Z = double_to_s15Fixed16Number(colorants.m[2][2] as f64); 390 true 391 } 392 pub(crate) fn get_rgb_colorants( 393 white_point: qcms_CIE_xyY, 394 primaries: qcms_CIE_xyYTRIPLE, 395 ) -> Option<Matrix> { 396 let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries); 397 adapt_matrix_to_D50(colorants, white_point) 398 } 399 /* Alpha is not corrected. 400 A rationale for this is found in Alvy Ray's "Should Alpha Be Nonlinear If 401 RGB Is?" Tech Memo 17 (December 14, 1998). 402 See: ftp://ftp.alvyray.com/Acrobat/17_Nonln.pdf 403 */ 404 unsafe extern "C" fn qcms_transform_data_gray_template_lut<I: GrayFormat, F: Format>( 405 transform: &qcms_transform, 406 mut src: *const u8, 407 mut dest: *mut u8, 408 length: usize, 409 ) { 410 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32; 411 let input_gamma_table_gray = transform.input_gamma_table_gray.as_ref().unwrap(); 412 413 let mut i: u32 = 0; 414 while (i as usize) < length { 415 let device: u8 = *src; 416 src = src.offset(1); 417 let mut alpha: u8 = 0xffu8; 418 if I::has_alpha { 419 alpha = *src; 420 src = src.offset(1); 421 } 422 let linear: f32 = input_gamma_table_gray[device as usize]; 423 424 let out_device_r: f32 = lut_interp_linear( 425 linear as f64, 426 &transform.output_gamma_lut_r.as_ref().unwrap(), 427 ); 428 let out_device_g: f32 = lut_interp_linear( 429 linear as f64, 430 &transform.output_gamma_lut_g.as_ref().unwrap(), 431 ); 432 let out_device_b: f32 = lut_interp_linear( 433 linear as f64, 434 &transform.output_gamma_lut_b.as_ref().unwrap(), 435 ); 436 *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32); 437 *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32); 438 *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32); 439 if F::kAIndex != 0xff { 440 *dest.add(F::kAIndex) = alpha 441 } 442 dest = dest.offset(components as isize); 443 i += 1 444 } 445 } 446 unsafe fn qcms_transform_data_gray_out_lut( 447 transform: &qcms_transform, 448 src: *const u8, 449 dest: *mut u8, 450 length: usize, 451 ) { 452 qcms_transform_data_gray_template_lut::<Gray, RGB>(transform, src, dest, length); 453 } 454 unsafe fn qcms_transform_data_gray_rgba_out_lut( 455 transform: &qcms_transform, 456 src: *const u8, 457 dest: *mut u8, 458 length: usize, 459 ) { 460 qcms_transform_data_gray_template_lut::<Gray, RGBA>(transform, src, dest, length); 461 } 462 unsafe fn qcms_transform_data_gray_bgra_out_lut( 463 transform: &qcms_transform, 464 src: *const u8, 465 dest: *mut u8, 466 length: usize, 467 ) { 468 qcms_transform_data_gray_template_lut::<Gray, BGRA>(transform, src, dest, length); 469 } 470 unsafe fn qcms_transform_data_graya_rgba_out_lut( 471 transform: &qcms_transform, 472 src: *const u8, 473 dest: *mut u8, 474 length: usize, 475 ) { 476 qcms_transform_data_gray_template_lut::<GrayAlpha, RGBA>(transform, src, dest, length); 477 } 478 unsafe fn qcms_transform_data_graya_bgra_out_lut( 479 transform: &qcms_transform, 480 src: *const u8, 481 dest: *mut u8, 482 length: usize, 483 ) { 484 qcms_transform_data_gray_template_lut::<GrayAlpha, BGRA>(transform, src, dest, length); 485 } 486 unsafe fn qcms_transform_data_gray_template_precache<I: GrayFormat, F: Format>( 487 transform: &qcms_transform, 488 mut src: *const u8, 489 mut dest: *mut u8, 490 length: usize, 491 ) { 492 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32; 493 let precache_output = transform.precache_output.as_deref().unwrap(); 494 let output_r = &precache_output.lut_r; 495 let output_g = &precache_output.lut_g; 496 let output_b = &precache_output.lut_b; 497 498 let input_gamma_table_gray = transform 499 .input_gamma_table_gray 500 .as_ref() 501 .unwrap() 502 .as_ptr(); 503 504 let mut i: u32 = 0; 505 while (i as usize) < length { 506 let device: u8 = *src; 507 src = src.offset(1); 508 let mut alpha: u8 = 0xffu8; 509 if I::has_alpha { 510 alpha = *src; 511 src = src.offset(1); 512 } 513 514 let linear: f32 = *input_gamma_table_gray.offset(device as isize); 515 /* we could round here... */ 516 let gray: u16 = (linear * PRECACHE_OUTPUT_MAX as f32) as u16; 517 *dest.add(F::kRIndex) = output_r[gray as usize]; 518 *dest.add(F::kGIndex) = output_g[gray as usize]; 519 *dest.add(F::kBIndex) = output_b[gray as usize]; 520 if F::kAIndex != 0xff { 521 *dest.add(F::kAIndex) = alpha 522 } 523 dest = dest.offset(components as isize); 524 i += 1 525 } 526 } 527 unsafe fn qcms_transform_data_gray_out_precache( 528 transform: &qcms_transform, 529 src: *const u8, 530 dest: *mut u8, 531 length: usize, 532 ) { 533 qcms_transform_data_gray_template_precache::<Gray, RGB>(transform, src, dest, length); 534 } 535 unsafe fn qcms_transform_data_gray_rgba_out_precache( 536 transform: &qcms_transform, 537 src: *const u8, 538 dest: *mut u8, 539 length: usize, 540 ) { 541 qcms_transform_data_gray_template_precache::<Gray, RGBA>(transform, src, dest, length); 542 } 543 unsafe fn qcms_transform_data_gray_bgra_out_precache( 544 transform: &qcms_transform, 545 src: *const u8, 546 dest: *mut u8, 547 length: usize, 548 ) { 549 qcms_transform_data_gray_template_precache::<Gray, BGRA>(transform, src, dest, length); 550 } 551 unsafe fn qcms_transform_data_graya_rgba_out_precache( 552 transform: &qcms_transform, 553 src: *const u8, 554 dest: *mut u8, 555 length: usize, 556 ) { 557 qcms_transform_data_gray_template_precache::<GrayAlpha, RGBA>(transform, src, dest, length); 558 } 559 unsafe fn qcms_transform_data_graya_bgra_out_precache( 560 transform: &qcms_transform, 561 src: *const u8, 562 dest: *mut u8, 563 length: usize, 564 ) { 565 qcms_transform_data_gray_template_precache::<GrayAlpha, BGRA>(transform, src, dest, length); 566 } 567 unsafe fn qcms_transform_data_template_lut_precache<F: Format>( 568 transform: &qcms_transform, 569 mut src: *const u8, 570 mut dest: *mut u8, 571 length: usize, 572 ) { 573 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32; 574 let output_table_r = &transform.precache_output.as_deref().unwrap().lut_r; 575 let output_table_g = &transform.precache_output.as_deref().unwrap().lut_g; 576 let output_table_b = &transform.precache_output.as_deref().unwrap().lut_b; 577 let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr(); 578 let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr(); 579 let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr(); 580 581 let mat = &transform.matrix; 582 let mut i: u32 = 0; 583 while (i as usize) < length { 584 let device_r: u8 = *src.add(F::kRIndex); 585 let device_g: u8 = *src.add(F::kGIndex); 586 let device_b: u8 = *src.add(F::kBIndex); 587 let mut alpha: u8 = 0; 588 if F::kAIndex != 0xff { 589 alpha = *src.add(F::kAIndex) 590 } 591 src = src.offset(components as isize); 592 593 let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize); 594 let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize); 595 let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize); 596 let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b; 597 let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b; 598 let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b; 599 out_linear_r = clamp_float(out_linear_r); 600 out_linear_g = clamp_float(out_linear_g); 601 out_linear_b = clamp_float(out_linear_b); 602 /* we could round here... */ 603 604 let r: u16 = (out_linear_r * PRECACHE_OUTPUT_MAX as f32) as u16; 605 let g: u16 = (out_linear_g * PRECACHE_OUTPUT_MAX as f32) as u16; 606 let b: u16 = (out_linear_b * PRECACHE_OUTPUT_MAX as f32) as u16; 607 *dest.add(F::kRIndex) = output_table_r[r as usize]; 608 *dest.add(F::kGIndex) = output_table_g[g as usize]; 609 *dest.add(F::kBIndex) = output_table_b[b as usize]; 610 if F::kAIndex != 0xff { 611 *dest.add(F::kAIndex) = alpha 612 } 613 dest = dest.offset(components as isize); 614 i += 1 615 } 616 } 617 #[no_mangle] 618 pub unsafe fn qcms_transform_data_rgb_out_lut_precache( 619 transform: &qcms_transform, 620 src: *const u8, 621 dest: *mut u8, 622 length: usize, 623 ) { 624 qcms_transform_data_template_lut_precache::<RGB>(transform, src, dest, length); 625 } 626 #[no_mangle] 627 pub unsafe fn qcms_transform_data_rgba_out_lut_precache( 628 transform: &qcms_transform, 629 src: *const u8, 630 dest: *mut u8, 631 length: usize, 632 ) { 633 qcms_transform_data_template_lut_precache::<RGBA>(transform, src, dest, length); 634 } 635 #[no_mangle] 636 pub unsafe fn qcms_transform_data_bgra_out_lut_precache( 637 transform: &qcms_transform, 638 src: *const u8, 639 dest: *mut u8, 640 length: usize, 641 ) { 642 qcms_transform_data_template_lut_precache::<BGRA>(transform, src, dest, length); 643 } 644 // Not used 645 /* 646 static void qcms_transform_data_clut(const qcms_transform *transform, const unsigned char *src, unsigned char *dest, size_t length) { 647 unsigned int i; 648 int xy_len = 1; 649 int x_len = transform->grid_size; 650 int len = x_len * x_len; 651 const float* r_table = transform->r_clut; 652 const float* g_table = transform->g_clut; 653 const float* b_table = transform->b_clut; 654 655 for (i = 0; i < length; i++) { 656 unsigned char in_r = *src++; 657 unsigned char in_g = *src++; 658 unsigned char in_b = *src++; 659 float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f; 660 661 int x = floorf(linear_r * (transform->grid_size-1)); 662 int y = floorf(linear_g * (transform->grid_size-1)); 663 int z = floorf(linear_b * (transform->grid_size-1)); 664 int x_n = ceilf(linear_r * (transform->grid_size-1)); 665 int y_n = ceilf(linear_g * (transform->grid_size-1)); 666 int z_n = ceilf(linear_b * (transform->grid_size-1)); 667 float x_d = linear_r * (transform->grid_size-1) - x; 668 float y_d = linear_g * (transform->grid_size-1) - y; 669 float z_d = linear_b * (transform->grid_size-1) - z; 670 671 float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d); 672 float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d); 673 float r_y1 = lerp(r_x1, r_x2, y_d); 674 float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d); 675 float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d); 676 float r_y2 = lerp(r_x3, r_x4, y_d); 677 float clut_r = lerp(r_y1, r_y2, z_d); 678 679 float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d); 680 float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d); 681 float g_y1 = lerp(g_x1, g_x2, y_d); 682 float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d); 683 float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d); 684 float g_y2 = lerp(g_x3, g_x4, y_d); 685 float clut_g = lerp(g_y1, g_y2, z_d); 686 687 float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d); 688 float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d); 689 float b_y1 = lerp(b_x1, b_x2, y_d); 690 float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d); 691 float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d); 692 float b_y2 = lerp(b_x3, b_x4, y_d); 693 float clut_b = lerp(b_y1, b_y2, z_d); 694 695 *dest++ = clamp_u8(clut_r*255.0f); 696 *dest++ = clamp_u8(clut_g*255.0f); 697 *dest++ = clamp_u8(clut_b*255.0f); 698 } 699 } 700 */ 701 fn int_div_ceil(value: i32, div: i32) -> i32 { 702 (value + div - 1) / div 703 } 704 // Using lcms' tetra interpolation algorithm. 705 unsafe extern "C" fn qcms_transform_data_tetra_clut_template<F: Format>( 706 transform: &qcms_transform, 707 mut src: *const u8, 708 mut dest: *mut u8, 709 length: usize, 710 ) { 711 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32; 712 713 let xy_len: i32 = 1; 714 let x_len: i32 = transform.grid_size as i32; 715 let len: i32 = x_len * x_len; 716 let table = transform.clut.as_ref().unwrap().as_ptr(); 717 let r_table: *const f32 = table; 718 let g_table: *const f32 = table.offset(1); 719 let b_table: *const f32 = table.offset(2); 720 721 let mut i: u32 = 0; 722 while (i as usize) < length { 723 let c0_r: f32; 724 let c1_r: f32; 725 let c2_r: f32; 726 let c3_r: f32; 727 let c0_g: f32; 728 let c1_g: f32; 729 let c2_g: f32; 730 let c3_g: f32; 731 let c0_b: f32; 732 let c1_b: f32; 733 let c2_b: f32; 734 let c3_b: f32; 735 let in_r: u8 = *src.add(F::kRIndex); 736 let in_g: u8 = *src.add(F::kGIndex); 737 let in_b: u8 = *src.add(F::kBIndex); 738 let mut in_a: u8 = 0; 739 if F::kAIndex != 0xff { 740 in_a = *src.add(F::kAIndex) 741 } 742 src = src.offset(components as isize); 743 let linear_r: f32 = in_r as i32 as f32 / 255.0; 744 let linear_g: f32 = in_g as i32 as f32 / 255.0; 745 let linear_b: f32 = in_b as i32 as f32 / 255.0; 746 let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255; 747 let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255; 748 let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255; 749 let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255); 750 let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255); 751 let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255); 752 let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32; 753 let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32; 754 let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32; 755 let CLU = |table: *const f32, x, y, z| { 756 *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize) 757 }; 758 759 c0_r = CLU(r_table, x, y, z); 760 c0_g = CLU(g_table, x, y, z); 761 c0_b = CLU(b_table, x, y, z); 762 if rx >= ry { 763 if ry >= rz { 764 //rx >= ry && ry >= rz 765 c1_r = CLU(r_table, x_n, y, z) - c0_r; 766 c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z); 767 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); 768 c1_g = CLU(g_table, x_n, y, z) - c0_g; 769 c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z); 770 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); 771 c1_b = CLU(b_table, x_n, y, z) - c0_b; 772 c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z); 773 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); 774 } else if rx >= rz { 775 //rx >= rz && rz >= ry 776 c1_r = CLU(r_table, x_n, y, z) - c0_r; 777 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); 778 c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z); 779 c1_g = CLU(g_table, x_n, y, z) - c0_g; 780 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); 781 c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z); 782 c1_b = CLU(b_table, x_n, y, z) - c0_b; 783 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); 784 c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z); 785 } else { 786 //rz > rx && rx >= ry 787 c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n); 788 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); 789 c3_r = CLU(r_table, x, y, z_n) - c0_r; 790 c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n); 791 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); 792 c3_g = CLU(g_table, x, y, z_n) - c0_g; 793 c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n); 794 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); 795 c3_b = CLU(b_table, x, y, z_n) - c0_b; 796 } 797 } else if rx >= rz { 798 //ry > rx && rx >= rz 799 c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z); 800 c2_r = CLU(r_table, x, y_n, z) - c0_r; 801 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); 802 c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z); 803 c2_g = CLU(g_table, x, y_n, z) - c0_g; 804 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); 805 c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z); 806 c2_b = CLU(b_table, x, y_n, z) - c0_b; 807 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); 808 } else if ry >= rz { 809 //ry >= rz && rz > rx 810 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); 811 c2_r = CLU(r_table, x, y_n, z) - c0_r; 812 c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z); 813 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); 814 c2_g = CLU(g_table, x, y_n, z) - c0_g; 815 c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z); 816 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); 817 c2_b = CLU(b_table, x, y_n, z) - c0_b; 818 c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z); 819 } else { 820 //rz > ry && ry > rx 821 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); 822 c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n); 823 c3_r = CLU(r_table, x, y, z_n) - c0_r; 824 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); 825 c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n); 826 c3_g = CLU(g_table, x, y, z_n) - c0_g; 827 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); 828 c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n); 829 c3_b = CLU(b_table, x, y, z_n) - c0_b; 830 } 831 let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz; 832 let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz; 833 let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz; 834 *dest.add(F::kRIndex) = clamp_u8(clut_r * 255.0); 835 *dest.add(F::kGIndex) = clamp_u8(clut_g * 255.0); 836 *dest.add(F::kBIndex) = clamp_u8(clut_b * 255.0); 837 if F::kAIndex != 0xff { 838 *dest.add(F::kAIndex) = in_a 839 } 840 dest = dest.offset(components as isize); 841 i += 1 842 } 843 } 844 845 unsafe fn tetra( 846 transform: &qcms_transform, 847 table: *const f32, 848 in_r: u8, 849 in_g: u8, 850 in_b: u8, 851 ) -> (f32, f32, f32) { 852 let r_table: *const f32 = table; 853 let g_table: *const f32 = table.offset(1); 854 let b_table: *const f32 = table.offset(2); 855 let linear_r: f32 = in_r as i32 as f32 / 255.0; 856 let linear_g: f32 = in_g as i32 as f32 / 255.0; 857 let linear_b: f32 = in_b as i32 as f32 / 255.0; 858 let xy_len: i32 = 1; 859 let x_len: i32 = transform.grid_size as i32; 860 let len: i32 = x_len * x_len; 861 let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255; 862 let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255; 863 let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255; 864 let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255); 865 let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255); 866 let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255); 867 let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32; 868 let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32; 869 let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32; 870 let CLU = |table: *const f32, x, y, z| { 871 *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize) 872 }; 873 let c0_r: f32; 874 let c1_r: f32; 875 let c2_r: f32; 876 let c3_r: f32; 877 let c0_g: f32; 878 let c1_g: f32; 879 let c2_g: f32; 880 let c3_g: f32; 881 let c0_b: f32; 882 let c1_b: f32; 883 let c2_b: f32; 884 let c3_b: f32; 885 c0_r = CLU(r_table, x, y, z); 886 c0_g = CLU(g_table, x, y, z); 887 c0_b = CLU(b_table, x, y, z); 888 if rx >= ry { 889 if ry >= rz { 890 //rx >= ry && ry >= rz 891 c1_r = CLU(r_table, x_n, y, z) - c0_r; 892 c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z); 893 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); 894 c1_g = CLU(g_table, x_n, y, z) - c0_g; 895 c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z); 896 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); 897 c1_b = CLU(b_table, x_n, y, z) - c0_b; 898 c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z); 899 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); 900 } else if rx >= rz { 901 //rx >= rz && rz >= ry 902 c1_r = CLU(r_table, x_n, y, z) - c0_r; 903 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); 904 c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z); 905 c1_g = CLU(g_table, x_n, y, z) - c0_g; 906 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); 907 c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z); 908 c1_b = CLU(b_table, x_n, y, z) - c0_b; 909 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); 910 c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z); 911 } else { 912 //rz > rx && rx >= ry 913 c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n); 914 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); 915 c3_r = CLU(r_table, x, y, z_n) - c0_r; 916 c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n); 917 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); 918 c3_g = CLU(g_table, x, y, z_n) - c0_g; 919 c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n); 920 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); 921 c3_b = CLU(b_table, x, y, z_n) - c0_b; 922 } 923 } else if rx >= rz { 924 //ry > rx && rx >= rz 925 c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z); 926 c2_r = CLU(r_table, x, y_n, z) - c0_r; 927 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); 928 c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z); 929 c2_g = CLU(g_table, x, y_n, z) - c0_g; 930 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); 931 c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z); 932 c2_b = CLU(b_table, x, y_n, z) - c0_b; 933 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); 934 } else if ry >= rz { 935 //ry >= rz && rz > rx 936 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); 937 c2_r = CLU(r_table, x, y_n, z) - c0_r; 938 c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z); 939 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); 940 c2_g = CLU(g_table, x, y_n, z) - c0_g; 941 c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z); 942 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); 943 c2_b = CLU(b_table, x, y_n, z) - c0_b; 944 c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z); 945 } else { 946 //rz > ry && ry > rx 947 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); 948 c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n); 949 c3_r = CLU(r_table, x, y, z_n) - c0_r; 950 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); 951 c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n); 952 c3_g = CLU(g_table, x, y, z_n) - c0_g; 953 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); 954 c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n); 955 c3_b = CLU(b_table, x, y, z_n) - c0_b; 956 } 957 let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz; 958 let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz; 959 let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz; 960 (clut_r, clut_g, clut_b) 961 } 962 963 #[inline] 964 fn lerp(a: f32, b: f32, t: f32) -> f32 { 965 a * (1. - t) + b * t 966 } 967 968 // lerp between two tetrahedral interpolations 969 // See lcms:Eval4InputsFloat 970 #[allow(clippy::many_single_char_names)] 971 unsafe fn qcms_transform_data_tetra_clut_cmyk( 972 transform: &qcms_transform, 973 mut src: *const u8, 974 mut dest: *mut u8, 975 length: usize, 976 ) { 977 let table = transform.clut.as_ref().unwrap().as_ptr(); 978 assert!( 979 transform.clut.as_ref().unwrap().len() 980 >= ((transform.grid_size as i32).pow(4) * 3) as usize 981 ); 982 for _ in 0..length { 983 let c: u8 = *src.add(0); 984 let m: u8 = *src.add(1); 985 let y: u8 = *src.add(2); 986 let k: u8 = *src.add(3); 987 src = src.offset(4); 988 let linear_k: f32 = k as i32 as f32 / 255.0; 989 let grid_size = transform.grid_size as i32; 990 let w: i32 = k as i32 * (transform.grid_size as i32 - 1) / 255; 991 let w_n: i32 = int_div_ceil(k as i32 * (transform.grid_size as i32 - 1), 255); 992 let t: f32 = linear_k * (transform.grid_size as i32 - 1) as f32 - w as f32; 993 994 let table1 = table.offset((w * grid_size * grid_size * grid_size * 3) as isize); 995 let table2 = table.offset((w_n * grid_size * grid_size * grid_size * 3) as isize); 996 997 let (r1, g1, b1) = tetra(transform, table1, c, m, y); 998 let (r2, g2, b2) = tetra(transform, table2, c, m, y); 999 let r = lerp(r1, r2, t); 1000 let g = lerp(g1, g2, t); 1001 let b = lerp(b1, b2, t); 1002 *dest.add(0) = clamp_u8(r * 255.0); 1003 *dest.add(1) = clamp_u8(g * 255.0); 1004 *dest.add(2) = clamp_u8(b * 255.0); 1005 dest = dest.offset(3); 1006 } 1007 } 1008 1009 unsafe fn qcms_transform_data_tetra_clut_rgb( 1010 transform: &qcms_transform, 1011 src: *const u8, 1012 dest: *mut u8, 1013 length: usize, 1014 ) { 1015 qcms_transform_data_tetra_clut_template::<RGB>(transform, src, dest, length); 1016 } 1017 unsafe fn qcms_transform_data_tetra_clut_rgba( 1018 transform: &qcms_transform, 1019 src: *const u8, 1020 dest: *mut u8, 1021 length: usize, 1022 ) { 1023 qcms_transform_data_tetra_clut_template::<RGBA>(transform, src, dest, length); 1024 } 1025 unsafe fn qcms_transform_data_tetra_clut_bgra( 1026 transform: &qcms_transform, 1027 src: *const u8, 1028 dest: *mut u8, 1029 length: usize, 1030 ) { 1031 qcms_transform_data_tetra_clut_template::<BGRA>(transform, src, dest, length); 1032 } 1033 unsafe fn qcms_transform_data_template_lut<F: Format>( 1034 transform: &qcms_transform, 1035 mut src: *const u8, 1036 mut dest: *mut u8, 1037 length: usize, 1038 ) { 1039 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32; 1040 1041 let mat = &transform.matrix; 1042 let mut i: u32 = 0; 1043 let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr(); 1044 let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr(); 1045 let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr(); 1046 while (i as usize) < length { 1047 let device_r: u8 = *src.add(F::kRIndex); 1048 let device_g: u8 = *src.add(F::kGIndex); 1049 let device_b: u8 = *src.add(F::kBIndex); 1050 let mut alpha: u8 = 0; 1051 if F::kAIndex != 0xff { 1052 alpha = *src.add(F::kAIndex) 1053 } 1054 src = src.offset(components as isize); 1055 1056 let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize); 1057 let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize); 1058 let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize); 1059 let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b; 1060 let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b; 1061 let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b; 1062 out_linear_r = clamp_float(out_linear_r); 1063 out_linear_g = clamp_float(out_linear_g); 1064 out_linear_b = clamp_float(out_linear_b); 1065 1066 let out_device_r: f32 = lut_interp_linear( 1067 out_linear_r as f64, 1068 &transform.output_gamma_lut_r.as_ref().unwrap(), 1069 ); 1070 let out_device_g: f32 = lut_interp_linear( 1071 out_linear_g as f64, 1072 transform.output_gamma_lut_g.as_ref().unwrap(), 1073 ); 1074 let out_device_b: f32 = lut_interp_linear( 1075 out_linear_b as f64, 1076 transform.output_gamma_lut_b.as_ref().unwrap(), 1077 ); 1078 *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32); 1079 *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32); 1080 *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32); 1081 if F::kAIndex != 0xff { 1082 *dest.add(F::kAIndex) = alpha 1083 } 1084 dest = dest.offset(components as isize); 1085 i += 1 1086 } 1087 } 1088 #[no_mangle] 1089 pub unsafe fn qcms_transform_data_rgb_out_lut( 1090 transform: &qcms_transform, 1091 src: *const u8, 1092 dest: *mut u8, 1093 length: usize, 1094 ) { 1095 qcms_transform_data_template_lut::<RGB>(transform, src, dest, length); 1096 } 1097 #[no_mangle] 1098 pub unsafe fn qcms_transform_data_rgba_out_lut( 1099 transform: &qcms_transform, 1100 src: *const u8, 1101 dest: *mut u8, 1102 length: usize, 1103 ) { 1104 qcms_transform_data_template_lut::<RGBA>(transform, src, dest, length); 1105 } 1106 #[no_mangle] 1107 pub unsafe fn qcms_transform_data_bgra_out_lut( 1108 transform: &qcms_transform, 1109 src: *const u8, 1110 dest: *mut u8, 1111 length: usize, 1112 ) { 1113 qcms_transform_data_template_lut::<BGRA>(transform, src, dest, length); 1114 } 1115 1116 fn precache_create() -> Arc<PrecacheOuput> { 1117 Arc::new(PrecacheOuput::default()) 1118 } 1119 1120 #[no_mangle] 1121 pub unsafe extern "C" fn qcms_transform_release(t: *mut qcms_transform) { 1122 drop(Box::from_raw(t)); 1123 } 1124 1125 const bradford_matrix: Matrix = Matrix { 1126 m: [ 1127 [0.8951, 0.2664, -0.1614], 1128 [-0.7502, 1.7135, 0.0367], 1129 [0.0389, -0.0685, 1.0296], 1130 ], 1131 }; 1132 1133 const bradford_matrix_inv: Matrix = Matrix { 1134 m: [ 1135 [0.9869929, -0.1470543, 0.1599627], 1136 [0.4323053, 0.5183603, 0.0492912], 1137 [-0.0085287, 0.0400428, 0.9684867], 1138 ], 1139 }; 1140 1141 // See ICCv4 E.3 1142 fn compute_whitepoint_adaption(X: f32, Y: f32, Z: f32) -> Matrix { 1143 let p: f32 = (0.96422 * bradford_matrix.m[0][0] 1144 + 1.000 * bradford_matrix.m[1][0] 1145 + 0.82521 * bradford_matrix.m[2][0]) 1146 / (X * bradford_matrix.m[0][0] + Y * bradford_matrix.m[1][0] + Z * bradford_matrix.m[2][0]); 1147 let y: f32 = (0.96422 * bradford_matrix.m[0][1] 1148 + 1.000 * bradford_matrix.m[1][1] 1149 + 0.82521 * bradford_matrix.m[2][1]) 1150 / (X * bradford_matrix.m[0][1] + Y * bradford_matrix.m[1][1] + Z * bradford_matrix.m[2][1]); 1151 let b: f32 = (0.96422 * bradford_matrix.m[0][2] 1152 + 1.000 * bradford_matrix.m[1][2] 1153 + 0.82521 * bradford_matrix.m[2][2]) 1154 / (X * bradford_matrix.m[0][2] + Y * bradford_matrix.m[1][2] + Z * bradford_matrix.m[2][2]); 1155 let white_adaption = Matrix { 1156 m: [[p, 0., 0.], [0., y, 0.], [0., 0., b]], 1157 }; 1158 Matrix::multiply( 1159 bradford_matrix_inv, 1160 Matrix::multiply(white_adaption, bradford_matrix), 1161 ) 1162 } 1163 #[no_mangle] 1164 pub extern "C" fn qcms_profile_precache_output_transform(profile: &mut Profile) { 1165 /* we only support precaching on rgb profiles */ 1166 if profile.color_space != RGB_SIGNATURE { 1167 return; 1168 } 1169 if SUPPORTS_ICCV4.load(Ordering::Relaxed) { 1170 /* don't precache since we will use the B2A LUT */ 1171 if profile.B2A0.is_some() { 1172 return; 1173 } 1174 /* don't precache since we will use the mBA LUT */ 1175 if profile.mBA.is_some() { 1176 return; 1177 } 1178 } 1179 /* don't precache if we do not have the TRC curves */ 1180 if profile.redTRC.is_none() || profile.greenTRC.is_none() || profile.blueTRC.is_none() { 1181 return; 1182 } 1183 if profile.precache_output.is_none() { 1184 let mut precache = precache_create(); 1185 compute_precache( 1186 profile.redTRC.as_deref().unwrap(), 1187 &mut Arc::get_mut(&mut precache).unwrap().lut_r, 1188 ); 1189 compute_precache( 1190 profile.greenTRC.as_deref().unwrap(), 1191 &mut Arc::get_mut(&mut precache).unwrap().lut_g, 1192 ); 1193 compute_precache( 1194 profile.blueTRC.as_deref().unwrap(), 1195 &mut Arc::get_mut(&mut precache).unwrap().lut_b, 1196 ); 1197 profile.precache_output = Some(precache); 1198 } 1199 } 1200 /* Replace the current transformation with a LUT transformation using a given number of sample points */ 1201 fn transform_precacheLUT_float( 1202 mut transform: Box<qcms_transform>, 1203 input: &Profile, 1204 output: &Profile, 1205 samples: i32, 1206 in_type: DataType, 1207 ) -> Option<Box<qcms_transform>> { 1208 /* The range between which 2 consecutive sample points can be used to interpolate */ 1209 let lutSize: u32 = (3 * samples * samples * samples) as u32; 1210 1211 let mut src = Vec::with_capacity(lutSize as usize); 1212 let dest = vec![0.; lutSize as usize]; 1213 /* Prepare a list of points we want to sample */ 1214 for x in 0..samples { 1215 for y in 0..samples { 1216 for z in 0..samples { 1217 src.push(x as f32 / (samples - 1) as f32); 1218 src.push(y as f32 / (samples - 1) as f32); 1219 src.push(z as f32 / (samples - 1) as f32); 1220 } 1221 } 1222 } 1223 let lut = chain_transform(input, output, src, dest, lutSize as usize); 1224 if let Some(lut) = lut { 1225 transform.clut = Some(lut); 1226 transform.grid_size = samples as u16; 1227 if in_type == RGBA8 { 1228 transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgba) 1229 } else if in_type == BGRA8 { 1230 transform.transform_fn = Some(qcms_transform_data_tetra_clut_bgra) 1231 } else if in_type == RGB8 { 1232 transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgb) 1233 } 1234 debug_assert!(transform.transform_fn.is_some()); 1235 } else { 1236 return None; 1237 } 1238 1239 Some(transform) 1240 } 1241 1242 fn transform_precacheLUT_cmyk_float( 1243 mut transform: Box<qcms_transform>, 1244 input: &Profile, 1245 output: &Profile, 1246 samples: i32, 1247 in_type: DataType, 1248 ) -> Option<Box<qcms_transform>> { 1249 /* The range between which 2 consecutive sample points can be used to interpolate */ 1250 let lutSize: u32 = (4 * samples * samples * samples * samples) as u32; 1251 1252 let mut src = Vec::with_capacity(lutSize as usize); 1253 let dest = vec![0.; lutSize as usize]; 1254 /* Prepare a list of points we want to sample */ 1255 for k in 0..samples { 1256 for c in 0..samples { 1257 for m in 0..samples { 1258 for y in 0..samples { 1259 src.push(c as f32 / (samples - 1) as f32); 1260 src.push(m as f32 / (samples - 1) as f32); 1261 src.push(y as f32 / (samples - 1) as f32); 1262 src.push(k as f32 / (samples - 1) as f32); 1263 } 1264 } 1265 } 1266 } 1267 let lut = chain_transform(input, output, src, dest, lutSize as usize); 1268 if let Some(lut) = lut { 1269 transform.clut = Some(lut); 1270 transform.grid_size = samples as u16; 1271 assert!(in_type == DataType::CMYK); 1272 transform.transform_fn = Some(qcms_transform_data_tetra_clut_cmyk) 1273 } else { 1274 return None; 1275 } 1276 1277 Some(transform) 1278 } 1279 1280 pub fn transform_create( 1281 input: &Profile, 1282 in_type: DataType, 1283 output: &Profile, 1284 out_type: DataType, 1285 _intent: Intent, 1286 ) -> Option<Box<qcms_transform>> { 1287 // Ensure the requested input and output types make sense. 1288 let matching_format = match (in_type, out_type) { 1289 (RGB8, RGB8) => true, 1290 (RGBA8, RGBA8) => true, 1291 (BGRA8, BGRA8) => true, 1292 (Gray8, out_type) => matches!(out_type, RGB8 | RGBA8 | BGRA8), 1293 (GrayA8, out_type) => matches!(out_type, RGBA8 | BGRA8), 1294 (CMYK, RGB8) => true, 1295 _ => false, 1296 }; 1297 if !matching_format { 1298 debug_assert!(false, "input/output type"); 1299 return None; 1300 } 1301 let mut transform: Box<qcms_transform> = Box::new(Default::default()); 1302 let mut precache: bool = false; 1303 if output.precache_output.is_some() { 1304 precache = true 1305 } 1306 // This precache assumes RGB_SIGNATURE (fails on GRAY_SIGNATURE, for instance) 1307 if SUPPORTS_ICCV4.load(Ordering::Relaxed) 1308 && (in_type == RGB8 || in_type == RGBA8 || in_type == BGRA8 || in_type == CMYK) 1309 && (input.A2B0.is_some() 1310 || output.B2A0.is_some() 1311 || input.mAB.is_some() 1312 || output.mAB.is_some()) 1313 { 1314 if in_type == CMYK { 1315 return transform_precacheLUT_cmyk_float(transform, input, output, 17, in_type); 1316 } 1317 // Precache the transformation to a CLUT 33x33x33 in size. 1318 // 33 is used by many profiles and works well in pratice. 1319 // This evenly divides 256 into blocks of 8x8x8. 1320 // TODO For transforming small data sets of about 200x200 or less 1321 // precaching should be avoided. 1322 let result = transform_precacheLUT_float(transform, input, output, 33, in_type); 1323 debug_assert!(result.is_some(), "precacheLUT failed"); 1324 return result; 1325 } 1326 if precache { 1327 transform.precache_output = Some(Arc::clone(output.precache_output.as_ref().unwrap())); 1328 } else { 1329 if output.redTRC.is_none() || output.greenTRC.is_none() || output.blueTRC.is_none() { 1330 return None; 1331 } 1332 transform.output_gamma_lut_r = build_output_lut(output.redTRC.as_deref().unwrap()); 1333 transform.output_gamma_lut_g = build_output_lut(output.greenTRC.as_deref().unwrap()); 1334 transform.output_gamma_lut_b = build_output_lut(output.blueTRC.as_deref().unwrap()); 1335 1336 if transform.output_gamma_lut_r.is_none() 1337 || transform.output_gamma_lut_g.is_none() 1338 || transform.output_gamma_lut_b.is_none() 1339 { 1340 return None; 1341 } 1342 } 1343 if input.color_space == RGB_SIGNATURE { 1344 if precache { 1345 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 1346 if is_x86_feature_detected!("avx") { 1347 if in_type == RGB8 { 1348 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_avx) 1349 } else if in_type == RGBA8 { 1350 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_avx) 1351 } else if in_type == BGRA8 { 1352 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_avx) 1353 } 1354 } else if cfg!(not(miri)) && is_x86_feature_detected!("sse2") { 1355 if in_type == RGB8 { 1356 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_sse2) 1357 } else if in_type == RGBA8 { 1358 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_sse2) 1359 } else if in_type == BGRA8 { 1360 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_sse2) 1361 } 1362 } 1363 1364 #[cfg(all(target_arch = "arm", feature = "neon"))] 1365 let neon_supported = is_arm_feature_detected!("neon"); 1366 #[cfg(all(target_arch = "aarch64", feature = "neon"))] 1367 let neon_supported = is_aarch64_feature_detected!("neon"); 1368 1369 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))] 1370 if neon_supported { 1371 if in_type == RGB8 { 1372 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_neon) 1373 } else if in_type == RGBA8 { 1374 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_neon) 1375 } else if in_type == BGRA8 { 1376 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_neon) 1377 } 1378 } 1379 1380 if transform.transform_fn.is_none() { 1381 if in_type == RGB8 { 1382 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_precache) 1383 } else if in_type == RGBA8 { 1384 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_precache) 1385 } else if in_type == BGRA8 { 1386 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_precache) 1387 } 1388 } 1389 } else if in_type == RGB8 { 1390 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut) 1391 } else if in_type == RGBA8 { 1392 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut) 1393 } else if in_type == BGRA8 { 1394 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut) 1395 } 1396 //XXX: avoid duplicating tables if we can 1397 transform.input_gamma_table_r = Some(Box::new(build_input_gamma_table(input.redTRC.as_deref()?))); 1398 transform.input_gamma_table_g = Some(Box::new(build_input_gamma_table(input.greenTRC.as_deref()?))); 1399 transform.input_gamma_table_b = Some(Box::new(build_input_gamma_table(input.blueTRC.as_deref()?))); 1400 1401 /* build combined colorant matrix */ 1402 1403 let in_matrix = build_colorant_matrix(input); 1404 let mut out_matrix = build_colorant_matrix(output); 1405 out_matrix = out_matrix.invert()?; 1406 1407 let result_0 = Matrix::multiply(out_matrix, in_matrix); 1408 /* check for NaN values in the matrix and bail if we find any */ 1409 let mut i: u32 = 0; 1410 while i < 3 { 1411 let mut j: u32 = 0; 1412 while j < 3 { 1413 #[allow(clippy::eq_op, clippy::float_cmp)] 1414 if result_0.m[i as usize][j as usize].is_nan() { 1415 return None; 1416 } 1417 j += 1 1418 } 1419 i += 1 1420 } 1421 /* store the results in column major mode 1422 * this makes doing the multiplication with sse easier */ 1423 transform.matrix[0][0] = result_0.m[0][0]; 1424 transform.matrix[1][0] = result_0.m[0][1]; 1425 transform.matrix[2][0] = result_0.m[0][2]; 1426 transform.matrix[0][1] = result_0.m[1][0]; 1427 transform.matrix[1][1] = result_0.m[1][1]; 1428 transform.matrix[2][1] = result_0.m[1][2]; 1429 transform.matrix[0][2] = result_0.m[2][0]; 1430 transform.matrix[1][2] = result_0.m[2][1]; 1431 transform.matrix[2][2] = result_0.m[2][2] 1432 } else if input.color_space == GRAY_SIGNATURE { 1433 transform.input_gamma_table_gray = Some(Box::new(build_input_gamma_table(input.grayTRC.as_deref()?))); 1434 if precache { 1435 if out_type == RGB8 { 1436 transform.transform_fn = Some(qcms_transform_data_gray_out_precache) 1437 } else if out_type == RGBA8 { 1438 if in_type == Gray8 { 1439 transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_precache) 1440 } else { 1441 transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_precache) 1442 } 1443 } else if out_type == BGRA8 { 1444 if in_type == Gray8 { 1445 transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_precache) 1446 } else { 1447 transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_precache) 1448 } 1449 } 1450 } else if out_type == RGB8 { 1451 transform.transform_fn = Some(qcms_transform_data_gray_out_lut) 1452 } else if out_type == RGBA8 { 1453 if in_type == Gray8 { 1454 transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_lut) 1455 } else { 1456 transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_lut) 1457 } 1458 } else if out_type == BGRA8 { 1459 if in_type == Gray8 { 1460 transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_lut) 1461 } else { 1462 transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_lut) 1463 } 1464 } 1465 } else { 1466 debug_assert!(false, "unexpected colorspace"); 1467 return None; 1468 } 1469 debug_assert!(transform.transform_fn.is_some()); 1470 Some(transform) 1471 } 1472 /// A transform from an input profile to an output one. 1473 pub struct Transform { 1474 src_ty: DataType, 1475 dst_ty: DataType, 1476 xfm: Box<qcms_transform>, 1477 } 1478 1479 impl Transform { 1480 /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent` 1481 pub fn new(input: &Profile, output: &Profile, ty: DataType, intent: Intent) -> Option<Self> { 1482 transform_create(input, ty, output, ty, intent).map(|xfm| Transform { 1483 src_ty: ty, 1484 dst_ty: ty, 1485 xfm, 1486 }) 1487 } 1488 1489 /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent` 1490 pub fn new_to( 1491 input: &Profile, 1492 output: &Profile, 1493 src_ty: DataType, 1494 dst_ty: DataType, 1495 intent: Intent, 1496 ) -> Option<Self> { 1497 transform_create(input, src_ty, output, dst_ty, intent).map(|xfm| Transform { 1498 src_ty, 1499 dst_ty, 1500 xfm, 1501 }) 1502 } 1503 1504 /// Apply the color space transform to `data` 1505 pub fn apply(&self, data: &mut [u8]) { 1506 if data.len() % self.src_ty.bytes_per_pixel() != 0 { 1507 panic!( 1508 "incomplete pixels: should be a multiple of {} got {}", 1509 self.src_ty.bytes_per_pixel(), 1510 data.len() 1511 ) 1512 } 1513 unsafe { 1514 self.xfm.transform_fn.expect("non-null function pointer")( 1515 &*self.xfm, 1516 data.as_ptr(), 1517 data.as_mut_ptr(), 1518 data.len() / self.src_ty.bytes_per_pixel(), 1519 ); 1520 } 1521 } 1522 1523 /// Apply the color space transform to `data` 1524 pub fn convert(&self, src: &[u8], dst: &mut [u8]) { 1525 if src.len() % self.src_ty.bytes_per_pixel() != 0 { 1526 panic!( 1527 "incomplete pixels: should be a multiple of {} got {}", 1528 self.src_ty.bytes_per_pixel(), 1529 src.len() 1530 ) 1531 } 1532 if dst.len() % self.dst_ty.bytes_per_pixel() != 0 { 1533 panic!( 1534 "incomplete pixels: should be a multiple of {} got {}", 1535 self.dst_ty.bytes_per_pixel(), 1536 dst.len() 1537 ) 1538 } 1539 assert_eq!( 1540 src.len() / self.src_ty.bytes_per_pixel(), 1541 dst.len() / self.dst_ty.bytes_per_pixel() 1542 ); 1543 unsafe { 1544 self.xfm.transform_fn.expect("non-null function pointer")( 1545 &*self.xfm, 1546 src.as_ptr(), 1547 dst.as_mut_ptr(), 1548 src.len() / self.src_ty.bytes_per_pixel(), 1549 ); 1550 } 1551 } 1552 } 1553 1554 #[no_mangle] 1555 pub extern "C" fn qcms_enable_iccv4() { 1556 SUPPORTS_ICCV4.store(true, Ordering::Relaxed); 1557 }