Bug 1925561 - Use a quicker radius calculation for ArcParams. r=aosmond
[gecko.git] / gfx / qcms / src / transform.rs
blob54f89864ebc653d3351780cc6d4dc5723ec36b6f
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:
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,
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     },
39 use crate::{
40     iccread::{qcms_CIE_xyY, qcms_CIE_xyYTRIPLE, Profile, GRAY_SIGNATURE, RGB_SIGNATURE},
41     transform_util::clamp_float,
42     Intent,
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     },
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;
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;
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],
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     }
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 */
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,
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,
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     }
152 use DataType::*;
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,
163 pub trait Format {
164     const kRIndex: usize;
165     const kGIndex: usize;
166     const kBIndex: usize;
167     const kAIndex: usize;
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;
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;
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;
197 pub trait GrayFormat {
198     const has_alpha: bool;
201 pub struct Gray;
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;
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     }
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.
227 // the alghoritm:
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 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;
245     if yn == 0.0f64 {
246         return None;
247     }
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;
279     Some(result)
281 /* CIE Illuminant D50 */
282 const D50_XYZ: CIE_XYZ = CIE_XYZ {
283     X: 0.9642f64,
284     Y: 1.0000f64,
285     Z: 0.8249f64,
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 {
291         X: 0.,
292         Y: 0.,
293         Z: 0.,
294     };
295     dest.X = source.x / source.y * source.Y;
296     dest.Y = source.Y;
297     dest.Z = (1f64 - source.x - source.y) / source.y * source.Y;
298     dest
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,
305     chad: Matrix,
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];
324     cone.m[0][1] = 0.;
325     cone.m[0][2] = 0.;
326     cone.m[1][0] = 0.;
327     cone.m[1][1] = cone_dest_rgb.v[1] / cone_source_rgb.v[1];
328     cone.m[1][2] = 0.;
329     cone.m[2][0] = 0.;
330     cone.m[2][1] = 0.;
331     cone.m[2][2] = cone_dest_rgb.v[2] / cone_source_rgb.v[2];
332     // Normalize
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 = {
340         Matrix {
341             m: [
342                 [0.8951, 0.2664, -0.1614],
343                 [-0.7502, 1.7135, 0.0367],
344                 [0.0389, -0.0685, 1.0296],
345             ],
346         }
347     };
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 {
353         return None;
354     }
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,
364 ) -> bool {
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,
369     };
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);
381     true
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,
397     mut src: *const u8,
398     mut dest: *mut u8,
399     length: usize,
400 ) {
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();
404     let mut i: u32 = 0;
405     while (i as usize) < length {
406         let fresh0 = src;
407         src = src.offset(1);
408         let device: u8 = *fresh0;
409         let mut alpha: u8 = 0xffu8;
410         if I::has_alpha {
411             let fresh1 = src;
412             src = src.offset(1);
413             alpha = *fresh1
414         }
415         let linear: f32 = input_gamma_table_gray[device as usize];
417         let out_device_r: f32 = lut_interp_linear(
418             linear as f64,
419             &transform.output_gamma_lut_r.as_ref().unwrap(),
420         );
421         let out_device_g: f32 = lut_interp_linear(
422             linear as f64,
423             &transform.output_gamma_lut_g.as_ref().unwrap(),
424         );
425         let out_device_b: f32 = lut_interp_linear(
426             linear as f64,
427             &transform.output_gamma_lut_b.as_ref().unwrap(),
428         );
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
434         }
435         dest = dest.offset(components as isize);
436         i += 1
437     }
439 unsafe fn qcms_transform_data_gray_out_lut(
440     transform: &qcms_transform,
441     src: *const u8,
442     dest: *mut u8,
443     length: usize,
444 ) {
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,
449     src: *const u8,
450     dest: *mut u8,
451     length: usize,
452 ) {
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,
457     src: *const u8,
458     dest: *mut u8,
459     length: usize,
460 ) {
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,
465     src: *const u8,
466     dest: *mut u8,
467     length: usize,
468 ) {
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,
473     src: *const u8,
474     dest: *mut u8,
475     length: usize,
476 ) {
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,
481     mut src: *const u8,
482     mut dest: *mut u8,
483     length: usize,
484 ) {
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
493         .as_ref()
494         .unwrap()
495         .as_ptr();
497     let mut i: u32 = 0;
498     while (i as usize) < length {
499         let fresh2 = src;
500         src = src.offset(1);
501         let device: u8 = *fresh2;
502         let mut alpha: u8 = 0xffu8;
503         if I::has_alpha {
504             let fresh3 = src;
505             src = src.offset(1);
506             alpha = *fresh3
507         }
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
517         }
518         dest = dest.offset(components as isize);
519         i += 1
520     }
522 unsafe fn qcms_transform_data_gray_out_precache(
523     transform: &qcms_transform,
524     src: *const u8,
525     dest: *mut u8,
526     length: usize,
527 ) {
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,
532     src: *const u8,
533     dest: *mut u8,
534     length: usize,
535 ) {
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,
540     src: *const u8,
541     dest: *mut u8,
542     length: usize,
543 ) {
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,
548     src: *const u8,
549     dest: *mut u8,
550     length: usize,
551 ) {
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,
556     src: *const u8,
557     dest: *mut u8,
558     length: usize,
559 ) {
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,
564     mut src: *const u8,
565     mut dest: *mut u8,
566     length: usize,
567 ) {
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;
577     let mut i: u32 = 0;
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)
585         }
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
607         }
608         dest = dest.offset(components as isize);
609         i += 1
610     }
612 #[no_mangle]
613 pub unsafe fn qcms_transform_data_rgb_out_lut_precache(
614     transform: &qcms_transform,
615     src: *const u8,
616     dest: *mut u8,
617     length: usize,
618 ) {
619     qcms_transform_data_template_lut_precache::<RGB>(transform, src, dest, length);
621 #[no_mangle]
622 pub unsafe fn qcms_transform_data_rgba_out_lut_precache(
623     transform: &qcms_transform,
624     src: *const u8,
625     dest: *mut u8,
626     length: usize,
627 ) {
628     qcms_transform_data_template_lut_precache::<RGBA>(transform, src, dest, length);
630 #[no_mangle]
631 pub unsafe fn qcms_transform_data_bgra_out_lut_precache(
632     transform: &qcms_transform,
633     src: *const u8,
634     dest: *mut u8,
635     length: usize,
636 ) {
637     qcms_transform_data_template_lut_precache::<BGRA>(transform, src, dest, length);
639 // Not used
641 static void qcms_transform_data_clut(const qcms_transform *transform, const unsigned char *src, unsigned char *dest, size_t length) {
642     unsigned int i;
643     int xy_len = 1;
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);
693     }
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,
702     mut src: *const u8,
703     mut dest: *mut u8,
704     length: usize,
705 ) {
706     let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
708     let xy_len: i32 = 1;
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);
716     let mut i: u32 = 0;
717     while (i as usize) < length {
718         let c0_r: f32;
719         let c1_r: f32;
720         let c2_r: f32;
721         let c3_r: f32;
722         let c0_g: f32;
723         let c1_g: f32;
724         let c2_g: f32;
725         let c3_g: f32;
726         let c0_b: f32;
727         let c1_b: f32;
728         let c2_b: f32;
729         let c3_b: f32;
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)
736         }
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)
752         };
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);
757         if rx >= ry {
758             if ry >= rz {
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);
769             } else if rx >= rz {
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);
780             } else {
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;
791             }
792         } else if rx >= rz {
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);
803         } else if ry >= rz {
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);
814         } else {
815             //rz > ry && ry > rx
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;
825         }
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
834         }
835         dest = dest.offset(components as isize);
836         i += 1
837     }
840 unsafe fn tetra(
841     transform: &qcms_transform,
842     table: *const f32,
843     in_r: u8,
844     in_g: u8,
845     in_b: u8,
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;
853     let xy_len: i32 = 1;
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)
867     };
868     let c0_r: f32;
869     let c1_r: f32;
870     let c2_r: f32;
871     let c3_r: f32;
872     let c0_g: f32;
873     let c1_g: f32;
874     let c2_g: f32;
875     let c3_g: f32;
876     let c0_b: f32;
877     let c1_b: f32;
878     let c2_b: f32;
879     let c3_b: f32;
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);
883     if rx >= ry {
884         if ry >= rz {
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);
895         } else if rx >= rz {
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);
906         } else {
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;
917         }
918     } else if rx >= rz {
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);
929     } else if ry >= rz {
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);
940     } else {
941         //rz > ry && ry > rx
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;
951     }
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)
958 #[inline]
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,
968     mut src: *const u8,
969     mut dest: *mut u8,
970     length: usize,
971 ) {
972     let table = transform.clut.as_ref().unwrap().as_ptr();
973     assert!(
974         transform.clut.as_ref().unwrap().len()
975             >= ((transform.grid_size as i32).pow(4) * 3) as usize
976     );
977     for _ in 0..length {
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);
982         src = src.offset(4);
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);
1001     }
1004 unsafe fn qcms_transform_data_tetra_clut_rgb(
1005     transform: &qcms_transform,
1006     src: *const u8,
1007     dest: *mut u8,
1008     length: usize,
1009 ) {
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,
1014     src: *const u8,
1015     dest: *mut u8,
1016     length: usize,
1017 ) {
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,
1022     src: *const u8,
1023     dest: *mut u8,
1024     length: usize,
1025 ) {
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,
1030     mut src: *const u8,
1031     mut dest: *mut u8,
1032     length: usize,
1033 ) {
1034     let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
1036     let mat = &transform.matrix;
1037     let mut i: u32 = 0;
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)
1048         }
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(),
1064         );
1065         let out_device_g: f32 = lut_interp_linear(
1066             out_linear_g as f64,
1067             transform.output_gamma_lut_g.as_ref().unwrap(),
1068         );
1069         let out_device_b: f32 = lut_interp_linear(
1070             out_linear_b as f64,
1071             transform.output_gamma_lut_b.as_ref().unwrap(),
1072         );
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
1078         }
1079         dest = dest.offset(components as isize);
1080         i += 1
1081     }
1083 #[no_mangle]
1084 pub unsafe fn qcms_transform_data_rgb_out_lut(
1085     transform: &qcms_transform,
1086     src: *const u8,
1087     dest: *mut u8,
1088     length: usize,
1089 ) {
1090     qcms_transform_data_template_lut::<RGB>(transform, src, dest, length);
1092 #[no_mangle]
1093 pub unsafe fn qcms_transform_data_rgba_out_lut(
1094     transform: &qcms_transform,
1095     src: *const u8,
1096     dest: *mut u8,
1097     length: usize,
1098 ) {
1099     qcms_transform_data_template_lut::<RGBA>(transform, src, dest, length);
1101 #[no_mangle]
1102 pub unsafe fn qcms_transform_data_bgra_out_lut(
1103     transform: &qcms_transform,
1104     src: *const u8,
1105     dest: *mut u8,
1106     length: usize,
1107 ) {
1108     qcms_transform_data_template_lut::<BGRA>(transform, src, dest, length);
1111 fn precache_create() -> Arc<PrecacheOuput> {
1112     Arc::new(PrecacheOuput::default())
1115 #[no_mangle]
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 {
1121     m: [
1122         [0.8951, 0.2664, -0.1614],
1123         [-0.7502, 1.7135, 0.0367],
1124         [0.0389, -0.0685, 1.0296],
1125     ],
1128 const bradford_matrix_inv: Matrix = Matrix {
1129     m: [
1130         [0.9869929, -0.1470543, 0.1599627],
1131         [0.4323053, 0.5183603, 0.0492912],
1132         [-0.0085287, 0.0400428, 0.9684867],
1133     ],
1136 // See ICCv4 E.3
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]],
1152     };
1153     Matrix::multiply(
1154         bradford_matrix_inv,
1155         Matrix::multiply(white_adaption, bradford_matrix),
1156     )
1158 #[no_mangle]
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 {
1162         return;
1163     }
1164     if SUPPORTS_ICCV4.load(Ordering::Relaxed) {
1165         /* don't precache since we will use the B2A LUT */
1166         if profile.B2A0.is_some() {
1167             return;
1168         }
1169         /* don't precache since we will use the mBA LUT */
1170         if profile.mBA.is_some() {
1171             return;
1172         }
1173     }
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() {
1176         return;
1177     }
1178     if profile.precache_output.is_none() {
1179         let mut precache = precache_create();
1180         compute_precache(
1181             profile.redTRC.as_deref().unwrap(),
1182             &mut Arc::get_mut(&mut precache).unwrap().lut_r,
1183         );
1184         compute_precache(
1185             profile.greenTRC.as_deref().unwrap(),
1186             &mut Arc::get_mut(&mut precache).unwrap().lut_g,
1187         );
1188         compute_precache(
1189             profile.blueTRC.as_deref().unwrap(),
1190             &mut Arc::get_mut(&mut precache).unwrap().lut_b,
1191         );
1192         profile.precache_output = Some(precache);
1193     }
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>,
1198     input: &Profile,
1199     output: &Profile,
1200     samples: i32,
1201     in_type: DataType,
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);
1215             }
1216         }
1217     }
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)
1228         }
1229         debug_assert!(transform.transform_fn.is_some());
1230     } else {
1231         return None;
1232     }
1234     Some(transform)
1237 fn transform_precacheLUT_cmyk_float(
1238     mut transform: Box<qcms_transform>,
1239     input: &Profile,
1240     output: &Profile,
1241     samples: i32,
1242     in_type: DataType,
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);
1258                 }
1259             }
1260         }
1261     }
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)
1268     } else {
1269         return None;
1270     }
1272     Some(transform)
1275 pub fn transform_create(
1276     input: &Profile,
1277     in_type: DataType,
1278     output: &Profile,
1279     out_type: DataType,
1280     _intent: Intent,
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,
1290         _ => false,
1291     };
1292     if !matching_format {
1293         debug_assert!(false, "input/output type");
1294         return None;
1295     }
1296     let mut transform: Box<qcms_transform> = Box::new(Default::default());
1297     let mut precache: bool = false;
1298     if output.precache_output.is_some() {
1299         precache = true
1300     }
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())
1308     {
1309         if in_type == CMYK {
1310             return transform_precacheLUT_cmyk_float(transform, input, output, 17, in_type);
1311         }
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");
1319         return result;
1320     }
1321     if precache {
1322         transform.precache_output = Some(Arc::clone(output.precache_output.as_ref().unwrap()));
1323     } else {
1324         if output.redTRC.is_none() || output.greenTRC.is_none() || output.blueTRC.is_none() {
1325             return None;
1326         }
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()
1334         {
1335             return None;
1336         }
1337     }
1338     if input.color_space == RGB_SIGNATURE {
1339         if precache {
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)
1348                 }
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)
1356                 }
1357             }
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"))]
1365             if neon_supported {
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)
1372                 }
1373             }
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)
1382                 }
1383             }
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)
1390         }
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()
1398         {
1399             return None;
1400         }
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 */
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 = build_input_gamma_table(input.grayTRC.as_deref());
1434         transform.input_gamma_table_gray.as_ref()?;
1435         if precache {
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)
1441                 } else {
1442                     transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_precache)
1443                 }
1444             } else if out_type == BGRA8 {
1445                 if in_type == Gray8 {
1446                     transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_precache)
1447                 } else {
1448                     transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_precache)
1449                 }
1450             }
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)
1456             } else {
1457                 transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_lut)
1458             }
1459         } else if out_type == BGRA8 {
1460             if in_type == Gray8 {
1461                 transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_lut)
1462             } else {
1463                 transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_lut)
1464             }
1465         }
1466     } else {
1467         debug_assert!(false, "unexpected colorspace");
1468         return None;
1469     }
1470     debug_assert!(transform.transform_fn.is_some());
1471     Some(transform)
1473 /// A transform from an input profile to an output one.
1474 pub struct Transform {
1475     src_ty: DataType,
1476     dst_ty: DataType,
1477     xfm: Box<qcms_transform>,
1480 impl 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 {
1484             src_ty: ty,
1485             dst_ty: ty,
1486             xfm,
1487         })
1488     }
1490     /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent`
1491     pub fn new_to(
1492         input: &Profile,
1493         output: &Profile,
1494         src_ty: DataType,
1495         dst_ty: DataType,
1496         intent: Intent,
1497     ) -> Option<Self> {
1498         transform_create(input, src_ty, output, dst_ty, intent).map(|xfm| Transform {
1499             src_ty,
1500             dst_ty,
1501             xfm,
1502         })
1503     }
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 {
1508             panic!(
1509                 "incomplete pixels: should be a multiple of {} got {}",
1510                 self.src_ty.bytes_per_pixel(),
1511                 data.len()
1512             )
1513         }
1514         unsafe {
1515             self.xfm.transform_fn.expect("non-null function pointer")(
1516                 &*self.xfm,
1517                 data.as_ptr(),
1518                 data.as_mut_ptr(),
1519                 data.len() / self.src_ty.bytes_per_pixel(),
1520             );
1521         }
1522     }
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 {
1527             panic!(
1528                 "incomplete pixels: should be a multiple of {} got {}",
1529                 self.src_ty.bytes_per_pixel(),
1530                 src.len()
1531             )
1532         }
1533         if dst.len() % self.dst_ty.bytes_per_pixel() != 0 {
1534             panic!(
1535                 "incomplete pixels: should be a multiple of {} got {}",
1536                 self.dst_ty.bytes_per_pixel(),
1537                 dst.len()
1538             )
1539         }
1540         assert_eq!(
1541             src.len() / self.src_ty.bytes_per_pixel(),
1542             dst.len() / self.dst_ty.bytes_per_pixel()
1543         );
1544         unsafe {
1545             self.xfm.transform_fn.expect("non-null function pointer")(
1546                 &*self.xfm,
1547                 src.as_ptr(),
1548                 dst.as_mut_ptr(),
1549                 src.len() / self.src_ty.bytes_per_pixel(),
1550             );
1551         }
1552     }
1555 #[no_mangle]
1556 pub extern "C" fn qcms_enable_iccv4() {
1557     SUPPORTS_ICCV4.store(true, Ordering::Relaxed);