2 // Copyright (C) 2009 Mozilla Foundation
3 // Copyright (C) 1998-2007 Marti Maria
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:
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
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.
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,
30 chain::chain_transform,
31 double_to_s15Fixed16Number,
32 iccread::SUPPORTS_ICCV4,
35 build_colorant_matrix, build_input_gamma_table, build_output_lut, compute_precache,
40 iccread::{qcms_CIE_xyY, qcms_CIE_xyYTRIPLE, Profile, GRAY_SIGNATURE, RGB_SIGNATURE},
41 transform_util::clamp_float,
44 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
47 qcms_transform_data_bgra_out_lut_avx, qcms_transform_data_rgb_out_lut_avx,
48 qcms_transform_data_rgba_out_lut_avx,
51 qcms_transform_data_bgra_out_lut_sse2, qcms_transform_data_rgb_out_lut_sse2,
52 qcms_transform_data_rgba_out_lut_sse2,
56 use std::sync::atomic::Ordering;
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;
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;
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],
81 impl Default for PrecacheOuput {
82 fn default() -> PrecacheOuput {
84 lut_r: [0; PRECACHE_OUTPUT_SIZE],
85 lut_g: [0; PRECACHE_OUTPUT_SIZE],
86 lut_b: [0; PRECACHE_OUTPUT_SIZE],
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 */
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>>,
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,
124 pub type transform_fn_t =
125 Option<unsafe fn(_: &qcms_transform, _: *const u8, _: *mut u8, _: usize) -> ()>;
126 /// The format of pixel data
128 #[derive(PartialEq, Eq, Clone, Copy)]
129 #[allow(clippy::upper_case_acronyms)]
140 pub fn bytes_per_pixel(&self) -> usize {
155 #[derive(Copy, Clone)]
156 #[allow(clippy::upper_case_acronyms)]
164 const kRIndex: usize;
165 const kGIndex: usize;
166 const kBIndex: usize;
167 const kAIndex: usize;
170 #[allow(clippy::upper_case_acronyms)]
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;
179 #[allow(clippy::upper_case_acronyms)]
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;
188 #[allow(clippy::upper_case_acronyms)]
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;
197 pub trait GrayFormat {
198 const has_alpha: bool;
202 impl GrayFormat for Gray {
203 const has_alpha: bool = false;
206 pub struct GrayAlpha;
207 impl GrayFormat for GrayAlpha {
208 const has_alpha: bool = true;
212 fn clamp_u8(v: f32) -> u8 {
218 (v + 0.5).floor() as u8
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.
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(
236 primrs: qcms_CIE_xyYTRIPLE,
237 ) -> Option<Matrix> {
238 let mut primaries: Matrix = Matrix { m: [[0.; 3]; 3] };
240 let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
241 let mut white_point: Vector = Vector { v: [0.; 3] };
243 let xn: f64 = white.x;
244 let yn: f64 = white.y;
249 let xr: f64 = primrs.red.x;
250 let yr: f64 = primrs.red.y;
251 let xg: f64 = primrs.green.x;
252 let yg: f64 = primrs.green.y;
253 let xb: f64 = primrs.blue.x;
254 let yb: f64 = primrs.blue.y;
255 primaries.m[0][0] = xr as f32;
256 primaries.m[0][1] = xg as f32;
257 primaries.m[0][2] = xb as f32;
258 primaries.m[1][0] = yr as f32;
259 primaries.m[1][1] = yg as f32;
260 primaries.m[1][2] = yb as f32;
261 primaries.m[2][0] = (1f64 - xr - yr) as f32;
262 primaries.m[2][1] = (1f64 - xg - yg) as f32;
263 primaries.m[2][2] = (1f64 - xb - yb) as f32;
264 white_point.v[0] = (xn / yn) as f32;
265 white_point.v[1] = 1.;
266 white_point.v[2] = ((1.0f64 - xn - yn) / yn) as f32;
267 let primaries_invert: Matrix = primaries.invert()?;
269 let coefs: Vector = primaries_invert.eval(white_point);
270 result.m[0][0] = (coefs.v[0] as f64 * xr) as f32;
271 result.m[0][1] = (coefs.v[1] as f64 * xg) as f32;
272 result.m[0][2] = (coefs.v[2] as f64 * xb) as f32;
273 result.m[1][0] = (coefs.v[0] as f64 * yr) as f32;
274 result.m[1][1] = (coefs.v[1] as f64 * yg) as f32;
275 result.m[1][2] = (coefs.v[2] as f64 * yb) as f32;
276 result.m[2][0] = (coefs.v[0] as f64 * (1.0f64 - xr - yr)) as f32;
277 result.m[2][1] = (coefs.v[1] as f64 * (1.0f64 - xg - yg)) as f32;
278 result.m[2][2] = (coefs.v[2] as f64 * (1.0f64 - xb - yb)) as f32;
281 /* CIE Illuminant D50 */
282 const D50_XYZ: CIE_XYZ = CIE_XYZ {
287 /* from lcms: xyY2XYZ()
288 * corresponds to argyll: icmYxy2XYZ() */
289 fn xyY2XYZ(source: qcms_CIE_xyY) -> CIE_XYZ {
290 let mut dest: CIE_XYZ = CIE_XYZ {
295 dest.X = source.x / source.y * source.Y;
297 dest.Z = (1f64 - source.x - source.y) / source.y * source.Y;
300 /* from lcms: ComputeChromaticAdaption */
301 // Compute chromatic adaption matrix using chad as cone matrix
302 fn compute_chromatic_adaption(
303 source_white_point: CIE_XYZ,
304 dest_white_point: CIE_XYZ,
306 ) -> Option<Matrix> {
307 let mut cone_source_XYZ: Vector = Vector { v: [0.; 3] };
309 let mut cone_dest_XYZ: Vector = Vector { v: [0.; 3] };
311 let mut cone: Matrix = Matrix { m: [[0.; 3]; 3] };
313 let chad_inv: Matrix = chad.invert()?;
314 cone_source_XYZ.v[0] = source_white_point.X as f32;
315 cone_source_XYZ.v[1] = source_white_point.Y as f32;
316 cone_source_XYZ.v[2] = source_white_point.Z as f32;
317 cone_dest_XYZ.v[0] = dest_white_point.X as f32;
318 cone_dest_XYZ.v[1] = dest_white_point.Y as f32;
319 cone_dest_XYZ.v[2] = dest_white_point.Z as f32;
321 let cone_source_rgb: Vector = chad.eval(cone_source_XYZ);
322 let cone_dest_rgb: Vector = chad.eval(cone_dest_XYZ);
323 cone.m[0][0] = cone_dest_rgb.v[0] / cone_source_rgb.v[0];
327 cone.m[1][1] = cone_dest_rgb.v[1] / cone_source_rgb.v[1];
331 cone.m[2][2] = cone_dest_rgb.v[2] / cone_source_rgb.v[2];
333 Some(Matrix::multiply(chad_inv, Matrix::multiply(cone, chad)))
335 /* from lcms: cmsAdaptionMatrix */
336 // Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll
337 // Bradford is assumed
338 fn adaption_matrix(source_illumination: CIE_XYZ, target_illumination: CIE_XYZ) -> Option<Matrix> {
339 let lam_rigg: Matrix = {
342 [0.8951, 0.2664, -0.1614],
343 [-0.7502, 1.7135, 0.0367],
344 [0.0389, -0.0685, 1.0296],
348 compute_chromatic_adaption(source_illumination, target_illumination, lam_rigg)
350 /* from lcms: cmsAdaptMatrixToD50 */
351 fn adapt_matrix_to_D50(r: Option<Matrix>, source_white_pt: qcms_CIE_xyY) -> Option<Matrix> {
352 if source_white_pt.y == 0.0f64 {
356 let Dn: CIE_XYZ = xyY2XYZ(source_white_pt);
357 let Bradford: Matrix = adaption_matrix(Dn, D50_XYZ)?;
358 Some(Matrix::multiply(Bradford, r?))
360 pub(crate) fn set_rgb_colorants(
361 profile: &mut Profile,
362 white_point: qcms_CIE_xyY,
363 primaries: qcms_CIE_xyYTRIPLE,
365 let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
366 let colorants = match adapt_matrix_to_D50(colorants, white_point) {
367 Some(colorants) => colorants,
368 None => return false,
371 /* note: there's a transpose type of operation going on here */
372 profile.redColorant.X = double_to_s15Fixed16Number(colorants.m[0][0] as f64);
373 profile.redColorant.Y = double_to_s15Fixed16Number(colorants.m[1][0] as f64);
374 profile.redColorant.Z = double_to_s15Fixed16Number(colorants.m[2][0] as f64);
375 profile.greenColorant.X = double_to_s15Fixed16Number(colorants.m[0][1] as f64);
376 profile.greenColorant.Y = double_to_s15Fixed16Number(colorants.m[1][1] as f64);
377 profile.greenColorant.Z = double_to_s15Fixed16Number(colorants.m[2][1] as f64);
378 profile.blueColorant.X = double_to_s15Fixed16Number(colorants.m[0][2] as f64);
379 profile.blueColorant.Y = double_to_s15Fixed16Number(colorants.m[1][2] as f64);
380 profile.blueColorant.Z = double_to_s15Fixed16Number(colorants.m[2][2] as f64);
383 pub(crate) fn get_rgb_colorants(
384 white_point: qcms_CIE_xyY,
385 primaries: qcms_CIE_xyYTRIPLE,
386 ) -> Option<Matrix> {
387 let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
388 adapt_matrix_to_D50(colorants, white_point)
390 /* Alpha is not corrected.
391 A rationale for this is found in Alvy Ray's "Should Alpha Be Nonlinear If
392 RGB Is?" Tech Memo 17 (December 14, 1998).
393 See: ftp://ftp.alvyray.com/Acrobat/17_Nonln.pdf
395 unsafe extern "C" fn qcms_transform_data_gray_template_lut<I: GrayFormat, F: Format>(
396 transform: &qcms_transform,
401 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
402 let input_gamma_table_gray = transform.input_gamma_table_gray.as_ref().unwrap();
405 while (i as usize) < length {
408 let device: u8 = *fresh0;
409 let mut alpha: u8 = 0xffu8;
415 let linear: f32 = input_gamma_table_gray[device as usize];
417 let out_device_r: f32 = lut_interp_linear(
419 &transform.output_gamma_lut_r.as_ref().unwrap(),
421 let out_device_g: f32 = lut_interp_linear(
423 &transform.output_gamma_lut_g.as_ref().unwrap(),
425 let out_device_b: f32 = lut_interp_linear(
427 &transform.output_gamma_lut_b.as_ref().unwrap(),
429 *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
430 *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
431 *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
432 if F::kAIndex != 0xff {
433 *dest.add(F::kAIndex) = alpha
435 dest = dest.offset(components as isize);
439 unsafe fn qcms_transform_data_gray_out_lut(
440 transform: &qcms_transform,
445 qcms_transform_data_gray_template_lut::<Gray, RGB>(transform, src, dest, length);
447 unsafe fn qcms_transform_data_gray_rgba_out_lut(
448 transform: &qcms_transform,
453 qcms_transform_data_gray_template_lut::<Gray, RGBA>(transform, src, dest, length);
455 unsafe fn qcms_transform_data_gray_bgra_out_lut(
456 transform: &qcms_transform,
461 qcms_transform_data_gray_template_lut::<Gray, BGRA>(transform, src, dest, length);
463 unsafe fn qcms_transform_data_graya_rgba_out_lut(
464 transform: &qcms_transform,
469 qcms_transform_data_gray_template_lut::<GrayAlpha, RGBA>(transform, src, dest, length);
471 unsafe fn qcms_transform_data_graya_bgra_out_lut(
472 transform: &qcms_transform,
477 qcms_transform_data_gray_template_lut::<GrayAlpha, BGRA>(transform, src, dest, length);
479 unsafe fn qcms_transform_data_gray_template_precache<I: GrayFormat, F: Format>(
480 transform: &qcms_transform,
485 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
486 let precache_output = transform.precache_output.as_deref().unwrap();
487 let output_r = &precache_output.lut_r;
488 let output_g = &precache_output.lut_g;
489 let output_b = &precache_output.lut_b;
491 let input_gamma_table_gray = transform
492 .input_gamma_table_gray
498 while (i as usize) < length {
501 let device: u8 = *fresh2;
502 let mut alpha: u8 = 0xffu8;
509 let linear: f32 = *input_gamma_table_gray.offset(device as isize);
510 /* we could round here... */
511 let gray: u16 = (linear * PRECACHE_OUTPUT_MAX as f32) as u16;
512 *dest.add(F::kRIndex) = output_r[gray as usize];
513 *dest.add(F::kGIndex) = output_g[gray as usize];
514 *dest.add(F::kBIndex) = output_b[gray as usize];
515 if F::kAIndex != 0xff {
516 *dest.add(F::kAIndex) = alpha
518 dest = dest.offset(components as isize);
522 unsafe fn qcms_transform_data_gray_out_precache(
523 transform: &qcms_transform,
528 qcms_transform_data_gray_template_precache::<Gray, RGB>(transform, src, dest, length);
530 unsafe fn qcms_transform_data_gray_rgba_out_precache(
531 transform: &qcms_transform,
536 qcms_transform_data_gray_template_precache::<Gray, RGBA>(transform, src, dest, length);
538 unsafe fn qcms_transform_data_gray_bgra_out_precache(
539 transform: &qcms_transform,
544 qcms_transform_data_gray_template_precache::<Gray, BGRA>(transform, src, dest, length);
546 unsafe fn qcms_transform_data_graya_rgba_out_precache(
547 transform: &qcms_transform,
552 qcms_transform_data_gray_template_precache::<GrayAlpha, RGBA>(transform, src, dest, length);
554 unsafe fn qcms_transform_data_graya_bgra_out_precache(
555 transform: &qcms_transform,
560 qcms_transform_data_gray_template_precache::<GrayAlpha, BGRA>(transform, src, dest, length);
562 unsafe fn qcms_transform_data_template_lut_precache<F: Format>(
563 transform: &qcms_transform,
568 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
569 let output_table_r = &transform.precache_output.as_deref().unwrap().lut_r;
570 let output_table_g = &transform.precache_output.as_deref().unwrap().lut_g;
571 let output_table_b = &transform.precache_output.as_deref().unwrap().lut_b;
572 let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr();
573 let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr();
574 let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr();
576 let mat = &transform.matrix;
578 while (i as usize) < length {
579 let device_r: u8 = *src.add(F::kRIndex);
580 let device_g: u8 = *src.add(F::kGIndex);
581 let device_b: u8 = *src.add(F::kBIndex);
582 let mut alpha: u8 = 0;
583 if F::kAIndex != 0xff {
584 alpha = *src.add(F::kAIndex)
586 src = src.offset(components as isize);
588 let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize);
589 let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize);
590 let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize);
591 let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
592 let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
593 let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b;
594 out_linear_r = clamp_float(out_linear_r);
595 out_linear_g = clamp_float(out_linear_g);
596 out_linear_b = clamp_float(out_linear_b);
597 /* we could round here... */
599 let r: u16 = (out_linear_r * PRECACHE_OUTPUT_MAX as f32) as u16;
600 let g: u16 = (out_linear_g * PRECACHE_OUTPUT_MAX as f32) as u16;
601 let b: u16 = (out_linear_b * PRECACHE_OUTPUT_MAX as f32) as u16;
602 *dest.add(F::kRIndex) = output_table_r[r as usize];
603 *dest.add(F::kGIndex) = output_table_g[g as usize];
604 *dest.add(F::kBIndex) = output_table_b[b as usize];
605 if F::kAIndex != 0xff {
606 *dest.add(F::kAIndex) = alpha
608 dest = dest.offset(components as isize);
613 pub unsafe fn qcms_transform_data_rgb_out_lut_precache(
614 transform: &qcms_transform,
619 qcms_transform_data_template_lut_precache::<RGB>(transform, src, dest, length);
622 pub unsafe fn qcms_transform_data_rgba_out_lut_precache(
623 transform: &qcms_transform,
628 qcms_transform_data_template_lut_precache::<RGBA>(transform, src, dest, length);
631 pub unsafe fn qcms_transform_data_bgra_out_lut_precache(
632 transform: &qcms_transform,
637 qcms_transform_data_template_lut_precache::<BGRA>(transform, src, dest, length);
641 static void qcms_transform_data_clut(const qcms_transform *transform, const unsigned char *src, unsigned char *dest, size_t length) {
644 int x_len = transform->grid_size;
645 int len = x_len * x_len;
646 const float* r_table = transform->r_clut;
647 const float* g_table = transform->g_clut;
648 const float* b_table = transform->b_clut;
650 for (i = 0; i < length; i++) {
651 unsigned char in_r = *src++;
652 unsigned char in_g = *src++;
653 unsigned char in_b = *src++;
654 float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f;
656 int x = floorf(linear_r * (transform->grid_size-1));
657 int y = floorf(linear_g * (transform->grid_size-1));
658 int z = floorf(linear_b * (transform->grid_size-1));
659 int x_n = ceilf(linear_r * (transform->grid_size-1));
660 int y_n = ceilf(linear_g * (transform->grid_size-1));
661 int z_n = ceilf(linear_b * (transform->grid_size-1));
662 float x_d = linear_r * (transform->grid_size-1) - x;
663 float y_d = linear_g * (transform->grid_size-1) - y;
664 float z_d = linear_b * (transform->grid_size-1) - z;
666 float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d);
667 float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d);
668 float r_y1 = lerp(r_x1, r_x2, y_d);
669 float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d);
670 float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d);
671 float r_y2 = lerp(r_x3, r_x4, y_d);
672 float clut_r = lerp(r_y1, r_y2, z_d);
674 float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d);
675 float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d);
676 float g_y1 = lerp(g_x1, g_x2, y_d);
677 float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d);
678 float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d);
679 float g_y2 = lerp(g_x3, g_x4, y_d);
680 float clut_g = lerp(g_y1, g_y2, z_d);
682 float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d);
683 float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d);
684 float b_y1 = lerp(b_x1, b_x2, y_d);
685 float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d);
686 float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d);
687 float b_y2 = lerp(b_x3, b_x4, y_d);
688 float clut_b = lerp(b_y1, b_y2, z_d);
690 *dest++ = clamp_u8(clut_r*255.0f);
691 *dest++ = clamp_u8(clut_g*255.0f);
692 *dest++ = clamp_u8(clut_b*255.0f);
696 fn int_div_ceil(value: i32, div: i32) -> i32 {
697 (value + div - 1) / div
699 // Using lcms' tetra interpolation algorithm.
700 unsafe extern "C" fn qcms_transform_data_tetra_clut_template<F: Format>(
701 transform: &qcms_transform,
706 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
709 let x_len: i32 = transform.grid_size as i32;
710 let len: i32 = x_len * x_len;
711 let table = transform.clut.as_ref().unwrap().as_ptr();
712 let r_table: *const f32 = table;
713 let g_table: *const f32 = table.offset(1);
714 let b_table: *const f32 = table.offset(2);
717 while (i as usize) < length {
730 let in_r: u8 = *src.add(F::kRIndex);
731 let in_g: u8 = *src.add(F::kGIndex);
732 let in_b: u8 = *src.add(F::kBIndex);
733 let mut in_a: u8 = 0;
734 if F::kAIndex != 0xff {
735 in_a = *src.add(F::kAIndex)
737 src = src.offset(components as isize);
738 let linear_r: f32 = in_r as i32 as f32 / 255.0;
739 let linear_g: f32 = in_g as i32 as f32 / 255.0;
740 let linear_b: f32 = in_b as i32 as f32 / 255.0;
741 let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255;
742 let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255;
743 let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255;
744 let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255);
745 let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255);
746 let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255);
747 let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32;
748 let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32;
749 let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32;
750 let CLU = |table: *const f32, x, y, z| {
751 *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
754 c0_r = CLU(r_table, x, y, z);
755 c0_g = CLU(g_table, x, y, z);
756 c0_b = CLU(b_table, x, y, z);
759 //rx >= ry && ry >= rz
760 c1_r = CLU(r_table, x_n, y, z) - c0_r;
761 c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
762 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
763 c1_g = CLU(g_table, x_n, y, z) - c0_g;
764 c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
765 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
766 c1_b = CLU(b_table, x_n, y, z) - c0_b;
767 c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
768 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
770 //rx >= rz && rz >= ry
771 c1_r = CLU(r_table, x_n, y, z) - c0_r;
772 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
773 c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
774 c1_g = CLU(g_table, x_n, y, z) - c0_g;
775 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
776 c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
777 c1_b = CLU(b_table, x_n, y, z) - c0_b;
778 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
779 c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
781 //rz > rx && rx >= ry
782 c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
783 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
784 c3_r = CLU(r_table, x, y, z_n) - c0_r;
785 c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
786 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
787 c3_g = CLU(g_table, x, y, z_n) - c0_g;
788 c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
789 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
790 c3_b = CLU(b_table, x, y, z_n) - c0_b;
793 //ry > rx && rx >= rz
794 c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
795 c2_r = CLU(r_table, x, y_n, z) - c0_r;
796 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
797 c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
798 c2_g = CLU(g_table, x, y_n, z) - c0_g;
799 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
800 c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
801 c2_b = CLU(b_table, x, y_n, z) - c0_b;
802 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
804 //ry >= rz && rz > rx
805 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
806 c2_r = CLU(r_table, x, y_n, z) - c0_r;
807 c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
808 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
809 c2_g = CLU(g_table, x, y_n, z) - c0_g;
810 c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
811 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
812 c2_b = CLU(b_table, x, y_n, z) - c0_b;
813 c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
816 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
817 c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
818 c3_r = CLU(r_table, x, y, z_n) - c0_r;
819 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
820 c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
821 c3_g = CLU(g_table, x, y, z_n) - c0_g;
822 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
823 c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
824 c3_b = CLU(b_table, x, y, z_n) - c0_b;
826 let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz;
827 let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz;
828 let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz;
829 *dest.add(F::kRIndex) = clamp_u8(clut_r * 255.0);
830 *dest.add(F::kGIndex) = clamp_u8(clut_g * 255.0);
831 *dest.add(F::kBIndex) = clamp_u8(clut_b * 255.0);
832 if F::kAIndex != 0xff {
833 *dest.add(F::kAIndex) = in_a
835 dest = dest.offset(components as isize);
841 transform: &qcms_transform,
846 ) -> (f32, f32, f32) {
847 let r_table: *const f32 = table;
848 let g_table: *const f32 = table.offset(1);
849 let b_table: *const f32 = table.offset(2);
850 let linear_r: f32 = in_r as i32 as f32 / 255.0;
851 let linear_g: f32 = in_g as i32 as f32 / 255.0;
852 let linear_b: f32 = in_b as i32 as f32 / 255.0;
854 let x_len: i32 = transform.grid_size as i32;
855 let len: i32 = x_len * x_len;
856 let x: i32 = in_r as i32 * (transform.grid_size as i32 - 1) / 255;
857 let y: i32 = in_g as i32 * (transform.grid_size as i32 - 1) / 255;
858 let z: i32 = in_b as i32 * (transform.grid_size as i32 - 1) / 255;
859 let x_n: i32 = int_div_ceil(in_r as i32 * (transform.grid_size as i32 - 1), 255);
860 let y_n: i32 = int_div_ceil(in_g as i32 * (transform.grid_size as i32 - 1), 255);
861 let z_n: i32 = int_div_ceil(in_b as i32 * (transform.grid_size as i32 - 1), 255);
862 let rx: f32 = linear_r * (transform.grid_size as i32 - 1) as f32 - x as f32;
863 let ry: f32 = linear_g * (transform.grid_size as i32 - 1) as f32 - y as f32;
864 let rz: f32 = linear_b * (transform.grid_size as i32 - 1) as f32 - z as f32;
865 let CLU = |table: *const f32, x, y, z| {
866 *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
880 c0_r = CLU(r_table, x, y, z);
881 c0_g = CLU(g_table, x, y, z);
882 c0_b = CLU(b_table, x, y, z);
885 //rx >= ry && ry >= rz
886 c1_r = CLU(r_table, x_n, y, z) - c0_r;
887 c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
888 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
889 c1_g = CLU(g_table, x_n, y, z) - c0_g;
890 c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
891 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
892 c1_b = CLU(b_table, x_n, y, z) - c0_b;
893 c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
894 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
896 //rx >= rz && rz >= ry
897 c1_r = CLU(r_table, x_n, y, z) - c0_r;
898 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
899 c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
900 c1_g = CLU(g_table, x_n, y, z) - c0_g;
901 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
902 c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
903 c1_b = CLU(b_table, x_n, y, z) - c0_b;
904 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
905 c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
907 //rz > rx && rx >= ry
908 c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
909 c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
910 c3_r = CLU(r_table, x, y, z_n) - c0_r;
911 c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
912 c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
913 c3_g = CLU(g_table, x, y, z_n) - c0_g;
914 c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
915 c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
916 c3_b = CLU(b_table, x, y, z_n) - c0_b;
919 //ry > rx && rx >= rz
920 c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
921 c2_r = CLU(r_table, x, y_n, z) - c0_r;
922 c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
923 c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
924 c2_g = CLU(g_table, x, y_n, z) - c0_g;
925 c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
926 c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
927 c2_b = CLU(b_table, x, y_n, z) - c0_b;
928 c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
930 //ry >= rz && rz > rx
931 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
932 c2_r = CLU(r_table, x, y_n, z) - c0_r;
933 c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
934 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
935 c2_g = CLU(g_table, x, y_n, z) - c0_g;
936 c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
937 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
938 c2_b = CLU(b_table, x, y_n, z) - c0_b;
939 c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
942 c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
943 c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
944 c3_r = CLU(r_table, x, y, z_n) - c0_r;
945 c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
946 c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
947 c3_g = CLU(g_table, x, y, z_n) - c0_g;
948 c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
949 c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
950 c3_b = CLU(b_table, x, y, z_n) - c0_b;
952 let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz;
953 let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz;
954 let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz;
955 (clut_r, clut_g, clut_b)
959 fn lerp(a: f32, b: f32, t: f32) -> f32 {
960 a * (1.0 - t) + b * t
963 // lerp between two tetrahedral interpolations
964 // See lcms:Eval4InputsFloat
965 #[allow(clippy::many_single_char_names)]
966 unsafe fn qcms_transform_data_tetra_clut_cmyk(
967 transform: &qcms_transform,
972 let table = transform.clut.as_ref().unwrap().as_ptr();
974 transform.clut.as_ref().unwrap().len()
975 >= ((transform.grid_size as i32).pow(4) * 3) as usize
978 let c: u8 = *src.add(0);
979 let m: u8 = *src.add(1);
980 let y: u8 = *src.add(2);
981 let k: u8 = *src.add(3);
983 let linear_k: f32 = k as i32 as f32 / 255.0;
984 let grid_size = transform.grid_size as i32;
985 let w: i32 = k as i32 * (transform.grid_size as i32 - 1) / 255;
986 let w_n: i32 = int_div_ceil(k as i32 * (transform.grid_size as i32 - 1), 255);
987 let t: f32 = linear_k * (transform.grid_size as i32 - 1) as f32 - w as f32;
989 let table1 = table.offset((w * grid_size * grid_size * grid_size * 3) as isize);
990 let table2 = table.offset((w_n * grid_size * grid_size * grid_size * 3) as isize);
992 let (r1, g1, b1) = tetra(transform, table1, c, m, y);
993 let (r2, g2, b2) = tetra(transform, table2, c, m, y);
994 let r = lerp(r1, r2, t);
995 let g = lerp(g1, g2, t);
996 let b = lerp(b1, b2, t);
997 *dest.add(0) = clamp_u8(r * 255.0);
998 *dest.add(1) = clamp_u8(g * 255.0);
999 *dest.add(2) = clamp_u8(b * 255.0);
1000 dest = dest.offset(3);
1004 unsafe fn qcms_transform_data_tetra_clut_rgb(
1005 transform: &qcms_transform,
1010 qcms_transform_data_tetra_clut_template::<RGB>(transform, src, dest, length);
1012 unsafe fn qcms_transform_data_tetra_clut_rgba(
1013 transform: &qcms_transform,
1018 qcms_transform_data_tetra_clut_template::<RGBA>(transform, src, dest, length);
1020 unsafe fn qcms_transform_data_tetra_clut_bgra(
1021 transform: &qcms_transform,
1026 qcms_transform_data_tetra_clut_template::<BGRA>(transform, src, dest, length);
1028 unsafe fn qcms_transform_data_template_lut<F: Format>(
1029 transform: &qcms_transform,
1034 let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
1036 let mat = &transform.matrix;
1038 let input_gamma_table_r = transform.input_gamma_table_r.as_ref().unwrap().as_ptr();
1039 let input_gamma_table_g = transform.input_gamma_table_g.as_ref().unwrap().as_ptr();
1040 let input_gamma_table_b = transform.input_gamma_table_b.as_ref().unwrap().as_ptr();
1041 while (i as usize) < length {
1042 let device_r: u8 = *src.add(F::kRIndex);
1043 let device_g: u8 = *src.add(F::kGIndex);
1044 let device_b: u8 = *src.add(F::kBIndex);
1045 let mut alpha: u8 = 0;
1046 if F::kAIndex != 0xff {
1047 alpha = *src.add(F::kAIndex)
1049 src = src.offset(components as isize);
1051 let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize);
1052 let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize);
1053 let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize);
1054 let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
1055 let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
1056 let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b;
1057 out_linear_r = clamp_float(out_linear_r);
1058 out_linear_g = clamp_float(out_linear_g);
1059 out_linear_b = clamp_float(out_linear_b);
1061 let out_device_r: f32 = lut_interp_linear(
1062 out_linear_r as f64,
1063 &transform.output_gamma_lut_r.as_ref().unwrap(),
1065 let out_device_g: f32 = lut_interp_linear(
1066 out_linear_g as f64,
1067 transform.output_gamma_lut_g.as_ref().unwrap(),
1069 let out_device_b: f32 = lut_interp_linear(
1070 out_linear_b as f64,
1071 transform.output_gamma_lut_b.as_ref().unwrap(),
1073 *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
1074 *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
1075 *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
1076 if F::kAIndex != 0xff {
1077 *dest.add(F::kAIndex) = alpha
1079 dest = dest.offset(components as isize);
1084 pub unsafe fn qcms_transform_data_rgb_out_lut(
1085 transform: &qcms_transform,
1090 qcms_transform_data_template_lut::<RGB>(transform, src, dest, length);
1093 pub unsafe fn qcms_transform_data_rgba_out_lut(
1094 transform: &qcms_transform,
1099 qcms_transform_data_template_lut::<RGBA>(transform, src, dest, length);
1102 pub unsafe fn qcms_transform_data_bgra_out_lut(
1103 transform: &qcms_transform,
1108 qcms_transform_data_template_lut::<BGRA>(transform, src, dest, length);
1111 fn precache_create() -> Arc<PrecacheOuput> {
1112 Arc::new(PrecacheOuput::default())
1116 pub unsafe extern "C" fn qcms_transform_release(t: *mut qcms_transform) {
1117 drop(Box::from_raw(t));
1120 const bradford_matrix: Matrix = Matrix {
1122 [0.8951, 0.2664, -0.1614],
1123 [-0.7502, 1.7135, 0.0367],
1124 [0.0389, -0.0685, 1.0296],
1128 const bradford_matrix_inv: Matrix = Matrix {
1130 [0.9869929, -0.1470543, 0.1599627],
1131 [0.4323053, 0.5183603, 0.0492912],
1132 [-0.0085287, 0.0400428, 0.9684867],
1137 fn compute_whitepoint_adaption(X: f32, Y: f32, Z: f32) -> Matrix {
1138 let p: f32 = (0.96422 * bradford_matrix.m[0][0]
1139 + 1.000 * bradford_matrix.m[1][0]
1140 + 0.82521 * bradford_matrix.m[2][0])
1141 / (X * bradford_matrix.m[0][0] + Y * bradford_matrix.m[1][0] + Z * bradford_matrix.m[2][0]);
1142 let y: f32 = (0.96422 * bradford_matrix.m[0][1]
1143 + 1.000 * bradford_matrix.m[1][1]
1144 + 0.82521 * bradford_matrix.m[2][1])
1145 / (X * bradford_matrix.m[0][1] + Y * bradford_matrix.m[1][1] + Z * bradford_matrix.m[2][1]);
1146 let b: f32 = (0.96422 * bradford_matrix.m[0][2]
1147 + 1.000 * bradford_matrix.m[1][2]
1148 + 0.82521 * bradford_matrix.m[2][2])
1149 / (X * bradford_matrix.m[0][2] + Y * bradford_matrix.m[1][2] + Z * bradford_matrix.m[2][2]);
1150 let white_adaption = Matrix {
1151 m: [[p, 0., 0.], [0., y, 0.], [0., 0., b]],
1154 bradford_matrix_inv,
1155 Matrix::multiply(white_adaption, bradford_matrix),
1159 pub extern "C" fn qcms_profile_precache_output_transform(profile: &mut Profile) {
1160 /* we only support precaching on rgb profiles */
1161 if profile.color_space != RGB_SIGNATURE {
1164 if SUPPORTS_ICCV4.load(Ordering::Relaxed) {
1165 /* don't precache since we will use the B2A LUT */
1166 if profile.B2A0.is_some() {
1169 /* don't precache since we will use the mBA LUT */
1170 if profile.mBA.is_some() {
1174 /* don't precache if we do not have the TRC curves */
1175 if profile.redTRC.is_none() || profile.greenTRC.is_none() || profile.blueTRC.is_none() {
1178 if profile.precache_output.is_none() {
1179 let mut precache = precache_create();
1181 profile.redTRC.as_deref().unwrap(),
1182 &mut Arc::get_mut(&mut precache).unwrap().lut_r,
1185 profile.greenTRC.as_deref().unwrap(),
1186 &mut Arc::get_mut(&mut precache).unwrap().lut_g,
1189 profile.blueTRC.as_deref().unwrap(),
1190 &mut Arc::get_mut(&mut precache).unwrap().lut_b,
1192 profile.precache_output = Some(precache);
1195 /* Replace the current transformation with a LUT transformation using a given number of sample points */
1196 fn transform_precacheLUT_float(
1197 mut transform: Box<qcms_transform>,
1202 ) -> Option<Box<qcms_transform>> {
1203 /* The range between which 2 consecutive sample points can be used to interpolate */
1204 let lutSize: u32 = (3 * samples * samples * samples) as u32;
1206 let mut src = Vec::with_capacity(lutSize as usize);
1207 let dest = vec![0.; lutSize as usize];
1208 /* Prepare a list of points we want to sample */
1209 for x in 0..samples {
1210 for y in 0..samples {
1211 for z in 0..samples {
1212 src.push(x as f32 / (samples - 1) as f32);
1213 src.push(y as f32 / (samples - 1) as f32);
1214 src.push(z as f32 / (samples - 1) as f32);
1218 let lut = chain_transform(input, output, src, dest, lutSize as usize);
1219 if let Some(lut) = lut {
1220 transform.clut = Some(lut);
1221 transform.grid_size = samples as u16;
1222 if in_type == RGBA8 {
1223 transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgba)
1224 } else if in_type == BGRA8 {
1225 transform.transform_fn = Some(qcms_transform_data_tetra_clut_bgra)
1226 } else if in_type == RGB8 {
1227 transform.transform_fn = Some(qcms_transform_data_tetra_clut_rgb)
1229 debug_assert!(transform.transform_fn.is_some());
1237 fn transform_precacheLUT_cmyk_float(
1238 mut transform: Box<qcms_transform>,
1243 ) -> Option<Box<qcms_transform>> {
1244 /* The range between which 2 consecutive sample points can be used to interpolate */
1245 let lutSize: u32 = (4 * samples * samples * samples * samples) as u32;
1247 let mut src = Vec::with_capacity(lutSize as usize);
1248 let dest = vec![0.; lutSize as usize];
1249 /* Prepare a list of points we want to sample */
1250 for k in 0..samples {
1251 for c in 0..samples {
1252 for m in 0..samples {
1253 for y in 0..samples {
1254 src.push(c as f32 / (samples - 1) as f32);
1255 src.push(m as f32 / (samples - 1) as f32);
1256 src.push(y as f32 / (samples - 1) as f32);
1257 src.push(k as f32 / (samples - 1) as f32);
1262 let lut = chain_transform(input, output, src, dest, lutSize as usize);
1263 if let Some(lut) = lut {
1264 transform.clut = Some(lut);
1265 transform.grid_size = samples as u16;
1266 assert!(in_type == DataType::CMYK);
1267 transform.transform_fn = Some(qcms_transform_data_tetra_clut_cmyk)
1275 pub fn transform_create(
1281 ) -> Option<Box<qcms_transform>> {
1282 // Ensure the requested input and output types make sense.
1283 let matching_format = match (in_type, out_type) {
1284 (RGB8, RGB8) => true,
1285 (RGBA8, RGBA8) => true,
1286 (BGRA8, BGRA8) => true,
1287 (Gray8, out_type) => matches!(out_type, RGB8 | RGBA8 | BGRA8),
1288 (GrayA8, out_type) => matches!(out_type, RGBA8 | BGRA8),
1289 (CMYK, RGB8) => true,
1292 if !matching_format {
1293 debug_assert!(false, "input/output type");
1296 let mut transform: Box<qcms_transform> = Box::new(Default::default());
1297 let mut precache: bool = false;
1298 if output.precache_output.is_some() {
1301 // This precache assumes RGB_SIGNATURE (fails on GRAY_SIGNATURE, for instance)
1302 if SUPPORTS_ICCV4.load(Ordering::Relaxed)
1303 && (in_type == RGB8 || in_type == RGBA8 || in_type == BGRA8 || in_type == CMYK)
1304 && (input.A2B0.is_some()
1305 || output.B2A0.is_some()
1306 || input.mAB.is_some()
1307 || output.mAB.is_some())
1309 if in_type == CMYK {
1310 return transform_precacheLUT_cmyk_float(transform, input, output, 17, in_type);
1312 // Precache the transformation to a CLUT 33x33x33 in size.
1313 // 33 is used by many profiles and works well in pratice.
1314 // This evenly divides 256 into blocks of 8x8x8.
1315 // TODO For transforming small data sets of about 200x200 or less
1316 // precaching should be avoided.
1317 let result = transform_precacheLUT_float(transform, input, output, 33, in_type);
1318 debug_assert!(result.is_some(), "precacheLUT failed");
1322 transform.precache_output = Some(Arc::clone(output.precache_output.as_ref().unwrap()));
1324 if output.redTRC.is_none() || output.greenTRC.is_none() || output.blueTRC.is_none() {
1327 transform.output_gamma_lut_r = build_output_lut(output.redTRC.as_deref().unwrap());
1328 transform.output_gamma_lut_g = build_output_lut(output.greenTRC.as_deref().unwrap());
1329 transform.output_gamma_lut_b = build_output_lut(output.blueTRC.as_deref().unwrap());
1331 if transform.output_gamma_lut_r.is_none()
1332 || transform.output_gamma_lut_g.is_none()
1333 || transform.output_gamma_lut_b.is_none()
1338 if input.color_space == RGB_SIGNATURE {
1340 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1341 if is_x86_feature_detected!("avx") {
1342 if in_type == RGB8 {
1343 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_avx)
1344 } else if in_type == RGBA8 {
1345 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_avx)
1346 } else if in_type == BGRA8 {
1347 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_avx)
1349 } else if cfg!(not(miri)) && is_x86_feature_detected!("sse2") {
1350 if in_type == RGB8 {
1351 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_sse2)
1352 } else if in_type == RGBA8 {
1353 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_sse2)
1354 } else if in_type == BGRA8 {
1355 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_sse2)
1359 #[cfg(all(target_arch = "arm", feature = "neon"))]
1360 let neon_supported = is_arm_feature_detected!("neon");
1361 #[cfg(all(target_arch = "aarch64", feature = "neon"))]
1362 let neon_supported = is_aarch64_feature_detected!("neon");
1364 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))]
1366 if in_type == RGB8 {
1367 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_neon)
1368 } else if in_type == RGBA8 {
1369 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_neon)
1370 } else if in_type == BGRA8 {
1371 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_neon)
1375 if transform.transform_fn.is_none() {
1376 if in_type == RGB8 {
1377 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_precache)
1378 } else if in_type == RGBA8 {
1379 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_precache)
1380 } else if in_type == BGRA8 {
1381 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_precache)
1384 } else if in_type == RGB8 {
1385 transform.transform_fn = Some(qcms_transform_data_rgb_out_lut)
1386 } else if in_type == RGBA8 {
1387 transform.transform_fn = Some(qcms_transform_data_rgba_out_lut)
1388 } else if in_type == BGRA8 {
1389 transform.transform_fn = Some(qcms_transform_data_bgra_out_lut)
1391 //XXX: avoid duplicating tables if we can
1392 transform.input_gamma_table_r = build_input_gamma_table(input.redTRC.as_deref());
1393 transform.input_gamma_table_g = build_input_gamma_table(input.greenTRC.as_deref());
1394 transform.input_gamma_table_b = build_input_gamma_table(input.blueTRC.as_deref());
1395 if transform.input_gamma_table_r.is_none()
1396 || transform.input_gamma_table_g.is_none()
1397 || transform.input_gamma_table_b.is_none()
1401 /* build combined colorant matrix */
1403 let in_matrix: Matrix = build_colorant_matrix(input);
1404 let mut out_matrix: Matrix = build_colorant_matrix(output);
1405 out_matrix = out_matrix.invert()?;
1407 let result_0: Matrix = Matrix::multiply(out_matrix, in_matrix);
1408 /* check for NaN values in the matrix and bail if we find any */
1413 #[allow(clippy::eq_op, clippy::float_cmp)]
1414 if result_0.m[i as usize][j as usize].is_nan() {
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 = build_input_gamma_table(input.grayTRC.as_deref());
1434 transform.input_gamma_table_gray.as_ref()?;
1436 if out_type == RGB8 {
1437 transform.transform_fn = Some(qcms_transform_data_gray_out_precache)
1438 } else if out_type == RGBA8 {
1439 if in_type == Gray8 {
1440 transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_precache)
1442 transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_precache)
1444 } else if out_type == BGRA8 {
1445 if in_type == Gray8 {
1446 transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_precache)
1448 transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_precache)
1451 } else if out_type == RGB8 {
1452 transform.transform_fn = Some(qcms_transform_data_gray_out_lut)
1453 } else if out_type == RGBA8 {
1454 if in_type == Gray8 {
1455 transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_lut)
1457 transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_lut)
1459 } else if out_type == BGRA8 {
1460 if in_type == Gray8 {
1461 transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_lut)
1463 transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_lut)
1467 debug_assert!(false, "unexpected colorspace");
1470 debug_assert!(transform.transform_fn.is_some());
1473 /// A transform from an input profile to an output one.
1474 pub struct Transform {
1477 xfm: Box<qcms_transform>,
1481 /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent`
1482 pub fn new(input: &Profile, output: &Profile, ty: DataType, intent: Intent) -> Option<Self> {
1483 transform_create(input, ty, output, ty, intent).map(|xfm| Transform {
1490 /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent`
1498 transform_create(input, src_ty, output, dst_ty, intent).map(|xfm| Transform {
1505 /// Apply the color space transform to `data`
1506 pub fn apply(&self, data: &mut [u8]) {
1507 if data.len() % self.src_ty.bytes_per_pixel() != 0 {
1509 "incomplete pixels: should be a multiple of {} got {}",
1510 self.src_ty.bytes_per_pixel(),
1515 self.xfm.transform_fn.expect("non-null function pointer")(
1519 data.len() / self.src_ty.bytes_per_pixel(),
1524 /// Apply the color space transform to `data`
1525 pub fn convert(&self, src: &[u8], dst: &mut [u8]) {
1526 if src.len() % self.src_ty.bytes_per_pixel() != 0 {
1528 "incomplete pixels: should be a multiple of {} got {}",
1529 self.src_ty.bytes_per_pixel(),
1533 if dst.len() % self.dst_ty.bytes_per_pixel() != 0 {
1535 "incomplete pixels: should be a multiple of {} got {}",
1536 self.dst_ty.bytes_per_pixel(),
1541 src.len() / self.src_ty.bytes_per_pixel(),
1542 dst.len() / self.dst_ty.bytes_per_pixel()
1545 self.xfm.transform_fn.expect("non-null function pointer")(
1549 src.len() / self.src_ty.bytes_per_pixel(),
1556 pub extern "C" fn qcms_enable_iccv4() {
1557 SUPPORTS_ICCV4.store(true, Ordering::Relaxed);