summaryrefslogtreecommitdiffstats
path: root/gfx/qcms
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/qcms
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/qcms')
-rw-r--r--gfx/qcms/CONTRIBUTING.md7
-rw-r--r--gfx/qcms/COPYING21
-rw-r--r--gfx/qcms/Cargo.toml22
-rw-r--r--gfx/qcms/ITU-2020.iccbin0 -> 556 bytes
-rw-r--r--gfx/qcms/ITU-709.iccbin0 -> 556 bytes
-rw-r--r--gfx/qcms/README.md36
-rw-r--r--gfx/qcms/fuzz/.gitignore4
-rw-r--r--gfx/qcms/fuzz/Cargo.lock47
-rw-r--r--gfx/qcms/fuzz/Cargo.toml28
-rw-r--r--gfx/qcms/fuzz/fuzz_targets/fuzz_target_qcms.rs95
-rw-r--r--gfx/qcms/fuzz/qcms_fuzzer.dict26
-rw-r--r--gfx/qcms/fuzz/samples/0220-ca351238d719fd07ef8607d326b398fe.iccbin0 -> 220 bytes
-rw-r--r--gfx/qcms/fuzz/samples/0316-eb3f97ab646cd7b66bee80bdfe6098ac.iccbin0 -> 316 bytes
-rw-r--r--gfx/qcms/fuzz/samples/0372-973178997787ee780b4b58ee47cad683.iccbin0 -> 372 bytes
-rw-r--r--gfx/qcms/fuzz/samples/0732-80707d91aea0f8e64ef0286cc7720e99.iccbin0 -> 732 bytes
-rw-r--r--gfx/qcms/fuzz/samples/0744-0a5faafe175e682b10c590b03d3f093b.iccbin0 -> 744 bytes
-rw-r--r--gfx/qcms/fuzz/samples/1809-2bd4b77651214ca6110fdbee2502671e.iccbin0 -> 1809 bytes
-rw-r--r--gfx/qcms/moz.build10
-rw-r--r--gfx/qcms/profiles/B2A0-ident.iccbin0 -> 704 bytes
-rw-r--r--gfx/qcms/profiles/displaycal-lut-stripped.iccbin0 -> 17236 bytes
-rw-r--r--gfx/qcms/profiles/lcms_samsung_syncmaster.iccbin0 -> 1944 bytes
-rw-r--r--gfx/qcms/profiles/lcms_thinkpad_w540.iccbin0 -> 1428 bytes
-rw-r--r--gfx/qcms/profiles/parametric-thresh.iccbin0 -> 476 bytes
-rw-r--r--gfx/qcms/profiles/ps_cmyk_min.iccbin0 -> 836 bytes
-rw-r--r--gfx/qcms/qcms.h241
-rw-r--r--gfx/qcms/qcmsint.h120
-rw-r--r--gfx/qcms/qcmstypes.h59
-rw-r--r--gfx/qcms/sRGB_lcms.iccbin0 -> 656 bytes
-rw-r--r--gfx/qcms/src/c_bindings.rs514
-rw-r--r--gfx/qcms/src/chain.rs1029
-rw-r--r--gfx/qcms/src/gtest.rs962
-rw-r--r--gfx/qcms/src/iccread.rs1742
-rw-r--r--gfx/qcms/src/lib.rs72
-rw-r--r--gfx/qcms/src/matrix.rs134
-rw-r--r--gfx/qcms/src/transform.rs1571
-rw-r--r--gfx/qcms/src/transform_avx.rs228
-rw-r--r--gfx/qcms/src/transform_neon.rs155
-rw-r--r--gfx/qcms/src/transform_sse2.rs159
-rw-r--r--gfx/qcms/src/transform_util.rs608
39 files changed, 7890 insertions, 0 deletions
diff --git a/gfx/qcms/CONTRIBUTING.md b/gfx/qcms/CONTRIBUTING.md
new file mode 100644
index 0000000000..c68d60621c
--- /dev/null
+++ b/gfx/qcms/CONTRIBUTING.md
@@ -0,0 +1,7 @@
+qcms is mirrored to https://github.com/FirefoxGraphics/qcms but all development
+happens via the regular [Firefox contribution process](https://firefox-source-docs.mozilla.org/setup/contributing_code.html).
+
+Bug should be filed under the 'Core' product in the 'Graphics: Color Management' component.
+
+If this process is too onerous, github PRs and issues can be used but they will still need to be migrated over
+to the Firefox process before landing.
diff --git a/gfx/qcms/COPYING b/gfx/qcms/COPYING
new file mode 100644
index 0000000000..eec8246dfa
--- /dev/null
+++ b/gfx/qcms/COPYING
@@ -0,0 +1,21 @@
+qcms
+Copyright (C) 2009-2024 Mozilla Corporation
+Copyright (C) 1998-2007 Marti Maria
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/gfx/qcms/Cargo.toml b/gfx/qcms/Cargo.toml
new file mode 100644
index 0000000000..094d1183d1
--- /dev/null
+++ b/gfx/qcms/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "qcms"
+authors = ["Jeff Muizelaar", "Benoit Girard", "Andrew Osmond"]
+version = "0.3.0"
+edition = "2018"
+include = ["src/**/*", "build.rs"]
+description = "lightweight color management"
+documentation = "https://docs.rs/qcms"
+license = "MIT"
+repository = "https://github.com/FirefoxGraphics/qcms"
+keywords = ["color"]
+categories = ["graphics"]
+
+[features]
+default = ["iccv4-enabled", "cmyk"]
+c_bindings = ["libc"]
+neon = []
+iccv4-enabled = []
+cmyk = []
+
+[dependencies]
+libc = {version = "0.2", optional = true }
diff --git a/gfx/qcms/ITU-2020.icc b/gfx/qcms/ITU-2020.icc
new file mode 100644
index 0000000000..0a91cac08c
--- /dev/null
+++ b/gfx/qcms/ITU-2020.icc
Binary files differ
diff --git a/gfx/qcms/ITU-709.icc b/gfx/qcms/ITU-709.icc
new file mode 100644
index 0000000000..3b9d46a283
--- /dev/null
+++ b/gfx/qcms/ITU-709.icc
Binary files differ
diff --git a/gfx/qcms/README.md b/gfx/qcms/README.md
new file mode 100644
index 0000000000..1997abcabe
--- /dev/null
+++ b/gfx/qcms/README.md
@@ -0,0 +1,36 @@
+# qcms
+[![Crates.io](https://img.shields.io/crates/v/qcms.svg)](https://crates.io/crates/qcms)
+[![Documentation](https://docs.rs/qcms/badge.svg)](https://docs.rs/qcms)
+
+
+Firefox's library for transforming image data between ICC profiles.
+
+## Example
+```rust
+ // Decode the jpeg
+ let mut d = jpeg_decoder::Decoder::new(std::fs::File::open("/Users/jrmuizel/Desktop/DSCF2460.jpg").unwrap());
+ let mut data = d.decode().unwrap();
+ let info = d.info().unwrap();
+
+ // Extract the profile after decode
+ let profile = d.icc_profile().unwrap();
+
+ // Create a new qcms Profile
+ let input = qcms::Profile::new_from_slice(&profile).unwrap();
+ let mut output = qcms::Profile::new_sRGB();
+ output.precache_output_transform();
+
+ // Create a transform between input and output profiles and apply it.
+ let xfm = qcms::Transform::new(&input, &output, qcms::DataType::RGB8, qcms::Intent::default()).unwrap();
+ xfm.apply(&mut data);
+
+ // write the result to a PNG
+ let mut encoder = png::Encoder::new(std::fs::File::create("out.png").unwrap(), info.width as u32, info.height as u32);
+ encoder.set_color(png::ColorType::Rgb);
+ encoder.set_srgb(png::SrgbRenderingIntent::Perceptual);
+ let mut writer = encoder.write_header().unwrap();
+ writer.write_image_data(&data).unwrap(); // Save
+```
+
+This library was originally written in C, was converted to Rust using [c2rust](https://c2rust.com/), and then refactored to be mostly
+safe and more idiomatic Rust.
diff --git a/gfx/qcms/fuzz/.gitignore b/gfx/qcms/fuzz/.gitignore
new file mode 100644
index 0000000000..572e03bdf3
--- /dev/null
+++ b/gfx/qcms/fuzz/.gitignore
@@ -0,0 +1,4 @@
+
+target
+corpus
+artifacts
diff --git a/gfx/qcms/fuzz/Cargo.lock b/gfx/qcms/fuzz/Cargo.lock
new file mode 100644
index 0000000000..bfa42bbfaf
--- /dev/null
+++ b/gfx/qcms/fuzz/Cargo.lock
@@ -0,0 +1,47 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "arbitrary"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b"
+
+[[package]]
+name = "cc"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
+
+[[package]]
+name = "libc"
+version = "0.2.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86c975d637bc2a2f99440932b731491fc34c7f785d239e38af3addd3c2fd0e46"
+dependencies = [
+ "arbitrary",
+ "cc",
+]
+
+[[package]]
+name = "qcms"
+version = "0.2.0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "qcms-fuzz"
+version = "0.0.0"
+dependencies = [
+ "libc",
+ "libfuzzer-sys",
+ "qcms",
+]
diff --git a/gfx/qcms/fuzz/Cargo.toml b/gfx/qcms/fuzz/Cargo.toml
new file mode 100644
index 0000000000..08b93e9e36
--- /dev/null
+++ b/gfx/qcms/fuzz/Cargo.toml
@@ -0,0 +1,28 @@
+
+[package]
+name = "qcms-fuzz"
+version = "0.0.0"
+authors = ["Automatically generated"]
+publish = false
+edition = "2018"
+
+[package.metadata]
+cargo-fuzz = true
+
+[dependencies]
+libfuzzer-sys = "0.4"
+libc = "0.2"
+
+[dependencies.qcms]
+path = ".."
+features = ["c_bindings"]
+
+# Prevent this from interfering with workspaces
+[workspace]
+members = ["."]
+
+[[bin]]
+name = "fuzz_target_qcms"
+path = "fuzz_targets/fuzz_target_qcms.rs"
+test = false
+doc = false
diff --git a/gfx/qcms/fuzz/fuzz_targets/fuzz_target_qcms.rs b/gfx/qcms/fuzz/fuzz_targets/fuzz_target_qcms.rs
new file mode 100644
index 0000000000..57cd0e2dd8
--- /dev/null
+++ b/gfx/qcms/fuzz/fuzz_targets/fuzz_target_qcms.rs
@@ -0,0 +1,95 @@
+#![no_main]
+use libfuzzer_sys::fuzz_target;
+extern crate qcms;
+extern crate libc;
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use qcms::c_bindings::{qcms_profile, icSigRgbData, icSigCmykData, icSigGrayData, qcms_profile_is_bogus};
+use qcms::c_bindings::{qcms_profile_get_color_space, qcms_profile_get_rendering_intent, qcms_profile_from_memory, qcms_profile_release, qcms_profile_sRGB, qcms_transform_create};
+use qcms::c_bindings::{qcms_profile_precache_output_transform, qcms_transform_data, qcms_transform_release, qcms_enable_iccv4};
+
+use qcms::DataType::*;
+
+ unsafe fn transform(src_profile: *const qcms_profile, dst_profile: *mut qcms_profile, size: usize)
+ {
+ // qcms supports GRAY and RGB profiles as input, and RGB as output.
+
+ let src_color_space = qcms_profile_get_color_space(&*src_profile);
+ let mut src_type = if (size & 1) != 0 { RGBA8 } else { RGB8 };
+ if src_color_space == icSigGrayData {
+ src_type = if (size & 1) != 0 { GrayA8 } else { Gray8 };
+ } else if src_color_space == icSigCmykData {
+ src_type = CMYK;
+ } else if src_color_space != icSigRgbData {
+ return;
+ }
+
+ let dst_color_space = qcms_profile_get_color_space(&*dst_profile);
+ if dst_color_space != icSigRgbData {
+ return;
+ }
+ let dst_type = if (size & 2) != 0 { RGBA8 } else { RGB8 };
+
+ let intent = qcms_profile_get_rendering_intent(&*src_profile);
+ // Firefox calls this on the display profile to increase performance.
+ // Skip with low probability to increase coverage.
+ if (size % 15) != 0 {
+ qcms_profile_precache_output_transform(&mut *dst_profile);
+ }
+
+ let transform =
+ qcms_transform_create(&*src_profile, src_type, &*dst_profile, dst_type, intent);
+ if transform == std::ptr::null_mut() {
+ return;
+ }
+
+ const SRC_SIZE: usize = 36;
+ let src:[u8; SRC_SIZE] = [
+ 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x7F, 0x7F, 0xFF, 0x7F, 0x10, 0x20, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xB0, 0xBF, 0xEF, 0x6F,
+ 0x3F, 0xC0, 0x9F, 0xE0, 0x90, 0xCF, 0x40, 0xAF, 0x0F, 0x01, 0x60, 0xF0,
+ ];
+ let mut dst: [u8; 36 * 4] = [0; 144]; // 4x in case of GRAY to RGBA
+
+ qcms_transform_data(&*transform, src.as_ptr() as *const libc::c_void, dst.as_mut_ptr() as *mut libc::c_void, (SRC_SIZE / src_type.bytes_per_pixel()) as usize);
+ qcms_transform_release(transform);
+ }
+
+ unsafe fn do_fuzz(data: &[u8])
+ {
+ let size = data.len();
+ qcms_enable_iccv4();
+
+ let profile = qcms_profile_from_memory(data.as_ptr() as *const libc::c_void, size);
+ if profile == std::ptr::null_mut() {
+ return;
+ }
+
+ let srgb_profile = qcms_profile_sRGB();
+ if srgb_profile == std::ptr::null_mut() {
+ qcms_profile_release(profile);
+ return;
+ }
+
+ transform(profile, srgb_profile, size);
+
+ // Firefox only checks the display (destination) profile.
+ if !qcms_profile_is_bogus(&mut *profile) {
+
+ transform(srgb_profile, profile, size);
+ let identity = qcms::Profile::new_XYZD50();
+ transform(&*identity, profile, size);
+ }
+ qcms_profile_release(profile);
+ qcms_profile_release(srgb_profile);
+
+ return;
+ }
+
+
+
+fuzz_target!(|data: &[u8]| {
+ unsafe { do_fuzz(data) }
+});
diff --git a/gfx/qcms/fuzz/qcms_fuzzer.dict b/gfx/qcms/fuzz/qcms_fuzzer.dict
new file mode 100644
index 0000000000..213193c7d1
--- /dev/null
+++ b/gfx/qcms/fuzz/qcms_fuzzer.dict
@@ -0,0 +1,26 @@
+# v2
+0x41324230="A2B0"
+0x42324130="B2A0"
+0x47524159="GRAY"
+0x4C616220="Lab "
+0x52474220="RGB "
+0x58595a20="XYZ "
+0x62545243="bTRC"
+0x6258595a="bXYZ"
+0x63686164="chad"
+0x63757276="curv"
+0x67545243="gTRC"
+0x6758595a="gXYZ"
+0x6D667431="mft1"
+0x6D667432="mft2"
+0x6b545243="kTRC"
+0x6d6e7472="mntr"
+0x72545243="rTRC"
+0x7258595a="rXYZ"
+0x73636e72="scnr"
+0x73663332="sf32"
+
+# v4
+0x6D414220="mAB "
+0x6D424120="mBA "
+0x70617261="para"
diff --git a/gfx/qcms/fuzz/samples/0220-ca351238d719fd07ef8607d326b398fe.icc b/gfx/qcms/fuzz/samples/0220-ca351238d719fd07ef8607d326b398fe.icc
new file mode 100644
index 0000000000..6dcf942ac1
--- /dev/null
+++ b/gfx/qcms/fuzz/samples/0220-ca351238d719fd07ef8607d326b398fe.icc
Binary files differ
diff --git a/gfx/qcms/fuzz/samples/0316-eb3f97ab646cd7b66bee80bdfe6098ac.icc b/gfx/qcms/fuzz/samples/0316-eb3f97ab646cd7b66bee80bdfe6098ac.icc
new file mode 100644
index 0000000000..12b096cac0
--- /dev/null
+++ b/gfx/qcms/fuzz/samples/0316-eb3f97ab646cd7b66bee80bdfe6098ac.icc
Binary files differ
diff --git a/gfx/qcms/fuzz/samples/0372-973178997787ee780b4b58ee47cad683.icc b/gfx/qcms/fuzz/samples/0372-973178997787ee780b4b58ee47cad683.icc
new file mode 100644
index 0000000000..2d8efe536b
--- /dev/null
+++ b/gfx/qcms/fuzz/samples/0372-973178997787ee780b4b58ee47cad683.icc
Binary files differ
diff --git a/gfx/qcms/fuzz/samples/0732-80707d91aea0f8e64ef0286cc7720e99.icc b/gfx/qcms/fuzz/samples/0732-80707d91aea0f8e64ef0286cc7720e99.icc
new file mode 100644
index 0000000000..1626458464
--- /dev/null
+++ b/gfx/qcms/fuzz/samples/0732-80707d91aea0f8e64ef0286cc7720e99.icc
Binary files differ
diff --git a/gfx/qcms/fuzz/samples/0744-0a5faafe175e682b10c590b03d3f093b.icc b/gfx/qcms/fuzz/samples/0744-0a5faafe175e682b10c590b03d3f093b.icc
new file mode 100644
index 0000000000..2db6991c23
--- /dev/null
+++ b/gfx/qcms/fuzz/samples/0744-0a5faafe175e682b10c590b03d3f093b.icc
Binary files differ
diff --git a/gfx/qcms/fuzz/samples/1809-2bd4b77651214ca6110fdbee2502671e.icc b/gfx/qcms/fuzz/samples/1809-2bd4b77651214ca6110fdbee2502671e.icc
new file mode 100644
index 0000000000..c13db9b200
--- /dev/null
+++ b/gfx/qcms/fuzz/samples/1809-2bd4b77651214ca6110fdbee2502671e.icc
Binary files differ
diff --git a/gfx/qcms/moz.build b/gfx/qcms/moz.build
new file mode 100644
index 0000000000..1e899c7a46
--- /dev/null
+++ b/gfx/qcms/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'qcms.h',
+ 'qcmstypes.h',
+]
diff --git a/gfx/qcms/profiles/B2A0-ident.icc b/gfx/qcms/profiles/B2A0-ident.icc
new file mode 100644
index 0000000000..672553030f
--- /dev/null
+++ b/gfx/qcms/profiles/B2A0-ident.icc
Binary files differ
diff --git a/gfx/qcms/profiles/displaycal-lut-stripped.icc b/gfx/qcms/profiles/displaycal-lut-stripped.icc
new file mode 100644
index 0000000000..79ddef53e9
--- /dev/null
+++ b/gfx/qcms/profiles/displaycal-lut-stripped.icc
Binary files differ
diff --git a/gfx/qcms/profiles/lcms_samsung_syncmaster.icc b/gfx/qcms/profiles/lcms_samsung_syncmaster.icc
new file mode 100644
index 0000000000..3dcde88d06
--- /dev/null
+++ b/gfx/qcms/profiles/lcms_samsung_syncmaster.icc
Binary files differ
diff --git a/gfx/qcms/profiles/lcms_thinkpad_w540.icc b/gfx/qcms/profiles/lcms_thinkpad_w540.icc
new file mode 100644
index 0000000000..c154e7e589
--- /dev/null
+++ b/gfx/qcms/profiles/lcms_thinkpad_w540.icc
Binary files differ
diff --git a/gfx/qcms/profiles/parametric-thresh.icc b/gfx/qcms/profiles/parametric-thresh.icc
new file mode 100644
index 0000000000..1a1f6216d1
--- /dev/null
+++ b/gfx/qcms/profiles/parametric-thresh.icc
Binary files differ
diff --git a/gfx/qcms/profiles/ps_cmyk_min.icc b/gfx/qcms/profiles/ps_cmyk_min.icc
new file mode 100644
index 0000000000..dd4fe0413c
--- /dev/null
+++ b/gfx/qcms/profiles/ps_cmyk_min.icc
Binary files differ
diff --git a/gfx/qcms/qcms.h b/gfx/qcms/qcms.h
new file mode 100644
index 0000000000..d78adb27a6
--- /dev/null
+++ b/gfx/qcms/qcms.h
@@ -0,0 +1,241 @@
+#ifndef QCMS_H
+#define QCMS_H
+
+/* clang-format off */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* if we've already got an ICC_H header we can ignore the following */
+#ifndef ICC_H
+/* icc34 defines */
+
+/*****************************************************************
+ Copyright (c) 1994-1996 SunSoft, Inc.
+
+ Rights Reserved
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without restrict-
+ion, including without limitation the rights to use, copy, modify,
+merge, publish distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-
+INFRINGEMENT. IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
+COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of SunSoft, Inc.
+shall not be used in advertising or otherwise to promote the
+sale, use or other dealings in this Software without written
+authorization from SunSoft Inc.
+******************************************************************/
+
+/*
+ * QCMS, in general, is not threadsafe. However, it should be safe to create
+ * profile and transformation objects on different threads, so long as you
+ * don't use the same objects on different threads at the same time.
+ */
+
+/*
+ * Color Space Signatures
+ * Note that only icSigXYZData and icSigLabData are valid
+ * Profile Connection Spaces (PCSs)
+ */
+typedef enum {
+ icSigXYZData = 0x58595A20L, /* 'XYZ ' */
+ icSigLabData = 0x4C616220L, /* 'Lab ' */
+ icSigLuvData = 0x4C757620L, /* 'Luv ' */
+ icSigYCbCrData = 0x59436272L, /* 'YCbr' */
+ icSigYxyData = 0x59787920L, /* 'Yxy ' */
+ icSigRgbData = 0x52474220L, /* 'RGB ' */
+ icSigGrayData = 0x47524159L, /* 'GRAY' */
+ icSigHsvData = 0x48535620L, /* 'HSV ' */
+ icSigHlsData = 0x484C5320L, /* 'HLS ' */
+ icSigCmykData = 0x434D594BL, /* 'CMYK' */
+ icSigCmyData = 0x434D5920L, /* 'CMY ' */
+ icSig2colorData = 0x32434C52L, /* '2CLR' */
+ icSig3colorData = 0x33434C52L, /* '3CLR' */
+ icSig4colorData = 0x34434C52L, /* '4CLR' */
+ icSig5colorData = 0x35434C52L, /* '5CLR' */
+ icSig6colorData = 0x36434C52L, /* '6CLR' */
+ icSig7colorData = 0x37434C52L, /* '7CLR' */
+ icSig8colorData = 0x38434C52L, /* '8CLR' */
+ icSig9colorData = 0x39434C52L, /* '9CLR' */
+ icSig10colorData = 0x41434C52L, /* 'ACLR' */
+ icSig11colorData = 0x42434C52L, /* 'BCLR' */
+ icSig12colorData = 0x43434C52L, /* 'CCLR' */
+ icSig13colorData = 0x44434C52L, /* 'DCLR' */
+ icSig14colorData = 0x45434C52L, /* 'ECLR' */
+ icSig15colorData = 0x46434C52L, /* 'FCLR' */
+ icMaxEnumData = 0xFFFFFFFFL
+} icColorSpaceSignature;
+#endif
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <cstdint>
+
+struct _qcms_transform;
+typedef struct _qcms_transform qcms_transform;
+
+struct _qcms_profile;
+typedef struct _qcms_profile qcms_profile;
+
+/* these values match the Rendering Intent values from the ICC spec */
+typedef enum {
+ QCMS_INTENT_MIN = 0,
+ QCMS_INTENT_PERCEPTUAL = 0,
+ QCMS_INTENT_RELATIVE_COLORIMETRIC = 1,
+ QCMS_INTENT_SATURATION = 2,
+ QCMS_INTENT_ABSOLUTE_COLORIMETRIC = 3,
+ QCMS_INTENT_MAX = 3,
+
+ /* Chris Murphy (CM consultant) suggests this as a default in the event that we
+ * cannot reproduce relative + Black Point Compensation. BPC brings an
+ * unacceptable performance overhead, so we go with perceptual. */
+ QCMS_INTENT_DEFAULT = QCMS_INTENT_PERCEPTUAL,
+} qcms_intent;
+
+//XXX: I don't really like the _DATA_ prefix
+typedef enum {
+ QCMS_DATA_RGB_8,
+ QCMS_DATA_RGBA_8,
+ QCMS_DATA_BGRA_8,
+ QCMS_DATA_GRAY_8,
+ QCMS_DATA_GRAYA_8,
+ QCMS_DATA_CMYK
+} qcms_data_type;
+
+/* the names for the following two types are sort of ugly */
+typedef struct
+{
+ double x;
+ double y;
+ double Y;
+} qcms_CIE_xyY;
+
+typedef struct
+{
+ qcms_CIE_xyY red;
+ qcms_CIE_xyY green;
+ qcms_CIE_xyY blue;
+} qcms_CIE_xyYTRIPLE;
+
+qcms_profile* qcms_profile_create_rgb_with_gamma_set(
+ qcms_CIE_xyY white_point,
+ qcms_CIE_xyYTRIPLE primaries,
+ float redGamma,
+ float blueGamma,
+ float greenGamma);
+
+qcms_profile* qcms_profile_create_rgb_with_gamma(
+ qcms_CIE_xyY white_point,
+ qcms_CIE_xyYTRIPLE primaries,
+ float gamma);
+
+void qcms_data_create_rgb_with_gamma(
+ qcms_CIE_xyY white_point,
+ qcms_CIE_xyYTRIPLE primaries,
+ float gamma,
+ void **mem,
+ size_t *size);
+
+/* The arguments are standardized Coding-independent Code Points
+ * See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en)
+ *
+ * Don't use enums here because they can't go safely across FFI. */
+qcms_profile* qcms_profile_create_cicp(uint8_t colour_primaries,
+ uint8_t transfer_characteristics);
+
+qcms_profile* qcms_profile_from_memory(const void *mem, size_t size);
+qcms_profile* qcms_profile_from_memory_curves_only(const void *mem, size_t size);
+
+qcms_profile* qcms_profile_from_file(FILE *file);
+qcms_profile* qcms_profile_from_path(const char *path);
+
+void qcms_data_from_path(const char *path, void **mem, size_t *size);
+
+#ifdef _WIN32
+qcms_profile* qcms_profile_from_unicode_path(const wchar_t *path);
+void qcms_data_from_unicode_path(const wchar_t *path, void **mem, size_t *size);
+#endif
+
+qcms_CIE_xyY qcms_white_point_sRGB(void);
+qcms_profile* qcms_profile_sRGB(void);
+qcms_profile* qcms_profile_displayP3(void);
+
+void qcms_profile_release(qcms_profile *profile);
+
+bool qcms_profile_is_bogus(qcms_profile *profile);
+qcms_intent qcms_profile_get_rendering_intent(qcms_profile *profile);
+icColorSpaceSignature qcms_profile_get_color_space(qcms_profile *profile);
+bool qcms_profile_is_sRGB(qcms_profile *profile);
+
+void qcms_profile_precache_output_transform(qcms_profile *profile);
+
+qcms_transform* qcms_transform_create(
+ qcms_profile *in, qcms_data_type in_type,
+ qcms_profile* out, qcms_data_type out_type,
+ qcms_intent intent);
+
+void qcms_transform_release(qcms_transform *);
+
+void qcms_transform_data(qcms_transform *transform, const void *src, void *dest, size_t length);
+
+void qcms_enable_iccv4();
+void qcms_enable_neon();
+void qcms_enable_avx();
+
+
+
+// -
+/*
+struct qcms_mat3r3 {
+ struct row {
+ float cols[3];
+ };
+ row rows[3];
+};
+*/
+struct qcms_profile_data {
+ uint32_t class_type;
+ uint32_t color_space;
+ uint32_t pcs;
+ qcms_intent rendering_intent;
+ float red_colorant_xyzd50[3];
+ float blue_colorant_xyzd50[3];
+ float green_colorant_xyzd50[3];
+ int32_t linear_from_trc_red_samples;
+ int32_t linear_from_trc_blue_samples;
+ int32_t linear_from_trc_green_samples;
+};
+
+void qcms_profile_get_data(const qcms_profile*, qcms_profile_data* out_data);
+
+
+enum class qcms_color_channel : uint8_t {
+ Red,
+ Green,
+ Blue,
+};
+
+void qcms_profile_get_lut(const qcms_profile*, qcms_color_channel,
+ float* begin, float* end);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/gfx/qcms/qcmsint.h b/gfx/qcms/qcmsint.h
new file mode 100644
index 0000000000..b08fc9490b
--- /dev/null
+++ b/gfx/qcms/qcmsint.h
@@ -0,0 +1,120 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
+#ifndef QCMS_INT_H
+#define QCMS_INT_H
+
+#include "qcms.h"
+#include "qcmstypes.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _MSC_VER
+#define ALIGN __declspec(align(16))
+#else
+#define ALIGN __attribute__(( aligned (16) ))
+#endif
+
+struct _qcms_transform;
+
+typedef void (*transform_fn_t)(const struct _qcms_transform *transform, const unsigned char *src, unsigned char *dest, size_t length);
+
+
+void qcms_transform_data_rgb_out_lut(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgba_out_lut(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_bgra_out_lut(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+
+void qcms_transform_data_rgb_out_lut_precache(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgba_out_lut_precache(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_bgra_out_lut_precache(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+
+void qcms_transform_data_rgb_out_lut_avx(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgba_out_lut_avx(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_bgra_out_lut_avx(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgb_out_lut_sse2(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgba_out_lut_sse2(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_bgra_out_lut_sse2(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgb_out_lut_sse1(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgba_out_lut_sse1(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_bgra_out_lut_sse1(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+
+void qcms_transform_data_rgb_out_lut_altivec(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgba_out_lut_altivec(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_bgra_out_lut_altivec(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+
+void qcms_transform_data_rgb_out_lut_neon(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_rgba_out_lut_neon(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+void qcms_transform_data_bgra_out_lut_neon(const qcms_transform *transform,
+ const unsigned char *src,
+ unsigned char *dest,
+ size_t length);
+
+extern bool qcms_supports_iccv4;
+extern bool qcms_supports_neon;
+extern bool qcms_supports_avx;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/gfx/qcms/qcmstypes.h b/gfx/qcms/qcmstypes.h
new file mode 100644
index 0000000000..7f6c70f12c
--- /dev/null
+++ b/gfx/qcms/qcmstypes.h
@@ -0,0 +1,59 @@
+#ifndef QCMS_TYPES_H
+#define QCMS_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if !defined(IS_LITTLE_ENDIAN) && !defined(IS_BIG_ENDIAN)
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define IS_LITTLE_ENDIAN
+#elif BYTE_ORDER == BIG_ENDIAN
+#define IS_BIG_ENDIAN
+#endif
+
+/* all of the platforms that we use _MSC_VER on are little endian
+ * so this is sufficient for now */
+#ifdef _MSC_VER
+#define IS_LITTLE_ENDIAN
+#endif
+
+#ifdef __OS2__
+#define IS_LITTLE_ENDIAN
+#endif
+#endif
+
+#if !defined(IS_LITTLE_ENDIAN) && !defined(IS_BIG_ENDIAN)
+#error Unknown endianess
+#endif
+
+#if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || defined (_sgi) || defined (__sun) || defined (sun) || defined (__digital__)
+# include <inttypes.h>
+#elif defined (_MSC_VER) && _MSC_VER < 1600
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#ifdef _WIN64
+typedef unsigned __int64 uintptr_t;
+#else
+typedef unsigned long uintptr_t;
+#endif
+
+#elif defined (_AIX)
+# include <sys/inttypes.h>
+#else
+# include <stdint.h>
+#endif
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/gfx/qcms/sRGB_lcms.icc b/gfx/qcms/sRGB_lcms.icc
new file mode 100644
index 0000000000..47f42206ca
--- /dev/null
+++ b/gfx/qcms/sRGB_lcms.icc
Binary files differ
diff --git a/gfx/qcms/src/c_bindings.rs b/gfx/qcms/src/c_bindings.rs
new file mode 100644
index 0000000000..50db415fc2
--- /dev/null
+++ b/gfx/qcms/src/c_bindings.rs
@@ -0,0 +1,514 @@
+#![allow(clippy::missing_safety_doc)]
+
+use std::{ptr::null_mut, slice};
+
+use libc::{fclose, fopen, fread, free, malloc, memset, FILE};
+
+use crate::{
+ double_to_s15Fixed16Number,
+ iccread::*,
+ s15Fixed16Number_to_float,
+ transform::get_rgb_colorants,
+ transform::DataType,
+ transform::{qcms_transform, transform_create},
+ transform_util,
+ Intent,
+};
+
+#[no_mangle]
+pub extern "C" fn qcms_profile_sRGB() -> *mut Profile {
+ let profile = Profile::new_sRGB();
+ Box::into_raw(profile)
+}
+
+#[no_mangle]
+pub extern "C" fn qcms_profile_displayP3() -> *mut Profile {
+ let profile = Profile::new_displayP3();
+ Box::into_raw(profile)
+}
+
+
+//XXX: it would be nice if we had a way of ensuring
+// everything in a profile was initialized regardless of how it was created
+//XXX: should this also be taking a black_point?
+/* similar to CGColorSpaceCreateCalibratedRGB */
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_create_rgb_with_gamma_set(
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+ redGamma: f32,
+ greenGamma: f32,
+ blueGamma: f32,
+) -> *mut Profile {
+ let profile =
+ Profile::new_rgb_with_gamma_set(white_point, primaries, redGamma, greenGamma, blueGamma);
+ profile.map_or_else(null_mut, Box::into_raw)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_create_gray_with_gamma(gamma: f32) -> *mut Profile {
+ let profile = Profile::new_gray_with_gamma(gamma);
+ Box::into_raw(profile)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_create_rgb_with_gamma(
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+ gamma: f32,
+) -> *mut Profile {
+ qcms_profile_create_rgb_with_gamma_set(white_point, primaries, gamma, gamma, gamma)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_create_rgb_with_table(
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+ table: *const u16,
+ num_entries: i32,
+) -> *mut Profile {
+ let table = slice::from_raw_parts(table, num_entries as usize);
+ let profile = Profile::new_rgb_with_table(white_point, primaries, table);
+ profile.map_or_else(null_mut, Box::into_raw)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_create_cicp(
+ colour_primaries: u8,
+ transfer_characteristics: u8,
+) -> *mut Profile {
+ Profile::new_cicp(colour_primaries.into(), transfer_characteristics.into())
+ .map_or_else(null_mut, Box::into_raw)
+}
+
+/* qcms_profile_from_memory does not hold a reference to the memory passed in */
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_from_memory(
+ mem: *const libc::c_void,
+ size: usize,
+) -> *mut Profile {
+ let mem = slice::from_raw_parts(mem as *const libc::c_uchar, size);
+ let profile = Profile::new_from_slice(mem, false);
+ profile.map_or_else(null_mut, Box::into_raw)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_from_memory_curves_only(
+ mem: *const libc::c_void,
+ size: usize,
+) -> *mut Profile {
+ let mem = slice::from_raw_parts(mem as *const libc::c_uchar, size);
+ let profile = Profile::new_from_slice(mem, true);
+ profile.map_or_else(null_mut, Box::into_raw)
+}
+
+
+#[no_mangle]
+pub extern "C" fn qcms_profile_get_rendering_intent(profile: &Profile) -> Intent {
+ profile.rendering_intent
+}
+#[no_mangle]
+pub extern "C" fn qcms_profile_get_color_space(profile: &Profile) -> icColorSpaceSignature {
+ profile.color_space
+}
+#[no_mangle]
+pub extern "C" fn qcms_profile_is_sRGB(profile: &Profile) -> bool {
+ profile.is_sRGB()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_release(profile: *mut Profile) {
+ drop(Box::from_raw(profile));
+}
+unsafe extern "C" fn qcms_data_from_file(
+ file: *mut FILE,
+ mem: *mut *mut libc::c_void,
+ size: *mut usize,
+) {
+ let length: u32;
+ let remaining_length: u32;
+ let read_length: usize;
+ let mut length_be: u32 = 0;
+ let data: *mut libc::c_void;
+ *mem = std::ptr::null_mut::<libc::c_void>();
+ *size = 0;
+ if fread(
+ &mut length_be as *mut u32 as *mut libc::c_void,
+ 1,
+ ::std::mem::size_of::<u32>(),
+ file,
+ ) != ::std::mem::size_of::<u32>()
+ {
+ return;
+ }
+ length = u32::from_be(length_be);
+ if length > MAX_PROFILE_SIZE as libc::c_uint
+ || (length as libc::c_ulong) < ::std::mem::size_of::<u32>() as libc::c_ulong
+ {
+ return;
+ }
+ /* allocate room for the entire profile */
+ data = malloc(length as usize);
+ if data.is_null() {
+ return;
+ }
+ /* copy in length to the front so that the buffer will contain the entire profile */
+ *(data as *mut u32) = length_be;
+ remaining_length =
+ (length as libc::c_ulong - ::std::mem::size_of::<u32>() as libc::c_ulong) as u32;
+ /* read the rest profile */
+ read_length = fread(
+ (data as *mut libc::c_uchar).add(::std::mem::size_of::<u32>()) as *mut libc::c_void,
+ 1,
+ remaining_length as usize,
+ file,
+ ) as usize;
+ if read_length != remaining_length as usize {
+ free(data);
+ return;
+ }
+ /* successfully get the profile.*/
+ *mem = data;
+ *size = length as usize;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_from_file(file: *mut FILE) -> *mut Profile {
+ let mut length: usize = 0;
+ let profile: *mut Profile;
+ let mut data: *mut libc::c_void = std::ptr::null_mut::<libc::c_void>();
+ qcms_data_from_file(file, &mut data, &mut length);
+ if data.is_null() || length == 0 {
+ return std::ptr::null_mut::<Profile>();
+ }
+ profile = qcms_profile_from_memory(data, length);
+ free(data);
+ profile
+}
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_from_path(path: *const libc::c_char) -> *mut Profile {
+ if let Ok(Some(boxed_profile)) = std::ffi::CStr::from_ptr(path)
+ .to_str()
+ .map(Profile::new_from_path)
+ {
+ Box::into_raw(boxed_profile)
+ } else {
+ std::ptr::null_mut()
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_data_from_path(
+ path: *const libc::c_char,
+ mem: *mut *mut libc::c_void,
+ size: *mut usize,
+) {
+ *mem = std::ptr::null_mut::<libc::c_void>();
+ *size = 0;
+ let file = fopen(path, b"rb\x00" as *const u8 as *const libc::c_char);
+ if !file.is_null() {
+ qcms_data_from_file(file, mem, size);
+ fclose(file);
+ };
+}
+
+#[cfg(windows)]
+extern "C" {
+ pub fn _wfopen(filename: *const libc::wchar_t, mode: *const libc::wchar_t) -> *mut FILE;
+}
+
+#[cfg(windows)]
+#[no_mangle]
+pub unsafe extern "C" fn qcms_profile_from_unicode_path(path: *const libc::wchar_t) {
+ let file = _wfopen(path, ['r' as u16, 'b' as u16, '\0' as u16].as_ptr());
+ if !file.is_null() {
+ qcms_profile_from_file(file);
+ fclose(file);
+ };
+}
+
+#[cfg(windows)]
+#[no_mangle]
+pub unsafe extern "C" fn qcms_data_from_unicode_path(
+ path: *const libc::wchar_t,
+ mem: *mut *mut libc::c_void,
+ size: *mut usize,
+) {
+ *mem = 0 as *mut libc::c_void;
+ *size = 0;
+ let file = _wfopen(path, ['r' as u16, 'b' as u16, '\0' as u16].as_ptr());
+ if !file.is_null() {
+ qcms_data_from_file(file, mem, size);
+ fclose(file);
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn qcms_transform_create(
+ in_0: &Profile,
+ in_type: DataType,
+ out: &Profile,
+ out_type: DataType,
+ intent: Intent,
+) -> *mut qcms_transform {
+ let transform = transform_create(in_0, in_type, out, out_type, intent);
+ match transform {
+ Some(transform) => Box::into_raw(transform),
+ None => null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_data_create_rgb_with_gamma(
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+ gamma: f32,
+ mem: *mut *mut libc::c_void,
+ size: *mut usize,
+) {
+ let length: u32;
+ let mut index: u32;
+ let xyz_count: u32;
+ let trc_count: u32;
+ let mut tag_table_offset: usize;
+ let mut tag_data_offset: usize;
+ let data: *mut libc::c_void;
+
+ let TAG_XYZ: [u32; 3] = [TAG_rXYZ, TAG_gXYZ, TAG_bXYZ];
+ let TAG_TRC: [u32; 3] = [TAG_rTRC, TAG_gTRC, TAG_bTRC];
+ if mem.is_null() || size.is_null() {
+ return;
+ }
+ *mem = std::ptr::null_mut::<libc::c_void>();
+ *size = 0;
+ /*
+ * total length = icc profile header(128) + tag count(4) +
+ * (tag table item (12) * total tag (6 = 3 rTRC + 3 rXYZ)) + rTRC elements data (3 * 20)
+ * + rXYZ elements data (3*16), and all tag data elements must start at the 4-byte boundary.
+ */
+ xyz_count = 3; // rXYZ, gXYZ, bXYZ
+ trc_count = 3; // rTRC, gTRC, bTRC
+ length =
+ (128 + 4) as libc::c_uint + 12 * (xyz_count + trc_count) + xyz_count * 20 + trc_count * 16;
+ // reserve the total memory.
+ data = malloc(length as usize);
+ if data.is_null() {
+ return;
+ }
+ memset(data, 0, length as usize);
+ // Part1 : write rXYZ, gXYZ and bXYZ
+ let colorants = match get_rgb_colorants(white_point, primaries) {
+ Some(colorants) => colorants,
+ None => {
+ free(data);
+ return;
+ }
+ };
+
+ let data = std::slice::from_raw_parts_mut(data as *mut u8, length as usize);
+ // the position of first tag's signature in tag table
+ tag_table_offset = (128 + 4) as usize; // the start of tag data elements.
+ tag_data_offset = ((128 + 4) as libc::c_uint + 12 * (xyz_count + trc_count)) as usize;
+ index = 0;
+ while index < xyz_count {
+ // tag table
+ write_u32(data, tag_table_offset, TAG_XYZ[index as usize]); // 20 bytes per TAG_(r/g/b)XYZ tag element
+ write_u32(data, tag_table_offset + 4, tag_data_offset as u32);
+ write_u32(data, tag_table_offset + 8, 20);
+ // tag data element
+ write_u32(data, tag_data_offset, XYZ_TYPE);
+ // reserved 4 bytes.
+ write_u32(
+ data,
+ tag_data_offset + 8,
+ double_to_s15Fixed16Number(colorants.m[0][index as usize] as f64) as u32,
+ );
+ write_u32(
+ data,
+ tag_data_offset + 12,
+ double_to_s15Fixed16Number(colorants.m[1][index as usize] as f64) as u32,
+ );
+ write_u32(
+ data,
+ tag_data_offset + 16,
+ double_to_s15Fixed16Number(colorants.m[2][index as usize] as f64) as u32,
+ );
+ tag_table_offset += 12;
+ tag_data_offset += 20;
+ index += 1
+ }
+ // Part2 : write rTRC, gTRC and bTRC
+ index = 0;
+ while index < trc_count {
+ // tag table
+ write_u32(data, tag_table_offset, TAG_TRC[index as usize]); // 14 bytes per TAG_(r/g/b)TRC element
+ write_u32(data, tag_table_offset + 4, tag_data_offset as u32);
+ write_u32(data, tag_table_offset + 8, 14);
+ // tag data element
+ write_u32(data, tag_data_offset, CURVE_TYPE);
+ // reserved 4 bytes.
+ write_u32(data, tag_data_offset + 8, 1); // count
+ write_u16(data, tag_data_offset + 12, float_to_u8Fixed8Number(gamma));
+ tag_table_offset += 12;
+ tag_data_offset += 16;
+ index += 1
+ }
+ /* Part3 : write profile header
+ *
+ * Important header fields are left empty. This generates a profile for internal use only.
+ * We should be generating: Profile version (04300000h), Profile signature (acsp),
+ * PCS illumiant field. Likewise mandatory profile tags are omitted.
+ */
+ write_u32(data, 0, length); // the total length of this memory
+ write_u32(data, 12, DISPLAY_DEVICE_PROFILE); // profile->class_type
+ write_u32(data, 16, RGB_SIGNATURE); // profile->color_space
+ write_u32(data, 20, XYZ_TYPE); // profile->pcs
+ write_u32(data, 64, Intent::Perceptual as u32); // profile->rendering_intent
+ write_u32(data, 128, 6); // total tag count
+ // prepare the result
+ *mem = data.as_mut_ptr() as *mut libc::c_void;
+ *size = length as usize;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_transform_data(
+ transform: &qcms_transform,
+ src: *const libc::c_void,
+ dest: *mut libc::c_void,
+ length: usize,
+) {
+ transform.transform_fn.expect("non-null function pointer")(
+ transform,
+ src as *const u8,
+ dest as *mut u8,
+ length,
+ );
+}
+/*
+use crate::matrix;
+#[repr(C)]
+#[derive(Clone, Debug, Default)]
+pub struct qcms_mat3r3 {
+ pub rows: [[f32; 3] ; 3],
+}
+impl qcms_mat3r3 {
+ fn from(m: matrix::Matrix) -> qcms_mat3r3 {
+ qcms_mat3r3{
+ rows: [
+ m.row(0),
+ m.row(1),
+ m.row(2),
+ ],
+ }
+ }
+}
+*/
+#[repr(C)]
+#[derive(Clone, Debug, Default)]
+#[allow(clippy::upper_case_acronyms)]
+pub struct qcms_profile_data {
+ pub class_type: u32,
+ pub color_space: u32,
+ pub pcs: u32,
+ pub rendering_intent: Intent,
+ pub red_colorant_xyzd50: [f32; 3],
+ pub blue_colorant_xyzd50: [f32; 3],
+ pub green_colorant_xyzd50: [f32; 3],
+ // Number of samples in the e.g. gamma->linear LUT.
+ pub linear_from_trc_red_samples: i32,
+ pub linear_from_trc_blue_samples: i32,
+ pub linear_from_trc_green_samples: i32,
+}
+
+pub use crate::iccread::Profile as qcms_profile;
+
+#[no_mangle]
+pub extern "C" fn qcms_profile_get_data(
+ profile: &qcms_profile,
+ out_data: &mut qcms_profile_data,
+) {
+ out_data.class_type = profile.class_type;
+ out_data.color_space = profile.color_space;
+ out_data.pcs = profile.pcs;
+ out_data.rendering_intent = profile.rendering_intent;
+
+ fn colorant(c: &XYZNumber) -> [f32;3] {
+ [c.X, c.Y, c.Z].map(s15Fixed16Number_to_float)
+ }
+ out_data.red_colorant_xyzd50 = colorant(&profile.redColorant);
+ out_data.blue_colorant_xyzd50 = colorant(&profile.blueColorant);
+ out_data.green_colorant_xyzd50 = colorant(&profile.greenColorant);
+
+ fn trc_to_samples(trc: &Option<Box<curveType>>) -> i32 {
+ if let Some(ref trc) = *trc {
+ match &**trc {
+ curveType::Curve(v) => {
+ let len = v.len();
+ if len <= 1 {
+ -1
+ } else {
+ len as i32
+ }
+ },
+ curveType::Parametric(_) => -1,
+ }
+ } else {
+ 0
+ }
+ }
+ out_data.linear_from_trc_red_samples = trc_to_samples(&profile.redTRC);
+ out_data.linear_from_trc_blue_samples = trc_to_samples(&profile.blueTRC);
+ out_data.linear_from_trc_green_samples = trc_to_samples(&profile.greenTRC);
+}
+
+#[repr(u8)]
+pub enum qcms_color_channel {
+ Red,
+ Green,
+ Blue,
+}
+
+#[no_mangle]
+pub extern "C" fn qcms_profile_get_lut(
+ profile: &qcms_profile,
+ channel: qcms_color_channel, // FYI: UB if you give Rust something out of range!
+ out_begin: *mut f32,
+ out_end: *mut f32,
+) {
+ let out_slice = unsafe {
+ std::slice::from_raw_parts_mut(out_begin, out_end.offset_from(out_begin) as usize)
+ };
+
+ let trc = match channel {
+ qcms_color_channel::Red => &profile.redTRC,
+ qcms_color_channel::Green => &profile.greenTRC,
+ qcms_color_channel::Blue => &profile.blueTRC,
+ };
+
+ let samples_u16 = if let Some(trc) = trc {
+ let trc = &*trc;
+ // Yes, sub-optimal, but easier to implement, and these aren't big or hot:
+ // 1. Ask for a new vec<u16> lut based on the trc.
+ // * (eat the extra alloc)
+ // 2. Convert the u16s back out to f32s in our slice.
+ // * (eat the copy and quantization error from f32->u16->f32 roundtrip)
+ transform_util::build_lut_for_linear_from_tf(trc, Some(out_slice.len()))
+ } else {
+ Vec::new()
+ };
+
+ assert_eq!(samples_u16.len(), out_slice.len());
+ for (d, s) in out_slice.iter_mut().zip(samples_u16.into_iter()) {
+ *d = (s as f32) / (u16::MAX as f32);
+ }
+}
+
+pub type icColorSpaceSignature = u32;
+pub const icSigGrayData: icColorSpaceSignature = 0x47524159; // 'GRAY'
+pub const icSigRgbData: icColorSpaceSignature = 0x52474220; // 'RGB '
+pub const icSigCmykData: icColorSpaceSignature = 0x434d594b; // 'CMYK'
+
+pub use crate::iccread::qcms_profile_is_bogus;
+pub use crate::transform::{
+ qcms_enable_iccv4, qcms_profile_precache_output_transform, qcms_transform_release,
+};
diff --git a/gfx/qcms/src/chain.rs b/gfx/qcms/src/chain.rs
new file mode 100644
index 0000000000..35a3896138
--- /dev/null
+++ b/gfx/qcms/src/chain.rs
@@ -0,0 +1,1029 @@
+// qcms
+// Copyright (C) 2009 Mozilla Corporation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+use crate::{
+ iccread::LAB_SIGNATURE,
+ iccread::RGB_SIGNATURE,
+ iccread::XYZ_SIGNATURE,
+ iccread::{lutType, lutmABType, Profile, CMYK_SIGNATURE},
+ matrix::Matrix,
+ s15Fixed16Number_to_float,
+ transform_util::clamp_float,
+ transform_util::{
+ build_colorant_matrix, build_input_gamma_table, build_output_lut, lut_interp_linear,
+ lut_interp_linear_float,
+ },
+};
+
+trait ModularTransform {
+ fn transform(&self, src: &[f32], dst: &mut [f32]);
+}
+
+#[inline]
+fn lerp(a: f32, b: f32, t: f32) -> f32 {
+ a * (1.0 - t) + b * t
+}
+
+fn build_lut_matrix(lut: &lutType) -> Matrix {
+ let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
+ result.m[0][0] = s15Fixed16Number_to_float(lut.e00);
+ result.m[0][1] = s15Fixed16Number_to_float(lut.e01);
+ result.m[0][2] = s15Fixed16Number_to_float(lut.e02);
+ result.m[1][0] = s15Fixed16Number_to_float(lut.e10);
+ result.m[1][1] = s15Fixed16Number_to_float(lut.e11);
+ result.m[1][2] = s15Fixed16Number_to_float(lut.e12);
+ result.m[2][0] = s15Fixed16Number_to_float(lut.e20);
+ result.m[2][1] = s15Fixed16Number_to_float(lut.e21);
+ result.m[2][2] = s15Fixed16Number_to_float(lut.e22);
+ result
+}
+fn build_mAB_matrix(lut: &lutmABType) -> Matrix {
+ let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
+
+ result.m[0][0] = s15Fixed16Number_to_float(lut.e00);
+ result.m[0][1] = s15Fixed16Number_to_float(lut.e01);
+ result.m[0][2] = s15Fixed16Number_to_float(lut.e02);
+ result.m[1][0] = s15Fixed16Number_to_float(lut.e10);
+ result.m[1][1] = s15Fixed16Number_to_float(lut.e11);
+ result.m[1][2] = s15Fixed16Number_to_float(lut.e12);
+ result.m[2][0] = s15Fixed16Number_to_float(lut.e20);
+ result.m[2][1] = s15Fixed16Number_to_float(lut.e21);
+ result.m[2][2] = s15Fixed16Number_to_float(lut.e22);
+
+ result
+}
+//Based on lcms cmsLab2XYZ
+fn f(t: f32) -> f32 {
+ if t <= 24. / 116. * (24. / 116.) * (24. / 116.) {
+ (841. / 108. * t) + 16. / 116.
+ } else {
+ t.powf(1. / 3.)
+ }
+}
+fn f_1(t: f32) -> f32 {
+ if t <= 24.0 / 116.0 {
+ (108.0 / 841.0) * (t - 16.0 / 116.0)
+ } else {
+ t * t * t
+ }
+}
+
+#[allow(clippy::upper_case_acronyms)]
+struct LABtoXYZ;
+impl ModularTransform for LABtoXYZ {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ // lcms: D50 XYZ values
+ let WhitePointX: f32 = 0.9642;
+ let WhitePointY: f32 = 1.0;
+ let WhitePointZ: f32 = 0.8249;
+
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ let device_L: f32 = src[0] * 100.0;
+ let device_a: f32 = src[1] * 255.0 - 128.0;
+ let device_b: f32 = src[2] * 255.0 - 128.0;
+
+ let y: f32 = (device_L + 16.0) / 116.0;
+
+ let X = f_1(y + 0.002 * device_a) * WhitePointX;
+ let Y = f_1(y) * WhitePointY;
+ let Z = f_1(y - 0.005 * device_b) * WhitePointZ;
+
+ dest[0] = (X as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
+ dest[1] = (Y as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
+ dest[2] = (Z as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
+ }
+ }
+}
+
+#[allow(clippy::upper_case_acronyms)]
+struct XYZtoLAB;
+impl ModularTransform for XYZtoLAB {
+ //Based on lcms cmsXYZ2Lab
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ // lcms: D50 XYZ values
+ let WhitePointX: f32 = 0.9642;
+ let WhitePointY: f32 = 1.0;
+ let WhitePointZ: f32 = 0.8249;
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ let device_x: f32 =
+ (src[0] as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WhitePointX as f64) as f32;
+ let device_y: f32 =
+ (src[1] as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WhitePointY as f64) as f32;
+ let device_z: f32 =
+ (src[2] as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WhitePointZ as f64) as f32;
+
+ let fx = f(device_x);
+ let fy = f(device_y);
+ let fz = f(device_z);
+
+ let L: f32 = 116.0 * fy - 16.0;
+ let a: f32 = 500.0 * (fx - fy);
+ let b: f32 = 200.0 * (fy - fz);
+
+ dest[0] = L / 100.0;
+ dest[1] = (a + 128.0) / 255.0;
+ dest[2] = (b + 128.0) / 255.0;
+ }
+ }
+}
+#[derive(Default)]
+struct ClutOnly {
+ clut: Option<Vec<f32>>,
+ grid_size: u16,
+}
+impl ModularTransform for ClutOnly {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ let xy_len: i32 = 1;
+ let x_len: i32 = self.grid_size as i32;
+ let len: i32 = x_len * x_len;
+
+ let r_table = &self.clut.as_ref().unwrap()[0..];
+ let g_table = &self.clut.as_ref().unwrap()[1..];
+ let b_table = &self.clut.as_ref().unwrap()[2..];
+
+ let CLU = |table: &[f32], x, y, z| table[((x * len + y * x_len + z * xy_len) * 3) as usize];
+
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ debug_assert!(self.grid_size as i32 >= 1);
+ let linear_r: f32 = src[0];
+ let linear_g: f32 = src[1];
+ let linear_b: f32 = src[2];
+ let x: i32 = (linear_r * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let y: i32 = (linear_g * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let z: i32 = (linear_b * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let x_n: i32 = (linear_r * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let y_n: i32 = (linear_g * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let z_n: i32 = (linear_b * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let x_d: f32 = linear_r * (self.grid_size as i32 - 1) as f32 - x as f32;
+ let y_d: f32 = linear_g * (self.grid_size as i32 - 1) as f32 - y as f32;
+ let z_d: f32 = linear_b * (self.grid_size as i32 - 1) as f32 - z as f32;
+
+ let r_x1: f32 = lerp(CLU(r_table, x, y, z), CLU(r_table, x_n, y, z), x_d);
+ let r_x2: f32 = lerp(CLU(r_table, x, y_n, z), CLU(r_table, x_n, y_n, z), x_d);
+ let r_y1: f32 = lerp(r_x1, r_x2, y_d);
+ let r_x3: f32 = lerp(CLU(r_table, x, y, z_n), CLU(r_table, x_n, y, z_n), x_d);
+ let r_x4: f32 = lerp(CLU(r_table, x, y_n, z_n), CLU(r_table, x_n, y_n, z_n), x_d);
+ let r_y2: f32 = lerp(r_x3, r_x4, y_d);
+ let clut_r: f32 = lerp(r_y1, r_y2, z_d);
+
+ let g_x1: f32 = lerp(CLU(g_table, x, y, z), CLU(g_table, x_n, y, z), x_d);
+ let g_x2: f32 = lerp(CLU(g_table, x, y_n, z), CLU(g_table, x_n, y_n, z), x_d);
+ let g_y1: f32 = lerp(g_x1, g_x2, y_d);
+ let g_x3: f32 = lerp(CLU(g_table, x, y, z_n), CLU(g_table, x_n, y, z_n), x_d);
+ let g_x4: f32 = lerp(CLU(g_table, x, y_n, z_n), CLU(g_table, x_n, y_n, z_n), x_d);
+ let g_y2: f32 = lerp(g_x3, g_x4, y_d);
+ let clut_g: f32 = lerp(g_y1, g_y2, z_d);
+
+ let b_x1: f32 = lerp(CLU(b_table, x, y, z), CLU(b_table, x_n, y, z), x_d);
+ let b_x2: f32 = lerp(CLU(b_table, x, y_n, z), CLU(b_table, x_n, y_n, z), x_d);
+ let b_y1: f32 = lerp(b_x1, b_x2, y_d);
+ let b_x3: f32 = lerp(CLU(b_table, x, y, z_n), CLU(b_table, x_n, y, z_n), x_d);
+ let b_x4: f32 = lerp(CLU(b_table, x, y_n, z_n), CLU(b_table, x_n, y_n, z_n), x_d);
+ let b_y2: f32 = lerp(b_x3, b_x4, y_d);
+ let clut_b: f32 = lerp(b_y1, b_y2, z_d);
+
+ dest[0] = clamp_float(clut_r);
+ dest[1] = clamp_float(clut_g);
+ dest[2] = clamp_float(clut_b);
+ }
+ }
+}
+#[derive(Default)]
+struct Clut3x3 {
+ input_clut_table: [Option<Vec<f32>>; 3],
+ clut: Option<Vec<f32>>,
+ grid_size: u16,
+ output_clut_table: [Option<Vec<f32>>; 3],
+}
+impl ModularTransform for Clut3x3 {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ let xy_len: i32 = 1;
+ let x_len: i32 = self.grid_size as i32;
+ let len: i32 = x_len * x_len;
+
+ let r_table = &self.clut.as_ref().unwrap()[0..];
+ let g_table = &self.clut.as_ref().unwrap()[1..];
+ let b_table = &self.clut.as_ref().unwrap()[2..];
+ let CLU = |table: &[f32], x, y, z| table[((x * len + y * x_len + z * xy_len) * 3) as usize];
+
+ let input_clut_table_r = self.input_clut_table[0].as_ref().unwrap();
+ let input_clut_table_g = self.input_clut_table[1].as_ref().unwrap();
+ let input_clut_table_b = self.input_clut_table[2].as_ref().unwrap();
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ debug_assert!(self.grid_size as i32 >= 1);
+ let device_r: f32 = src[0];
+ let device_g: f32 = src[1];
+ let device_b: f32 = src[2];
+ let linear_r: f32 = lut_interp_linear_float(device_r, &input_clut_table_r);
+ let linear_g: f32 = lut_interp_linear_float(device_g, &input_clut_table_g);
+ let linear_b: f32 = lut_interp_linear_float(device_b, &input_clut_table_b);
+ let x: i32 = (linear_r * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let y: i32 = (linear_g * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let z: i32 = (linear_b * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let x_n: i32 = (linear_r * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let y_n: i32 = (linear_g * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let z_n: i32 = (linear_b * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let x_d: f32 = linear_r * (self.grid_size as i32 - 1) as f32 - x as f32;
+ let y_d: f32 = linear_g * (self.grid_size as i32 - 1) as f32 - y as f32;
+ let z_d: f32 = linear_b * (self.grid_size as i32 - 1) as f32 - z as f32;
+
+ let r_x1: f32 = lerp(CLU(r_table, x, y, z), CLU(r_table, x_n, y, z), x_d);
+ let r_x2: f32 = lerp(CLU(r_table, x, y_n, z), CLU(r_table, x_n, y_n, z), x_d);
+ let r_y1: f32 = lerp(r_x1, r_x2, y_d);
+ let r_x3: f32 = lerp(CLU(r_table, x, y, z_n), CLU(r_table, x_n, y, z_n), x_d);
+ let r_x4: f32 = lerp(CLU(r_table, x, y_n, z_n), CLU(r_table, x_n, y_n, z_n), x_d);
+ let r_y2: f32 = lerp(r_x3, r_x4, y_d);
+ let clut_r: f32 = lerp(r_y1, r_y2, z_d);
+
+ let g_x1: f32 = lerp(CLU(g_table, x, y, z), CLU(g_table, x_n, y, z), x_d);
+ let g_x2: f32 = lerp(CLU(g_table, x, y_n, z), CLU(g_table, x_n, y_n, z), x_d);
+ let g_y1: f32 = lerp(g_x1, g_x2, y_d);
+ let g_x3: f32 = lerp(CLU(g_table, x, y, z_n), CLU(g_table, x_n, y, z_n), x_d);
+ let g_x4: f32 = lerp(CLU(g_table, x, y_n, z_n), CLU(g_table, x_n, y_n, z_n), x_d);
+ let g_y2: f32 = lerp(g_x3, g_x4, y_d);
+ let clut_g: f32 = lerp(g_y1, g_y2, z_d);
+
+ let b_x1: f32 = lerp(CLU(b_table, x, y, z), CLU(b_table, x_n, y, z), x_d);
+ let b_x2: f32 = lerp(CLU(b_table, x, y_n, z), CLU(b_table, x_n, y_n, z), x_d);
+ let b_y1: f32 = lerp(b_x1, b_x2, y_d);
+ let b_x3: f32 = lerp(CLU(b_table, x, y, z_n), CLU(b_table, x_n, y, z_n), x_d);
+ let b_x4: f32 = lerp(CLU(b_table, x, y_n, z_n), CLU(b_table, x_n, y_n, z_n), x_d);
+ let b_y2: f32 = lerp(b_x3, b_x4, y_d);
+ let clut_b: f32 = lerp(b_y1, b_y2, z_d);
+ let pcs_r: f32 =
+ lut_interp_linear_float(clut_r, &self.output_clut_table[0].as_ref().unwrap());
+ let pcs_g: f32 =
+ lut_interp_linear_float(clut_g, &self.output_clut_table[1].as_ref().unwrap());
+ let pcs_b: f32 =
+ lut_interp_linear_float(clut_b, &self.output_clut_table[2].as_ref().unwrap());
+ dest[0] = clamp_float(pcs_r);
+ dest[1] = clamp_float(pcs_g);
+ dest[2] = clamp_float(pcs_b);
+ }
+ }
+}
+#[derive(Default)]
+struct Clut4x3 {
+ input_clut_table: [Option<Vec<f32>>; 4],
+ clut: Option<Vec<f32>>,
+ grid_size: u16,
+ output_clut_table: [Option<Vec<f32>>; 3],
+}
+impl ModularTransform for Clut4x3 {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ let z_stride: i32 = self.grid_size as i32;
+ let y_stride: i32 = z_stride * z_stride;
+ let x_stride: i32 = z_stride * z_stride * z_stride;
+
+ let r_tbl = &self.clut.as_ref().unwrap()[0..];
+ let g_tbl = &self.clut.as_ref().unwrap()[1..];
+ let b_tbl = &self.clut.as_ref().unwrap()[2..];
+
+ let CLU = |table: &[f32], x, y, z, w| {
+ table[((x * x_stride + y * y_stride + z * z_stride + w) * 3) as usize]
+ };
+
+ let input_clut_table_0 = self.input_clut_table[0].as_ref().unwrap();
+ let input_clut_table_1 = self.input_clut_table[1].as_ref().unwrap();
+ let input_clut_table_2 = self.input_clut_table[2].as_ref().unwrap();
+ let input_clut_table_3 = self.input_clut_table[3].as_ref().unwrap();
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(4)) {
+ debug_assert!(self.grid_size as i32 >= 1);
+ let linear_x: f32 = lut_interp_linear_float(src[0], &input_clut_table_0);
+ let linear_y: f32 = lut_interp_linear_float(src[1], &input_clut_table_1);
+ let linear_z: f32 = lut_interp_linear_float(src[2], &input_clut_table_2);
+ let linear_w: f32 = lut_interp_linear_float(src[3], &input_clut_table_3);
+
+ let x: i32 = (linear_x * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let y: i32 = (linear_y * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let z: i32 = (linear_z * (self.grid_size as i32 - 1) as f32).floor() as i32;
+ let w: i32 = (linear_w * (self.grid_size as i32 - 1) as f32).floor() as i32;
+
+ let x_n: i32 = (linear_x * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let y_n: i32 = (linear_y * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let z_n: i32 = (linear_z * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+ let w_n: i32 = (linear_w * (self.grid_size as i32 - 1) as f32).ceil() as i32;
+
+ let x_d: f32 = linear_x * (self.grid_size as i32 - 1) as f32 - x as f32;
+ let y_d: f32 = linear_y * (self.grid_size as i32 - 1) as f32 - y as f32;
+ let z_d: f32 = linear_z * (self.grid_size as i32 - 1) as f32 - z as f32;
+ let w_d: f32 = linear_w * (self.grid_size as i32 - 1) as f32 - w as f32;
+
+ let quadlinear = |tbl| {
+ let CLU = |x, y, z, w| CLU(tbl, x, y, z, w);
+ let r_x1 = lerp(CLU(x, y, z, w), CLU(x_n, y, z, w), x_d);
+ let r_x2 = lerp(CLU(x, y_n, z, w), CLU(x_n, y_n, z, w), x_d);
+ let r_y1 = lerp(r_x1, r_x2, y_d);
+ let r_x3 = lerp(CLU(x, y, z_n, w), CLU(x_n, y, z_n, w), x_d);
+ let r_x4 = lerp(CLU(x, y_n, z_n, w), CLU(x_n, y_n, z_n, w), x_d);
+ let r_y2 = lerp(r_x3, r_x4, y_d);
+ let r_z1 = lerp(r_y1, r_y2, z_d);
+
+ let r_x1 = lerp(CLU(x, y, z, w_n), CLU(x_n, y, z, w_n), x_d);
+ let r_x2 = lerp(CLU(x, y_n, z, w_n), CLU(x_n, y_n, z, w_n), x_d);
+ let r_y1 = lerp(r_x1, r_x2, y_d);
+ let r_x3 = lerp(CLU(x, y, z_n, w_n), CLU(x_n, y, z_n, w_n), x_d);
+ let r_x4 = lerp(CLU(x, y_n, z_n, w_n), CLU(x_n, y_n, z_n, w_n), x_d);
+ let r_y2 = lerp(r_x3, r_x4, y_d);
+ let r_z2 = lerp(r_y1, r_y2, z_d);
+ lerp(r_z1, r_z2, w_d)
+ };
+ // TODO: instead of reading each component separately we should read all three components at once.
+ let clut_r = quadlinear(r_tbl);
+ let clut_g = quadlinear(g_tbl);
+ let clut_b = quadlinear(b_tbl);
+
+ let pcs_r =
+ lut_interp_linear_float(clut_r, &self.output_clut_table[0].as_ref().unwrap());
+ let pcs_g =
+ lut_interp_linear_float(clut_g, &self.output_clut_table[1].as_ref().unwrap());
+ let pcs_b =
+ lut_interp_linear_float(clut_b, &self.output_clut_table[2].as_ref().unwrap());
+ dest[0] = clamp_float(pcs_r);
+ dest[1] = clamp_float(pcs_g);
+ dest[2] = clamp_float(pcs_b);
+ }
+ }
+}
+/* NOT USED
+static void qcms_transform_module_tetra_clut(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ float* r_table = transform->r_clut;
+ float* g_table = transform->g_clut;
+ float* b_table = transform->b_clut;
+ float c0_r, c1_r, c2_r, c3_r;
+ float c0_g, c1_g, c2_g, c3_g;
+ float c0_b, c1_b, c2_b, c3_b;
+ float clut_r, clut_g, clut_b;
+ float pcs_r, pcs_g, pcs_b;
+ for (i = 0; i < length; i++) {
+ float device_r = *src++;
+ float device_g = *src++;
+ float device_b = *src++;
+ float linear_r = lut_interp_linear_float(device_r,
+ transform->input_clut_table_r, transform->input_clut_table_length);
+ float linear_g = lut_interp_linear_float(device_g,
+ transform->input_clut_table_g, transform->input_clut_table_length);
+ float linear_b = lut_interp_linear_float(device_b,
+ transform->input_clut_table_b, transform->input_clut_table_length);
+
+ int x = floorf(linear_r * (transform->grid_size-1));
+ int y = floorf(linear_g * (transform->grid_size-1));
+ int z = floorf(linear_b * (transform->grid_size-1));
+ int x_n = ceilf(linear_r * (transform->grid_size-1));
+ int y_n = ceilf(linear_g * (transform->grid_size-1));
+ int z_n = ceilf(linear_b * (transform->grid_size-1));
+ float rx = linear_r * (transform->grid_size-1) - x;
+ float ry = linear_g * (transform->grid_size-1) - y;
+ float rz = linear_b * (transform->grid_size-1) - z;
+
+ c0_r = CLU(r_table, x, y, z);
+ c0_g = CLU(g_table, x, y, z);
+ c0_b = CLU(b_table, x, y, z);
+ if( rx >= ry ) {
+ if (ry >= rz) { //rx >= ry && ry >= rz
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (rx >= rz) { //rx >= rz && rz >= ry
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
+ } else { //rz > rx && rx >= ry
+ c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ }
+ } else {
+ if (rx >= rz) { //ry > rx && rx >= rz
+ c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
+ c2_r = CLU(r_table, x_n, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
+ c2_g = CLU(g_table, x_n, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
+ c2_b = CLU(b_table, x_n, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (ry >= rz) { //ry >= rz && rz > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
+ } else { //rz > ry && ry > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ }
+ }
+ }
+
+ clut_r = c0_r + c1_r*rx + c2_r*ry + c3_r*rz;
+ clut_g = c0_g + c1_g*rx + c2_g*ry + c3_g*rz;
+ clut_b = c0_b + c1_b*rx + c2_b*ry + c3_b*rz;
+
+ pcs_r = lut_interp_linear_float(clut_r,
+ transform->output_clut_table_r, transform->output_clut_table_length);
+ pcs_g = lut_interp_linear_float(clut_g,
+ transform->output_clut_table_g, transform->output_clut_table_length);
+ pcs_b = lut_interp_linear_float(clut_b,
+ transform->output_clut_table_b, transform->output_clut_table_length);
+ *dest++ = clamp_float(pcs_r);
+ *dest++ = clamp_float(pcs_g);
+ *dest++ = clamp_float(pcs_b);
+ }
+}
+*/
+#[derive(Default)]
+struct GammaTable {
+ input_clut_table: [Option<Vec<f32>>; 3],
+}
+impl ModularTransform for GammaTable {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ let mut out_r: f32;
+ let mut out_g: f32;
+ let mut out_b: f32;
+ let input_clut_table_r = self.input_clut_table[0].as_ref().unwrap();
+ let input_clut_table_g = self.input_clut_table[1].as_ref().unwrap();
+ let input_clut_table_b = self.input_clut_table[2].as_ref().unwrap();
+
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ let in_r: f32 = src[0];
+ let in_g: f32 = src[1];
+ let in_b: f32 = src[2];
+ out_r = lut_interp_linear_float(in_r, input_clut_table_r);
+ out_g = lut_interp_linear_float(in_g, input_clut_table_g);
+ out_b = lut_interp_linear_float(in_b, input_clut_table_b);
+
+ dest[0] = clamp_float(out_r);
+ dest[1] = clamp_float(out_g);
+ dest[2] = clamp_float(out_b);
+ }
+ }
+}
+#[derive(Default)]
+struct GammaLut {
+ output_gamma_lut_r: Option<Vec<u16>>,
+ output_gamma_lut_g: Option<Vec<u16>>,
+ output_gamma_lut_b: Option<Vec<u16>>,
+}
+impl ModularTransform for GammaLut {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ let mut out_r: f32;
+ let mut out_g: f32;
+ let mut out_b: f32;
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ let in_r: f32 = src[0];
+ let in_g: f32 = src[1];
+ let in_b: f32 = src[2];
+ out_r = lut_interp_linear(in_r as f64, &self.output_gamma_lut_r.as_ref().unwrap());
+ out_g = lut_interp_linear(in_g as f64, &self.output_gamma_lut_g.as_ref().unwrap());
+ out_b = lut_interp_linear(in_b as f64, &self.output_gamma_lut_b.as_ref().unwrap());
+ dest[0] = clamp_float(out_r);
+ dest[1] = clamp_float(out_g);
+ dest[2] = clamp_float(out_b);
+ }
+ }
+}
+#[derive(Default)]
+struct MatrixTranslate {
+ matrix: Matrix,
+ tx: f32,
+ ty: f32,
+ tz: f32,
+}
+impl ModularTransform for MatrixTranslate {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ let mut mat: Matrix = Matrix { m: [[0.; 3]; 3] };
+ /* store the results in column major mode
+ * this makes doing the multiplication with sse easier */
+ mat.m[0][0] = self.matrix.m[0][0];
+ mat.m[1][0] = self.matrix.m[0][1];
+ mat.m[2][0] = self.matrix.m[0][2];
+ mat.m[0][1] = self.matrix.m[1][0];
+ mat.m[1][1] = self.matrix.m[1][1];
+ mat.m[2][1] = self.matrix.m[1][2];
+ mat.m[0][2] = self.matrix.m[2][0];
+ mat.m[1][2] = self.matrix.m[2][1];
+ mat.m[2][2] = self.matrix.m[2][2];
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ let in_r: f32 = src[0];
+ let in_g: f32 = src[1];
+ let in_b: f32 = src[2];
+ let out_r: f32 = mat.m[0][0] * in_r + mat.m[1][0] * in_g + mat.m[2][0] * in_b + self.tx;
+ let out_g: f32 = mat.m[0][1] * in_r + mat.m[1][1] * in_g + mat.m[2][1] * in_b + self.ty;
+ let out_b: f32 = mat.m[0][2] * in_r + mat.m[1][2] * in_g + mat.m[2][2] * in_b + self.tz;
+ dest[0] = clamp_float(out_r);
+ dest[1] = clamp_float(out_g);
+ dest[2] = clamp_float(out_b);
+ }
+ }
+}
+#[derive(Default)]
+struct MatrixTransform {
+ matrix: Matrix,
+}
+impl ModularTransform for MatrixTransform {
+ fn transform(&self, src: &[f32], dest: &mut [f32]) {
+ let mut mat: Matrix = Matrix { m: [[0.; 3]; 3] };
+ /* store the results in column major mode
+ * this makes doing the multiplication with sse easier */
+ mat.m[0][0] = self.matrix.m[0][0];
+ mat.m[1][0] = self.matrix.m[0][1];
+ mat.m[2][0] = self.matrix.m[0][2];
+ mat.m[0][1] = self.matrix.m[1][0];
+ mat.m[1][1] = self.matrix.m[1][1];
+ mat.m[2][1] = self.matrix.m[1][2];
+ mat.m[0][2] = self.matrix.m[2][0];
+ mat.m[1][2] = self.matrix.m[2][1];
+ mat.m[2][2] = self.matrix.m[2][2];
+ for (dest, src) in dest.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
+ let in_r: f32 = src[0];
+ let in_g: f32 = src[1];
+ let in_b: f32 = src[2];
+ let out_r: f32 = mat.m[0][0] * in_r + mat.m[1][0] * in_g + mat.m[2][0] * in_b;
+ let out_g: f32 = mat.m[0][1] * in_r + mat.m[1][1] * in_g + mat.m[2][1] * in_b;
+ let out_b: f32 = mat.m[0][2] * in_r + mat.m[1][2] * in_g + mat.m[2][2] * in_b;
+ dest[0] = clamp_float(out_r);
+ dest[1] = clamp_float(out_g);
+ dest[2] = clamp_float(out_b);
+ }
+ }
+}
+
+fn modular_transform_create_mAB(lut: &lutmABType) -> Option<Vec<Box<dyn ModularTransform>>> {
+ let mut transforms: Vec<Box<dyn ModularTransform>> = Vec::new();
+ if lut.a_curves[0].is_some() {
+ let clut_length: usize;
+ // If the A curve is present this also implies the
+ // presence of a CLUT.
+ lut.clut_table.as_ref()?;
+
+ // Prepare A curve.
+ let mut transform = Box::new(GammaTable::default());
+ transform.input_clut_table[0] = build_input_gamma_table(lut.a_curves[0].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[1] = build_input_gamma_table(lut.a_curves[1].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[2] = build_input_gamma_table(lut.a_curves[2].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+
+ if lut.num_grid_points[0] as i32 != lut.num_grid_points[1] as i32
+ || lut.num_grid_points[1] as i32 != lut.num_grid_points[2] as i32
+ {
+ //XXX: We don't currently support clut that are not squared!
+ return None;
+ }
+ transforms.push(transform);
+
+ // Prepare CLUT
+ let mut transform = Box::new(ClutOnly::default());
+ clut_length = (lut.num_grid_points[0] as usize).pow(3) * 3;
+ assert_eq!(clut_length, lut.clut_table.as_ref().unwrap().len());
+ transform.clut = lut.clut_table.clone();
+ transform.grid_size = lut.num_grid_points[0] as u16;
+ transforms.push(transform);
+ }
+
+ if lut.m_curves[0].is_some() {
+ // M curve imples the presence of a Matrix
+
+ // Prepare M curve
+ let mut transform = Box::new(GammaTable::default());
+ transform.input_clut_table[0] = build_input_gamma_table(lut.m_curves[0].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[1] = build_input_gamma_table(lut.m_curves[1].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[2] = build_input_gamma_table(lut.m_curves[2].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transforms.push(transform);
+
+ // Prepare Matrix
+ let mut transform = Box::new(MatrixTranslate::default());
+ transform.matrix = build_mAB_matrix(lut);
+ transform.tx = s15Fixed16Number_to_float(lut.e03);
+ transform.ty = s15Fixed16Number_to_float(lut.e13);
+ transform.tz = s15Fixed16Number_to_float(lut.e23);
+ transforms.push(transform);
+ }
+
+ if lut.b_curves[0].is_some() {
+ // Prepare B curve
+ let mut transform = Box::new(GammaTable::default());
+ transform.input_clut_table[0] = build_input_gamma_table(lut.b_curves[0].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[1] = build_input_gamma_table(lut.b_curves[1].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[2] = build_input_gamma_table(lut.b_curves[2].as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transforms.push(transform);
+ } else {
+ // B curve is mandatory
+ return None;
+ }
+
+ if lut.reversed {
+ // mBA are identical to mAB except that the transformation order
+ // is reversed
+ transforms.reverse();
+ }
+ Some(transforms)
+}
+
+fn modular_transform_create_lut(lut: &lutType) -> Option<Vec<Box<dyn ModularTransform>>> {
+ let mut transforms: Vec<Box<dyn ModularTransform>> = Vec::new();
+
+ let clut_length: usize;
+ let mut transform = Box::new(MatrixTransform::default());
+
+ transform.matrix = build_lut_matrix(lut);
+ if true {
+ transforms.push(transform);
+
+ // Prepare input curves
+ let mut transform = Box::new(Clut3x3::default());
+ transform.input_clut_table[0] =
+ Some(lut.input_table[0..lut.num_input_table_entries as usize].to_vec());
+ transform.input_clut_table[1] = Some(
+ lut.input_table
+ [lut.num_input_table_entries as usize..lut.num_input_table_entries as usize * 2]
+ .to_vec(),
+ );
+ transform.input_clut_table[2] = Some(
+ lut.input_table[lut.num_input_table_entries as usize * 2
+ ..lut.num_input_table_entries as usize * 3]
+ .to_vec(),
+ );
+ // Prepare table
+ clut_length = (lut.num_clut_grid_points as usize).pow(3) * 3;
+ assert_eq!(clut_length, lut.clut_table.len());
+ transform.clut = Some(lut.clut_table.clone());
+
+ transform.grid_size = lut.num_clut_grid_points as u16;
+ // Prepare output curves
+ transform.output_clut_table[0] =
+ Some(lut.output_table[0..lut.num_output_table_entries as usize].to_vec());
+ transform.output_clut_table[1] = Some(
+ lut.output_table
+ [lut.num_output_table_entries as usize..lut.num_output_table_entries as usize * 2]
+ .to_vec(),
+ );
+ transform.output_clut_table[2] = Some(
+ lut.output_table[lut.num_output_table_entries as usize * 2
+ ..lut.num_output_table_entries as usize * 3]
+ .to_vec(),
+ );
+ transforms.push(transform);
+ return Some(transforms);
+ }
+ None
+}
+
+fn modular_transform_create_lut4x3(lut: &lutType) -> Vec<Box<dyn ModularTransform>> {
+ let mut transforms: Vec<Box<dyn ModularTransform>> = Vec::new();
+
+ let clut_length: usize;
+ // the matrix of lutType is only used when the input color space is XYZ.
+
+ // Prepare input curves
+ let mut transform = Box::new(Clut4x3::default());
+ transform.input_clut_table[0] =
+ Some(lut.input_table[0..lut.num_input_table_entries as usize].to_vec());
+ transform.input_clut_table[1] = Some(
+ lut.input_table
+ [lut.num_input_table_entries as usize..lut.num_input_table_entries as usize * 2]
+ .to_vec(),
+ );
+ transform.input_clut_table[2] = Some(
+ lut.input_table
+ [lut.num_input_table_entries as usize * 2..lut.num_input_table_entries as usize * 3]
+ .to_vec(),
+ );
+ transform.input_clut_table[3] = Some(
+ lut.input_table
+ [lut.num_input_table_entries as usize * 3..lut.num_input_table_entries as usize * 4]
+ .to_vec(),
+ );
+ // Prepare table
+ clut_length = (lut.num_clut_grid_points as usize).pow(lut.num_input_channels as u32)
+ * lut.num_output_channels as usize;
+ assert_eq!(clut_length, lut.clut_table.len());
+ transform.clut = Some(lut.clut_table.clone());
+
+ transform.grid_size = lut.num_clut_grid_points as u16;
+ // Prepare output curves
+ transform.output_clut_table[0] =
+ Some(lut.output_table[0..lut.num_output_table_entries as usize].to_vec());
+ transform.output_clut_table[1] = Some(
+ lut.output_table
+ [lut.num_output_table_entries as usize..lut.num_output_table_entries as usize * 2]
+ .to_vec(),
+ );
+ transform.output_clut_table[2] = Some(
+ lut.output_table
+ [lut.num_output_table_entries as usize * 2..lut.num_output_table_entries as usize * 3]
+ .to_vec(),
+ );
+ transforms.push(transform);
+ transforms
+}
+
+fn modular_transform_create_input(input: &Profile) -> Option<Vec<Box<dyn ModularTransform>>> {
+ let mut transforms = Vec::new();
+ if let Some(A2B0) = &input.A2B0 {
+ let lut_transform;
+ if A2B0.num_input_channels == 4 {
+ lut_transform = Some(modular_transform_create_lut4x3(&A2B0));
+ } else {
+ lut_transform = modular_transform_create_lut(&A2B0);
+ }
+ if let Some(lut_transform) = lut_transform {
+ transforms.extend(lut_transform);
+ } else {
+ return None;
+ }
+ } else if input.mAB.is_some()
+ && (*input.mAB.as_deref().unwrap()).num_in_channels == 3
+ && (*input.mAB.as_deref().unwrap()).num_out_channels == 3
+ {
+ let mAB_transform = modular_transform_create_mAB(input.mAB.as_deref().unwrap());
+ if let Some(mAB_transform) = mAB_transform {
+ transforms.extend(mAB_transform);
+ } else {
+ return None;
+ }
+ } else {
+ let mut transform = Box::new(GammaTable::default());
+ transform.input_clut_table[0] =
+ build_input_gamma_table(input.redTRC.as_deref()).map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[1] = build_input_gamma_table(input.greenTRC.as_deref())
+ .map(|x| (x as Box<[f32]>).into_vec());
+ transform.input_clut_table[2] =
+ build_input_gamma_table(input.blueTRC.as_deref()).map(|x| (x as Box<[f32]>).into_vec());
+ if transform.input_clut_table[0].is_none()
+ || transform.input_clut_table[1].is_none()
+ || transform.input_clut_table[2].is_none()
+ {
+ return None;
+ } else {
+ transforms.push(transform);
+
+ let mut transform = Box::new(MatrixTransform::default());
+ transform.matrix.m[0][0] = 1. / 1.999_969_5;
+ transform.matrix.m[0][1] = 0.0;
+ transform.matrix.m[0][2] = 0.0;
+ transform.matrix.m[1][0] = 0.0;
+ transform.matrix.m[1][1] = 1. / 1.999_969_5;
+ transform.matrix.m[1][2] = 0.0;
+ transform.matrix.m[2][0] = 0.0;
+ transform.matrix.m[2][1] = 0.0;
+ transform.matrix.m[2][2] = 1. / 1.999_969_5;
+ transforms.push(transform);
+
+ let mut transform = Box::new(MatrixTransform::default());
+ transform.matrix = build_colorant_matrix(input);
+ transforms.push(transform);
+ }
+ }
+ Some(transforms)
+}
+fn modular_transform_create_output(out: &Profile) -> Option<Vec<Box<dyn ModularTransform>>> {
+ let mut transforms = Vec::new();
+ if let Some(B2A0) = &out.B2A0 {
+ if B2A0.num_input_channels != 3 || B2A0.num_output_channels != 3 {
+ return None;
+ }
+ let lut_transform = modular_transform_create_lut(B2A0);
+ if let Some(lut_transform) = lut_transform {
+ transforms.extend(lut_transform);
+ } else {
+ return None;
+ }
+ } else if out.mBA.is_some()
+ && (*out.mBA.as_deref().unwrap()).num_in_channels == 3
+ && (*out.mBA.as_deref().unwrap()).num_out_channels == 3
+ {
+ let lut_transform = modular_transform_create_mAB(out.mBA.as_deref().unwrap());
+ if let Some(lut_transform) = lut_transform {
+ transforms.extend(lut_transform)
+ } else {
+ return None;
+ }
+ } else if let (Some(redTRC), Some(greenTRC), Some(blueTRC)) =
+ (&out.redTRC, &out.greenTRC, &out.blueTRC)
+ {
+ let mut transform = Box::new(MatrixTransform::default());
+ transform.matrix = build_colorant_matrix(out).invert()?;
+ transforms.push(transform);
+
+ let mut transform = Box::new(MatrixTransform::default());
+ transform.matrix.m[0][0] = 1.999_969_5;
+ transform.matrix.m[0][1] = 0.0;
+ transform.matrix.m[0][2] = 0.0;
+ transform.matrix.m[1][0] = 0.0;
+ transform.matrix.m[1][1] = 1.999_969_5;
+ transform.matrix.m[1][2] = 0.0;
+ transform.matrix.m[2][0] = 0.0;
+ transform.matrix.m[2][1] = 0.0;
+ transform.matrix.m[2][2] = 1.999_969_5;
+ transforms.push(transform);
+
+ let mut transform = Box::new(GammaLut::default());
+ transform.output_gamma_lut_r = Some(build_output_lut(redTRC)?);
+ transform.output_gamma_lut_g = Some(build_output_lut(greenTRC)?);
+ transform.output_gamma_lut_b = Some(build_output_lut(blueTRC)?);
+ transforms.push(transform);
+ } else {
+ debug_assert!(false, "Unsupported output profile workflow.");
+ return None;
+ }
+ Some(transforms)
+}
+/* Not Completed
+// Simplify the transformation chain to an equivalent transformation chain
+static struct qcms_modular_transform* qcms_modular_transform_reduce(struct qcms_modular_transform *transform)
+{
+ struct qcms_modular_transform *first_transform = NULL;
+ struct qcms_modular_transform *curr_trans = transform;
+ struct qcms_modular_transform *prev_trans = NULL;
+ while (curr_trans) {
+ struct qcms_modular_transform *next_trans = curr_trans->next_transform;
+ if (curr_trans->transform_module_fn == qcms_transform_module_matrix) {
+ if (next_trans && next_trans->transform_module_fn == qcms_transform_module_matrix) {
+ curr_trans->matrix = matrix_multiply(curr_trans->matrix, next_trans->matrix);
+ goto remove_next;
+ }
+ }
+ if (curr_trans->transform_module_fn == qcms_transform_module_gamma_table) {
+ bool isLinear = true;
+ uint16_t i;
+ for (i = 0; isLinear && i < 256; i++) {
+ isLinear &= (int)(curr_trans->input_clut_table_r[i] * 255) == i;
+ isLinear &= (int)(curr_trans->input_clut_table_g[i] * 255) == i;
+ isLinear &= (int)(curr_trans->input_clut_table_b[i] * 255) == i;
+ }
+ goto remove_current;
+ }
+
+next_transform:
+ if (!next_trans) break;
+ prev_trans = curr_trans;
+ curr_trans = next_trans;
+ continue;
+remove_current:
+ if (curr_trans == transform) {
+ //Update head
+ transform = next_trans;
+ } else {
+ prev_trans->next_transform = next_trans;
+ }
+ curr_trans->next_transform = NULL;
+ qcms_modular_transform_release(curr_trans);
+ //return transform;
+ return qcms_modular_transform_reduce(transform);
+remove_next:
+ curr_trans->next_transform = next_trans->next_transform;
+ next_trans->next_transform = NULL;
+ qcms_modular_transform_release(next_trans);
+ continue;
+ }
+ return transform;
+}
+*/
+fn modular_transform_create(
+ input: &Profile,
+ output: &Profile,
+) -> Option<Vec<Box<dyn ModularTransform>>> {
+ let mut transforms = Vec::new();
+ if input.color_space == RGB_SIGNATURE || input.color_space == CMYK_SIGNATURE {
+ let rgb_to_pcs = modular_transform_create_input(input);
+ if let Some(rgb_to_pcs) = rgb_to_pcs {
+ transforms.extend(rgb_to_pcs);
+ } else {
+ return None;
+ }
+ } else {
+ debug_assert!(false, "input color space not supported");
+ return None;
+ }
+
+ if input.pcs == LAB_SIGNATURE && output.pcs == XYZ_SIGNATURE {
+ transforms.push(Box::new(LABtoXYZ {}));
+ }
+
+ // This does not improve accuracy in practice, something is wrong here.
+ //if (in->chromaticAdaption.invalid == false) {
+ // struct qcms_modular_transform* chromaticAdaption;
+ // chromaticAdaption = qcms_modular_transform_alloc();
+ // if (!chromaticAdaption)
+ // goto fail;
+ // append_transform(chromaticAdaption, &next_transform);
+ // chromaticAdaption->matrix = matrix_invert(in->chromaticAdaption);
+ // chromaticAdaption->transform_module_fn = qcms_transform_module_matrix;
+ //}
+
+ if input.pcs == XYZ_SIGNATURE && output.pcs == LAB_SIGNATURE {
+ transforms.push(Box::new(XYZtoLAB {}));
+ }
+
+ if output.color_space == RGB_SIGNATURE {
+ let pcs_to_rgb = modular_transform_create_output(output);
+ if let Some(pcs_to_rgb) = pcs_to_rgb {
+ transforms.extend(pcs_to_rgb);
+ } else {
+ return None;
+ }
+ } else if output.color_space == CMYK_SIGNATURE {
+ let pcs_to_cmyk = modular_transform_create_output(output)?;
+ transforms.extend(pcs_to_cmyk);
+ } else {
+ debug_assert!(false, "output color space not supported");
+ }
+
+ // Not Completed
+ //return qcms_modular_transform_reduce(first_transform);
+ Some(transforms)
+}
+fn modular_transform_data(
+ transforms: Vec<Box<dyn ModularTransform>>,
+ mut src: Vec<f32>,
+ mut dest: Vec<f32>,
+ _len: usize,
+) -> Vec<f32> {
+ for transform in transforms {
+ // Keep swaping src/dest when performing a transform to use less memory.
+ transform.transform(&src, &mut dest);
+ std::mem::swap(&mut src, &mut dest);
+ }
+ // The results end up in the src buffer because of the switching
+ src
+}
+
+pub fn chain_transform(
+ input: &Profile,
+ output: &Profile,
+ src: Vec<f32>,
+ dest: Vec<f32>,
+ lutSize: usize,
+) -> Option<Vec<f32>> {
+ let transform_list = modular_transform_create(input, output);
+ if let Some(transform_list) = transform_list {
+ let lut = modular_transform_data(transform_list, src, dest, lutSize / 3);
+ return Some(lut);
+ }
+ None
+}
diff --git a/gfx/qcms/src/gtest.rs b/gfx/qcms/src/gtest.rs
new file mode 100644
index 0000000000..bfe350def4
--- /dev/null
+++ b/gfx/qcms/src/gtest.rs
@@ -0,0 +1,962 @@
+#[cfg(all(test, feature = "c_bindings"))]
+#[allow(clippy::all)]
+mod gtest {
+ use crate::{
+ c_bindings::*, iccread::*, transform::DataType::*, transform::*,
+ transform_util::lut_inverse_interp16, Intent::Perceptual,
+ };
+ use libc::c_void;
+ #[cfg(target_arch = "arm")]
+ use std::arch::is_arm_feature_detected;
+ #[cfg(target_arch = "aarch64")]
+ use std::arch::is_aarch64_feature_detected;
+ use std::ptr::null_mut;
+
+ #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+ use crate::transform_neon::{
+ qcms_transform_data_bgra_out_lut_neon, qcms_transform_data_rgb_out_lut_neon,
+ qcms_transform_data_rgba_out_lut_neon,
+ };
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ use crate::{
+ transform_avx::{
+ qcms_transform_data_bgra_out_lut_avx, qcms_transform_data_rgb_out_lut_avx,
+ qcms_transform_data_rgba_out_lut_avx,
+ },
+ transform_sse2::{
+ qcms_transform_data_bgra_out_lut_sse2, qcms_transform_data_rgb_out_lut_sse2,
+ qcms_transform_data_rgba_out_lut_sse2,
+ },
+ };
+
+ #[test]
+ fn test_lut_inverse_crash() {
+ let lutTable1: [u16; 128] = [
+ 0x0000, 0x0000, 0x0000, 0x8000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ ];
+ let lutTable2: [u16; 128] = [
+ 0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ ];
+
+ // Crash/Assert test
+
+ lut_inverse_interp16(5, &lutTable1);
+ lut_inverse_interp16(5, &lutTable2);
+ }
+
+ #[test]
+ fn test_lut_inverse() {
+ // mimic sRGB_v4_ICC mBA Output
+ //
+ // XXXX
+ // X
+ // X
+ // XXXX
+ let mut value: u16;
+ let mut lutTable: [u16; 256] = [0; 256];
+
+ for i in 0..20 {
+ lutTable[i] = 0;
+ }
+
+ for i in 20..200 {
+ lutTable[i] = ((i - 20) * 0xFFFF / (200 - 20)) as u16;
+ }
+
+ for i in 200..lutTable.len() {
+ lutTable[i] = 0xFFFF;
+ }
+
+ for i in 0..65535 {
+ lut_inverse_interp16(i, &lutTable);
+ }
+
+ // Lookup the interesting points
+
+ value = lut_inverse_interp16(0, &lutTable);
+ assert!(value <= 20 * 256);
+
+ value = lut_inverse_interp16(1, &lutTable);
+ assert!(value > 20 * 256);
+
+ value = lut_inverse_interp16(65535, &lutTable);
+ assert!(value < 201 * 256);
+ }
+
+ // this test takes to long to run on miri
+ #[cfg(not(miri))]
+ #[test]
+ fn test_lut_inverse_non_monotonic() {
+ // Make sure we behave sanely for non monotic functions
+ // X X X
+ // X X X
+ // X X X
+ let mut lutTable: [u16; 256] = [0; 256];
+
+ for i in 0..100 {
+ lutTable[i] = ((i - 0) * 0xFFFF / (100 - 0)) as u16;
+ }
+
+ for i in 100..200 {
+ lutTable[i] = ((i - 100) * 0xFFFF / (200 - 100)) as u16;
+ }
+
+ for i in 200..256 {
+ lutTable[i] = ((i - 200) * 0xFFFF / (256 - 200)) as u16;
+ }
+
+ for i in 0..65535 {
+ lut_inverse_interp16(i, &lutTable);
+ }
+
+ // Make sure we don't crash, hang or let sanitizers do their magic
+ }
+ /* qcms_data_create_rgb_with_gamma is broken
+ #[test]
+ fn profile_from_gamma() {
+
+ let white_point = qcms_CIE_xyY { x: 0.64, y: 0.33, Y: 1.};
+ let primaries = qcms_CIE_xyYTRIPLE {
+ red: qcms_CIE_xyY { x: 0.64, y: 0.33, Y: 1.},
+ green: qcms_CIE_xyY { x: 0.21, y: 0.71, Y: 1.},
+ blue: qcms_CIE_xyY { x: 0.15, y: 0.06, Y: 1.}
+ };
+ let mut mem: *mut libc::c_void = std::ptr::null_mut();
+ let mut size: size_t = 0;
+ unsafe { qcms_data_create_rgb_with_gamma(white_point, primaries, 2.2, &mut mem, &mut size); }
+ assert!(size != 0)
+ }
+ */
+
+ #[test]
+ fn alignment() {
+ assert_eq!(std::mem::align_of::<qcms_transform>(), 16);
+ }
+
+ #[test]
+ fn basic() {
+ let sRGB_profile = crate::c_bindings::qcms_profile_sRGB();
+
+ let Rec709Primaries = qcms_CIE_xyYTRIPLE {
+ red: qcms_CIE_xyY {
+ x: 0.6400f64,
+ y: 0.3300f64,
+ Y: 1.0f64,
+ },
+ green: qcms_CIE_xyY {
+ x: 0.3000f64,
+ y: 0.6000f64,
+ Y: 1.0f64,
+ },
+ blue: qcms_CIE_xyY {
+ x: 0.1500f64,
+ y: 0.0600f64,
+ Y: 1.0f64,
+ },
+ };
+ let D65 = qcms_white_point_sRGB();
+ let other = unsafe { qcms_profile_create_rgb_with_gamma(D65, Rec709Primaries, 2.2) };
+ unsafe { qcms_profile_precache_output_transform(&mut *other) };
+
+ let transform = unsafe {
+ qcms_transform_create(&mut *sRGB_profile, RGB8, &mut *other, RGB8, Perceptual)
+ };
+ let mut data: [u8; 120] = [0; 120];
+
+ unsafe {
+ qcms_transform_data(
+ &*transform,
+ data.as_ptr() as *const libc::c_void,
+ data.as_mut_ptr() as *mut libc::c_void,
+ data.len() / 3,
+ )
+ };
+
+ unsafe {
+ qcms_transform_release(transform);
+ qcms_profile_release(sRGB_profile);
+ qcms_profile_release(other);
+ }
+ }
+
+ #[test]
+ fn gray_alpha() {
+ let sRGB_profile = qcms_profile_sRGB();
+ let other = unsafe { qcms_profile_create_gray_with_gamma(2.2) };
+ unsafe { qcms_profile_precache_output_transform(&mut *other) };
+
+ let transform = unsafe {
+ qcms_transform_create(&mut *other, GrayA8, &mut *sRGB_profile, RGBA8, Perceptual)
+ };
+ assert!(!transform.is_null());
+
+ let in_data: [u8; 4] = [0, 255, 255, 0];
+ let mut out_data: [u8; 2 * 4] = [0; 8];
+ unsafe {
+ qcms_transform_data(
+ &*transform,
+ in_data.as_ptr() as *const libc::c_void,
+ out_data.as_mut_ptr() as *mut libc::c_void,
+ in_data.len() / 2,
+ )
+ };
+
+ assert_eq!(out_data, [0, 0, 0, 255, 255, 255, 255, 0]);
+ unsafe {
+ qcms_transform_release(transform);
+ qcms_profile_release(sRGB_profile);
+ qcms_profile_release(other);
+ }
+ }
+ #[test]
+ fn samples() {
+ use libc::c_void;
+ use std::io::Read;
+
+ let mut d = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ qcms_enable_iccv4();
+ d.push("fuzz");
+ d.push("samples");
+ let samples = [
+ "0220-ca351238d719fd07ef8607d326b398fe.icc",
+ "0372-973178997787ee780b4b58ee47cad683.icc",
+ "0744-0a5faafe175e682b10c590b03d3f093b.icc",
+ "0316-eb3f97ab646cd7b66bee80bdfe6098ac.icc",
+ "0732-80707d91aea0f8e64ef0286cc7720e99.icc",
+ "1809-2bd4b77651214ca6110fdbee2502671e.icc",
+ ];
+ for s in samples.iter() {
+ let mut p = d.clone();
+ p.push(s);
+ let mut file = std::fs::File::open(p.clone()).unwrap();
+ let mut data = Vec::new();
+ file.read_to_end(&mut data).unwrap();
+ let profile =
+ unsafe { qcms_profile_from_memory(data.as_ptr() as *const c_void, data.len()) };
+ assert_ne!(profile, std::ptr::null_mut());
+ unsafe { qcms_profile_release(profile) };
+ }
+ }
+
+ #[test]
+ fn v4() {
+ use libc::c_void;
+ use std::io::Read;
+
+ let mut p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ qcms_enable_iccv4();
+ p.push("profiles");
+ // this profile was made by taking the lookup table profile from
+ // http://displaycal.net/icc-color-management-test/ and removing
+ // the unneeed tables using lcms
+ p.push("displaycal-lut-stripped.icc");
+
+ let mut file = std::fs::File::open(p).unwrap();
+ let mut data = Vec::new();
+ file.read_to_end(&mut data).unwrap();
+ let profile =
+ unsafe { qcms_profile_from_memory(data.as_ptr() as *const c_void, data.len()) };
+ assert_ne!(profile, std::ptr::null_mut());
+
+ let srgb_profile = qcms_profile_sRGB();
+ assert_ne!(srgb_profile, std::ptr::null_mut());
+
+ unsafe { qcms_profile_precache_output_transform(&mut *srgb_profile) };
+
+ let intent = unsafe { qcms_profile_get_rendering_intent(&*profile) };
+ let transform =
+ unsafe { qcms_transform_create(&*profile, RGB8, &*srgb_profile, RGB8, intent) };
+
+ assert_ne!(transform, std::ptr::null_mut());
+
+ const SRC_SIZE: usize = 4;
+ let src: [u8; SRC_SIZE * 3] = [
+ 246, 246, 246, // gray
+ 255, 0, 0, // red
+ 0, 255, 255, // cyan
+ 255, 255, 0, // yellow
+ ];
+ let mut dst: [u8; SRC_SIZE * 3] = [0; SRC_SIZE * 3];
+
+ // the reference values here should be adjusted if the accuracy
+ // of the transformation changes
+ let reference = [
+ 246, 246, 246, // gray
+ 255, 0, 0, // red
+ 248, 14, 22, // red
+ 0, 0, 255, // blue
+ ];
+
+ unsafe {
+ qcms_transform_data(
+ &*transform,
+ src.as_ptr() as *const libc::c_void,
+ dst.as_mut_ptr() as *mut libc::c_void,
+ SRC_SIZE,
+ );
+ }
+
+ assert_eq!(reference, dst);
+ unsafe { qcms_transform_release(transform) }
+ unsafe { qcms_profile_release(profile) }
+ unsafe { qcms_profile_release(srgb_profile) }
+ }
+
+ fn CmpRgbChannel(reference: &[u8], test: &[u8], index: usize) -> bool {
+ (reference[index] as i32 - test[index] as i32).abs() <= 1
+ }
+
+ fn CmpRgbBufferImpl(
+ refBuffer: &[u8],
+ testBuffer: &[u8],
+ pixels: usize,
+ kSwapRB: bool,
+ hasAlpha: bool,
+ ) -> bool {
+ let pixelSize = if hasAlpha { 4 } else { 3 };
+ if refBuffer[..pixels * pixelSize] == testBuffer[..pixels * pixelSize] {
+ return true;
+ }
+
+ let kRIndex = if kSwapRB { 2 } else { 0 };
+ let kGIndex = 1;
+ let kBIndex = if kSwapRB { 0 } else { 2 };
+ let kAIndex = 3;
+
+ let mut remaining = pixels;
+ let mut reference = &refBuffer[..];
+ let mut test = &testBuffer[..];
+ while remaining > 0 {
+ if !CmpRgbChannel(reference, test, kRIndex)
+ || !CmpRgbChannel(reference, test, kGIndex)
+ || !CmpRgbChannel(reference, test, kBIndex)
+ || (hasAlpha && reference[kAIndex] != test[kAIndex])
+ {
+ assert_eq!(test[kRIndex], reference[kRIndex]);
+ assert_eq!(test[kGIndex], reference[kGIndex]);
+ assert_eq!(test[kBIndex], reference[kBIndex]);
+ if hasAlpha {
+ assert_eq!(test[kAIndex], reference[kAIndex]);
+ }
+ return false;
+ }
+ remaining -= 1;
+ reference = &reference[pixelSize..];
+ test = &test[pixelSize..];
+ }
+
+ true
+ }
+
+ fn GetRgbInputBufferImpl(kSwapRB: bool, kHasAlpha: bool) -> (usize, Vec<u8>) {
+ let colorSamples = [0, 5, 16, 43, 101, 127, 182, 255];
+ let colorSampleMax = colorSamples.len();
+ let pixelSize = if kHasAlpha { 4 } else { 3 };
+ let pixelCount = colorSampleMax * colorSampleMax * 256 * 3;
+
+ let mut outBuffer = vec![0; pixelCount * pixelSize];
+
+ let kRIndex = if kSwapRB { 2 } else { 0 };
+ let kGIndex = 1;
+ let kBIndex = if kSwapRB { 0 } else { 2 };
+ let kAIndex = 3;
+
+ // Sample every red pixel value with a subset of green and blue.
+ // we use a u16 for r to avoid https://github.com/rust-lang/rust/issues/78283
+ let mut color: &mut [u8] = &mut outBuffer[..];
+ for r in 0..=255u16 {
+ for &g in colorSamples.iter() {
+ for &b in colorSamples.iter() {
+ color[kRIndex] = r as u8;
+ color[kGIndex] = g;
+ color[kBIndex] = b;
+ if kHasAlpha {
+ color[kAIndex] = 0x80;
+ }
+ color = &mut color[pixelSize..];
+ }
+ }
+ }
+
+ // Sample every green pixel value with a subset of red and blue.
+ let mut color = &mut outBuffer[..];
+ for &r in colorSamples.iter() {
+ for g in 0..=255u16 {
+ for &b in colorSamples.iter() {
+ color[kRIndex] = r;
+ color[kGIndex] = g as u8;
+ color[kBIndex] = b;
+ if kHasAlpha {
+ color[kAIndex] = 0x80;
+ }
+ color = &mut color[pixelSize..];
+ }
+ }
+ }
+
+ // Sample every blue pixel value with a subset of red and green.
+ let mut color = &mut outBuffer[..];
+ for &r in colorSamples.iter() {
+ for &g in colorSamples.iter() {
+ for b in 0..=255u16 {
+ color[kRIndex] = r;
+ color[kGIndex] = g;
+ color[kBIndex] = b as u8;
+ if kHasAlpha {
+ color[kAIndex] = 0x80;
+ }
+ color = &mut color[pixelSize..];
+ }
+ }
+ }
+
+ (pixelCount, outBuffer)
+ }
+
+ fn GetRgbInputBuffer() -> (usize, Vec<u8>) {
+ GetRgbInputBufferImpl(false, false)
+ }
+
+ fn GetRgbaInputBuffer() -> (usize, Vec<u8>) {
+ GetRgbInputBufferImpl(false, true)
+ }
+
+ fn GetBgraInputBuffer() -> (usize, Vec<u8>) {
+ GetRgbInputBufferImpl(true, true)
+ }
+
+ fn CmpRgbBuffer(refBuffer: &[u8], testBuffer: &[u8], pixels: usize) -> bool {
+ CmpRgbBufferImpl(refBuffer, testBuffer, pixels, false, false)
+ }
+
+ fn CmpRgbaBuffer(refBuffer: &[u8], testBuffer: &[u8], pixels: usize) -> bool {
+ CmpRgbBufferImpl(refBuffer, testBuffer, pixels, false, true)
+ }
+
+ fn CmpBgraBuffer(refBuffer: &[u8], testBuffer: &[u8], pixels: usize) -> bool {
+ CmpRgbBufferImpl(refBuffer, testBuffer, pixels, true, true)
+ }
+
+ fn ClearRgbBuffer(buffer: &mut [u8], pixels: usize) {
+ for i in 0..pixels * 3 {
+ buffer[i] = 0;
+ }
+ }
+
+ fn ClearRgbaBuffer(buffer: &mut [u8], pixels: usize) {
+ for i in 0..pixels * 4 {
+ buffer[i] = 0;
+ }
+ }
+
+ fn GetRgbOutputBuffer(pixels: usize) -> Vec<u8> {
+ vec![0; pixels * 3]
+ }
+
+ fn GetRgbaOutputBuffer(pixels: usize) -> Vec<u8> {
+ vec![0; pixels * 4]
+ }
+
+ struct QcmsProfileTest {
+ in_profile: *mut Profile,
+ out_profile: *mut Profile,
+ transform: *mut qcms_transform,
+
+ input: Vec<u8>,
+ output: Vec<u8>,
+ reference: Vec<u8>,
+
+ pixels: usize,
+ storage_type: DataType,
+ precache: bool,
+ }
+
+ impl QcmsProfileTest {
+ fn new() -> QcmsProfileTest {
+ QcmsProfileTest {
+ in_profile: null_mut(),
+ out_profile: null_mut(),
+ transform: null_mut(),
+ input: Vec::new(),
+ output: Vec::new(),
+ reference: Vec::new(),
+
+ pixels: 0,
+ storage_type: RGB8,
+ precache: false,
+ }
+ }
+
+ fn SetUp(&mut self) {
+ qcms_enable_iccv4();
+ }
+
+ unsafe fn TearDown(&mut self) {
+ if self.in_profile != null_mut() {
+ qcms_profile_release(self.in_profile)
+ }
+
+ if self.out_profile != null_mut() {
+ qcms_profile_release(self.out_profile)
+ }
+
+ if self.transform != null_mut() {
+ qcms_transform_release(self.transform)
+ }
+ }
+
+ unsafe fn SetTransform(&mut self, transform: *mut qcms_transform) -> bool {
+ if self.transform != null_mut() {
+ qcms_transform_release(self.transform)
+ }
+ self.transform = transform;
+ self.transform != null_mut()
+ }
+
+ unsafe fn SetTransformForType(&mut self, ty: DataType) -> bool {
+ self.SetTransform(qcms_transform_create(
+ &*self.in_profile,
+ ty,
+ &*self.out_profile,
+ ty,
+ Perceptual,
+ ))
+ }
+
+ unsafe fn SetBuffers(&mut self, ty: DataType) -> bool {
+ match ty {
+ RGB8 => {
+ let (pixels, input) = GetRgbInputBuffer();
+ self.input = input;
+ self.pixels = pixels;
+ self.reference = GetRgbOutputBuffer(self.pixels);
+ self.output = GetRgbOutputBuffer(self.pixels)
+ }
+ RGBA8 => {
+ let (pixels, input) = GetBgraInputBuffer();
+ self.input = input;
+ self.pixels = pixels;
+ self.reference = GetRgbaOutputBuffer(self.pixels);
+ self.output = GetRgbaOutputBuffer(self.pixels);
+ }
+ BGRA8 => {
+ let (pixels, input) = GetRgbaInputBuffer();
+ self.input = input;
+ self.pixels = pixels;
+ self.reference = GetRgbaOutputBuffer(self.pixels);
+ self.output = GetRgbaOutputBuffer(self.pixels);
+ }
+ _ => unreachable!("Unknown type!"),
+ }
+ self.storage_type = ty;
+ self.pixels > 0
+ }
+
+ unsafe fn ClearOutputBuffer(&mut self) {
+ match self.storage_type {
+ RGB8 => ClearRgbBuffer(&mut self.output, self.pixels),
+ RGBA8 | BGRA8 => ClearRgbaBuffer(&mut self.output, self.pixels),
+ _ => unreachable!("Unknown type!"),
+ }
+ }
+
+ unsafe fn ProduceRef(&mut self, trans_fn: transform_fn_t) {
+ trans_fn.unwrap()(
+ &*self.transform,
+ self.input.as_mut_ptr(),
+ self.reference.as_mut_ptr(),
+ self.pixels,
+ )
+ }
+
+ fn CopyInputToRef(&mut self) {
+ let pixelSize = match self.storage_type {
+ RGB8 => 3,
+ RGBA8 | BGRA8 => 4,
+ _ => unreachable!("Unknown type!"),
+ };
+ self.reference
+ .copy_from_slice(&self.input[..self.pixels * pixelSize])
+ }
+
+ unsafe fn ProduceOutput(&mut self, trans_fn: transform_fn_t) {
+ self.ClearOutputBuffer();
+ trans_fn.unwrap()(
+ &*self.transform,
+ self.input.as_mut_ptr(),
+ self.output.as_mut_ptr(),
+ self.pixels,
+ )
+ }
+
+ unsafe fn VerifyOutput(&self, buf: &[u8]) -> bool {
+ match self.storage_type {
+ RGB8 => CmpRgbBuffer(buf, &self.output, self.pixels),
+ RGBA8 => CmpRgbaBuffer(buf, &self.output, self.pixels),
+ BGRA8 => CmpBgraBuffer(buf, &self.output, self.pixels),
+ _ => unreachable!("Unknown type!"),
+ }
+ }
+
+ unsafe fn ProduceVerifyOutput(&mut self, trans_fn: transform_fn_t) -> bool {
+ self.ProduceOutput(trans_fn);
+ self.VerifyOutput(&self.reference)
+ }
+
+ unsafe fn PrecacheOutput(&mut self) {
+ qcms_profile_precache_output_transform(&mut *self.out_profile);
+ self.precache = true;
+ }
+ unsafe fn TransformPrecache(&mut self) {
+ assert_eq!(self.precache, false);
+ assert!(self.SetBuffers(RGB8));
+ assert!(self.SetTransformForType(RGB8));
+ self.ProduceRef(Some(qcms_transform_data_rgb_out_lut));
+
+ self.PrecacheOutput();
+ assert!(self.SetTransformForType(RGB8));
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgb_out_lut_precache)))
+ }
+
+ unsafe fn TransformPrecachePlatformExt(&mut self) {
+ self.PrecacheOutput();
+
+ // Verify RGB transforms.
+ assert!(self.SetBuffers(RGB8));
+ assert!(self.SetTransformForType(RGB8));
+ self.ProduceRef(Some(qcms_transform_data_rgb_out_lut_precache));
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ if is_x86_feature_detected!("sse2") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgb_out_lut_sse2)));
+ }
+ if is_x86_feature_detected!("avx") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgb_out_lut_avx)))
+ }
+ }
+
+ #[cfg(target_arch = "arm")]
+ {
+ if is_arm_feature_detected!("neon") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgb_out_lut_neon)))
+ }
+ }
+
+ #[cfg(target_arch = "aarch64")]
+ {
+ if is_aarch64_feature_detected!("neon") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgb_out_lut_neon)))
+ }
+ }
+
+ // Verify RGBA transform.
+ assert!(self.SetBuffers(RGBA8));
+ assert!(self.SetTransformForType(RGBA8));
+ self.ProduceRef(Some(qcms_transform_data_rgba_out_lut_precache));
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ if is_x86_feature_detected!("sse2") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgba_out_lut_sse2)));
+ }
+ if is_x86_feature_detected!("avx") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgba_out_lut_avx)))
+ }
+ }
+
+ #[cfg(target_arch = "arm")]
+ {
+ if is_arm_feature_detected!("neon") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgba_out_lut_neon)))
+ }
+ }
+
+ #[cfg(target_arch = "aarch64")]
+ {
+ if is_aarch64_feature_detected!("neon") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_rgba_out_lut_neon)))
+ }
+ }
+
+ // Verify BGRA transform.
+ assert!(self.SetBuffers(BGRA8));
+ assert!(self.SetTransformForType(BGRA8));
+ self.ProduceRef(Some(qcms_transform_data_bgra_out_lut_precache));
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ if is_x86_feature_detected!("sse2") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_bgra_out_lut_sse2)));
+ }
+ if is_x86_feature_detected!("avx") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_bgra_out_lut_avx)))
+ }
+ }
+
+ #[cfg(target_arch = "arm")]
+ {
+ if is_arm_feature_detected!("neon") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_bgra_out_lut_neon)))
+ }
+ }
+
+ #[cfg(target_arch = "aarch64")]
+ {
+ if is_aarch64_feature_detected!("neon") {
+ assert!(self.ProduceVerifyOutput(Some(qcms_transform_data_bgra_out_lut_neon)))
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn sRGB_to_sRGB_precache() {
+ unsafe {
+ let mut pt = QcmsProfileTest::new();
+ pt.SetUp();
+ pt.in_profile = qcms_profile_sRGB();
+ pt.out_profile = qcms_profile_sRGB();
+ pt.TransformPrecache();
+ pt.TearDown();
+ }
+ }
+
+ #[test]
+ fn sRGB_to_sRGB_transform_identity() {
+ unsafe {
+ let mut pt = QcmsProfileTest::new();
+ pt.SetUp();
+ pt.in_profile = qcms_profile_sRGB();
+ pt.out_profile = qcms_profile_sRGB();
+ pt.PrecacheOutput();
+ pt.SetBuffers(RGB8);
+ pt.SetTransformForType(RGB8);
+ qcms_transform_data(
+ &*pt.transform,
+ pt.input.as_mut_ptr() as *mut c_void,
+ pt.output.as_mut_ptr() as *mut c_void,
+ pt.pixels,
+ );
+ assert!(pt.VerifyOutput(&pt.input));
+ pt.TearDown();
+ }
+ }
+
+ fn profile_from_path(file: &str) -> *mut Profile {
+ use std::io::Read;
+ let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ path.push("profiles");
+ path.push(file);
+ let mut file = std::fs::File::open(path).unwrap();
+ let mut data = Vec::new();
+ file.read_to_end(&mut data).unwrap();
+ let profile =
+ unsafe { qcms_profile_from_memory(data.as_ptr() as *const c_void, data.len()) };
+ assert_ne!(profile, std::ptr::null_mut());
+ profile
+ }
+
+ #[test]
+ fn sRGB_to_ThinkpadW540() {
+ unsafe {
+ let mut pt = QcmsProfileTest::new();
+ pt.SetUp();
+ pt.in_profile = qcms_profile_sRGB();
+ pt.out_profile = profile_from_path("lcms_thinkpad_w540.icc");
+ pt.TransformPrecachePlatformExt();
+ pt.TearDown();
+ }
+ }
+
+ #[test]
+ fn sRGB_to_SamsungSyncmaster() {
+ unsafe {
+ let mut pt = QcmsProfileTest::new();
+ pt.SetUp();
+ pt.in_profile = qcms_profile_sRGB();
+ pt.out_profile = profile_from_path("lcms_samsung_syncmaster.icc");
+ pt.TransformPrecachePlatformExt();
+ pt.TearDown();
+ }
+ }
+
+ #[test]
+ fn v4_output() {
+ qcms_enable_iccv4();
+ let input = qcms_profile_sRGB();
+ // B2A0-ident.icc was created from the profile in bug 1679621
+ // manually edited using iccToXML/iccFromXML
+ let output = profile_from_path("B2A0-ident.icc");
+
+ let transform = unsafe { qcms_transform_create(&*input, RGB8, &*output, RGB8, Perceptual) };
+ let src = [0u8, 60, 195];
+ let mut dst = [0u8, 0, 0];
+ unsafe {
+ qcms_transform_data(
+ &*transform,
+ src.as_ptr() as *const libc::c_void,
+ dst.as_mut_ptr() as *mut libc::c_void,
+ 1,
+ );
+ }
+ assert_eq!(dst, [15, 16, 122]);
+ unsafe {
+ qcms_transform_release(transform);
+ qcms_profile_release(input);
+ qcms_profile_release(output);
+ }
+ }
+
+ #[test]
+ fn gray_smoke_test() {
+ let input = crate::Profile::new_gray_with_gamma(2.2);
+ let output = crate::Profile::new_sRGB();
+ let xfm =
+ transform_create(&input, GrayA8, &output, RGBA8, crate::Intent::default()).unwrap();
+ let src = [20u8, 20u8];
+ let mut dst = [0u8, 0, 0, 0];
+ unsafe {
+ qcms_transform_data(
+ &xfm,
+ src.as_ptr() as *const libc::c_void,
+ dst.as_mut_ptr() as *mut libc::c_void,
+ src.len() / GrayA8.bytes_per_pixel(),
+ );
+ }
+ }
+
+ #[test]
+ fn data_create_rgb_with_gamma() {
+ let Rec709Primaries = qcms_CIE_xyYTRIPLE {
+ red: {
+ qcms_CIE_xyY {
+ x: 0.6400,
+ y: 0.3300,
+ Y: 1.0,
+ }
+ },
+ green: {
+ qcms_CIE_xyY {
+ x: 0.3000,
+ y: 0.6000,
+ Y: 1.0,
+ }
+ },
+ blue: {
+ qcms_CIE_xyY {
+ x: 0.1500,
+ y: 0.0600,
+ Y: 1.0,
+ }
+ },
+ };
+ let D65 = qcms_white_point_sRGB();
+ let mut mem = std::ptr::null_mut();
+ let mut size = 0;
+ unsafe {
+ qcms_data_create_rgb_with_gamma(D65, Rec709Primaries, 2.2, &mut mem, &mut size);
+ }
+ assert_ne!(size, 0);
+ unsafe { libc::free(mem) };
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{Profile, Transform};
+ #[test]
+ fn identity() {
+ let p1 = Profile::new_sRGB();
+ let p2 = Profile::new_sRGB();
+ let xfm =
+ Transform::new(&p1, &p2, crate::DataType::RGB8, crate::Intent::default()).unwrap();
+ let mut data = [4, 30, 80];
+ xfm.apply(&mut data);
+ assert_eq!(data, [4, 30, 80]);
+ }
+ #[test]
+ fn D50() {
+ let p1 = Profile::new_sRGB();
+ let p2 = Profile::new_XYZD50();
+ let xfm =
+ Transform::new(&p1, &p2, crate::DataType::RGB8, crate::Intent::default()).unwrap();
+ let mut data = [4, 30, 80];
+ xfm.apply(&mut data);
+ assert_eq!(data, [4, 4, 15]);
+ }
+
+ fn profile_from_path(file: &str) -> Box<Profile> {
+ use std::io::Read;
+ let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ path.push("profiles");
+ path.push(file);
+ let mut file = std::fs::File::open(path).unwrap();
+ let mut data = Vec::new();
+ file.read_to_end(&mut data).unwrap();
+ Profile::new_from_slice(&data, false).unwrap()
+ }
+
+ #[test]
+ fn parametric_threshold() {
+ let src = profile_from_path("parametric-thresh.icc");
+ let dst = crate::Profile::new_sRGB();
+ let xfm =
+ Transform::new(&src, &dst, crate::DataType::RGB8, crate::Intent::default()).unwrap();
+ let mut data = [4, 30, 80];
+ xfm.apply(&mut data);
+ assert_eq!(data, [188, 188, 189]);
+ }
+
+ #[test]
+ fn cmyk() {
+ let input = profile_from_path("ps_cmyk_min.icc");
+ let output = Profile::new_sRGB();
+ let xfm = crate::Transform::new_to(
+ &input,
+ &output,
+ crate::DataType::CMYK,
+ crate::DataType::RGB8,
+ crate::Intent::default(),
+ )
+ .unwrap();
+ let src = [4, 30, 80, 10];
+ let mut dst = [0, 0, 0];
+ xfm.convert(&src, &mut dst);
+ assert_eq!(dst, [252, 237, 211]);
+ }
+
+ #[test]
+ fn sRGB_parametric() {
+ let src = Profile::new_sRGB();
+ let dst = Profile::new_sRGB_parametric();
+ let xfm =
+ Transform::new(&src, &dst, crate::DataType::RGB8, crate::Intent::default()).unwrap();
+ let mut data = [4, 30, 80];
+ xfm.apply(&mut data);
+ assert_eq!(data, [4, 30, 80]);
+ }
+}
diff --git a/gfx/qcms/src/iccread.rs b/gfx/qcms/src/iccread.rs
new file mode 100644
index 0000000000..ad45a58d35
--- /dev/null
+++ b/gfx/qcms/src/iccread.rs
@@ -0,0 +1,1742 @@
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+use std::{
+ convert::{TryInto, TryFrom},
+ sync::atomic::AtomicBool,
+ sync::Arc,
+};
+
+use crate::{
+ double_to_s15Fixed16Number,
+ transform::{set_rgb_colorants, PrecacheOuput},
+};
+use crate::{matrix::Matrix, s15Fixed16Number, s15Fixed16Number_to_float, Intent, Intent::*};
+
+pub static SUPPORTS_ICCV4: AtomicBool = AtomicBool::new(cfg!(feature = "iccv4-enabled"));
+
+pub const RGB_SIGNATURE: u32 = 0x52474220;
+pub const GRAY_SIGNATURE: u32 = 0x47524159;
+pub const XYZ_SIGNATURE: u32 = 0x58595A20;
+pub const LAB_SIGNATURE: u32 = 0x4C616220;
+pub const CMYK_SIGNATURE: u32 = 0x434D594B; // 'CMYK'
+
+/// A color profile
+#[derive(Default, Debug)]
+pub struct Profile {
+ pub(crate) class_type: u32,
+ pub(crate) color_space: u32,
+ pub(crate) pcs: u32,
+ pub(crate) rendering_intent: Intent,
+ pub(crate) redColorant: XYZNumber,
+ pub(crate) blueColorant: XYZNumber,
+ pub(crate) greenColorant: XYZNumber,
+ // "TRC" is EOTF, e.g. gamma->linear transfer function.
+ // Because ICC profiles are phrased as decodings to the xyzd50-linear PCS.
+ pub(crate) redTRC: Option<Box<curveType>>,
+ pub(crate) blueTRC: Option<Box<curveType>>,
+ pub(crate) greenTRC: Option<Box<curveType>>,
+ pub(crate) grayTRC: Option<Box<curveType>>,
+ pub(crate) A2B0: Option<Box<lutType>>,
+ pub(crate) B2A0: Option<Box<lutType>>,
+ pub(crate) mAB: Option<Box<lutmABType>>,
+ pub(crate) mBA: Option<Box<lutmABType>>,
+ pub(crate) chromaticAdaption: Option<Matrix>,
+ pub(crate) output_table_r: Option<Arc<PrecacheOuput>>,
+ pub(crate) output_table_g: Option<Arc<PrecacheOuput>>,
+ pub(crate) output_table_b: Option<Arc<PrecacheOuput>>,
+ is_srgb: bool,
+}
+
+#[derive(Debug, Default)]
+#[allow(clippy::upper_case_acronyms)]
+pub(crate) struct lutmABType {
+ pub num_in_channels: u8,
+ pub num_out_channels: u8,
+ // 16 is the upperbound, actual is 0..num_in_channels.
+ pub num_grid_points: [u8; 16],
+ pub e00: s15Fixed16Number,
+ pub e01: s15Fixed16Number,
+ pub e02: s15Fixed16Number,
+ pub e03: s15Fixed16Number,
+ pub e10: s15Fixed16Number,
+ pub e11: s15Fixed16Number,
+ pub e12: s15Fixed16Number,
+ pub e13: s15Fixed16Number,
+ pub e20: s15Fixed16Number,
+ pub e21: s15Fixed16Number,
+ pub e22: s15Fixed16Number,
+ pub e23: s15Fixed16Number,
+ // reversed elements (for mBA)
+ pub reversed: bool,
+ pub clut_table: Option<Vec<f32>>,
+ pub a_curves: [Option<Box<curveType>>; MAX_CHANNELS],
+ pub b_curves: [Option<Box<curveType>>; MAX_CHANNELS],
+ pub m_curves: [Option<Box<curveType>>; MAX_CHANNELS],
+}
+#[derive(Clone, Debug)]
+pub(crate) enum curveType {
+ Curve(Vec<uInt16Number>), // len=0 => Linear, len=1 => Gamma(v[0]), _ => lut
+ /// The ICC parametricCurveType is specified in terms of s15Fixed16Number,
+ /// so it's possible to use this variant to specify greater precision than
+ /// any raw ICC profile could
+ Parametric(Vec<f32>),
+}
+type uInt16Number = u16;
+
+/* should lut8Type and lut16Type be different types? */
+#[derive(Debug)]
+pub(crate) struct lutType {
+ // used by lut8Type/lut16Type (mft2) only
+ pub num_input_channels: u8,
+ pub num_output_channels: u8,
+ pub num_clut_grid_points: u8,
+ pub e00: s15Fixed16Number,
+ pub e01: s15Fixed16Number,
+ pub e02: s15Fixed16Number,
+ pub e10: s15Fixed16Number,
+ pub e11: s15Fixed16Number,
+ pub e12: s15Fixed16Number,
+ pub e20: s15Fixed16Number,
+ pub e21: s15Fixed16Number,
+ pub e22: s15Fixed16Number,
+ pub num_input_table_entries: u16,
+ pub num_output_table_entries: u16,
+ pub input_table: Vec<f32>,
+ pub clut_table: Vec<f32>,
+ pub output_table: Vec<f32>,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Default)]
+#[allow(clippy::upper_case_acronyms)]
+pub struct XYZNumber {
+ pub X: s15Fixed16Number,
+ pub Y: s15Fixed16Number,
+ pub Z: s15Fixed16Number,
+}
+
+/// A color in the CIE xyY color space
+/* the names for the following two types are sort of ugly */
+#[repr(C)]
+#[derive(Copy, Clone)]
+#[allow(clippy::upper_case_acronyms)]
+pub struct qcms_CIE_xyY {
+ pub x: f64,
+ pub y: f64,
+ pub Y: f64,
+}
+
+/// A more convenient type for specifying primaries and white points where
+/// luminosity is irrelevant
+struct qcms_chromaticity {
+ x: f64,
+ y: f64,
+}
+
+impl qcms_chromaticity {
+ const D65: Self = Self {
+ x: 0.3127,
+ y: 0.3290,
+ };
+}
+
+impl From<qcms_chromaticity> for qcms_CIE_xyY {
+ fn from(qcms_chromaticity { x, y }: qcms_chromaticity) -> Self {
+ Self { x, y, Y: 1.0 }
+ }
+}
+
+/// a set of CIE_xyY values that can use to describe the primaries of a color space
+#[repr(C)]
+#[derive(Copy, Clone)]
+#[allow(clippy::upper_case_acronyms)]
+pub struct qcms_CIE_xyYTRIPLE {
+ pub red: qcms_CIE_xyY,
+ pub green: qcms_CIE_xyY,
+ pub blue: qcms_CIE_xyY,
+}
+
+struct Tag {
+ signature: u32,
+ offset: u32,
+ size: u32,
+}
+
+/* It might be worth having a unified limit on content controlled
+ * allocation per profile. This would remove the need for many
+ * of the arbitrary limits that we used */
+
+type TagIndex = [Tag];
+
+/* a wrapper around the memory that we are going to parse
+ * into a qcms_profile */
+struct MemSource<'a> {
+ buf: &'a [u8],
+ valid: bool,
+ invalid_reason: Option<&'static str>,
+}
+pub type uInt8Number = u8;
+#[inline]
+fn uInt8Number_to_float(a: uInt8Number) -> f32 {
+ a as f32 / 255.0
+}
+
+#[inline]
+fn uInt16Number_to_float(a: uInt16Number) -> f32 {
+ a as f32 / 65535.0
+}
+
+fn invalid_source(mem: &mut MemSource, reason: &'static str) {
+ mem.valid = false;
+ mem.invalid_reason = Some(reason);
+}
+fn read_u32(mem: &mut MemSource, offset: usize) -> u32 {
+ let val = mem.buf.get(offset..offset + 4);
+ if let Some(val) = val {
+ let val = val.try_into().unwrap();
+ u32::from_be_bytes(val)
+ } else {
+ invalid_source(mem, "Invalid offset");
+ 0
+ }
+}
+fn read_u16(mem: &mut MemSource, offset: usize) -> u16 {
+ let val = mem.buf.get(offset..offset + 2);
+ if let Some(val) = val {
+ let val = val.try_into().unwrap();
+ u16::from_be_bytes(val)
+ } else {
+ invalid_source(mem, "Invalid offset");
+ 0
+ }
+}
+fn read_u8(mem: &mut MemSource, offset: usize) -> u8 {
+ let val = mem.buf.get(offset);
+ if let Some(val) = val {
+ *val
+ } else {
+ invalid_source(mem, "Invalid offset");
+ 0
+ }
+}
+fn read_s15Fixed16Number(mem: &mut MemSource, offset: usize) -> s15Fixed16Number {
+ read_u32(mem, offset) as s15Fixed16Number
+}
+fn read_uInt8Number(mem: &mut MemSource, offset: usize) -> uInt8Number {
+ read_u8(mem, offset)
+}
+fn read_uInt16Number(mem: &mut MemSource, offset: usize) -> uInt16Number {
+ read_u16(mem, offset)
+}
+pub fn write_u32(mem: &mut [u8], offset: usize, value: u32) {
+ // we use get() and expect() instead of [..] so there's only one call to panic
+ // instead of two
+ mem.get_mut(offset..offset + std::mem::size_of_val(&value))
+ .expect("OOB")
+ .copy_from_slice(&value.to_be_bytes());
+}
+pub fn write_u16(mem: &mut [u8], offset: usize, value: u16) {
+ // we use get() and expect() instead of [..] so there's only one call to panic
+ // intead of two
+ mem.get_mut(offset..offset + std::mem::size_of_val(&value))
+ .expect("OOB")
+ .copy_from_slice(&value.to_be_bytes());
+}
+
+/* An arbitrary 4MB limit on profile size */
+pub(crate) const MAX_PROFILE_SIZE: usize = 1024 * 1024 * 4;
+const MAX_TAG_COUNT: u32 = 1024;
+
+fn check_CMM_type_signature(_src: &mut MemSource) {
+ //uint32_t CMM_type_signature = read_u32(src, 4);
+ //TODO: do the check?
+}
+fn check_profile_version(src: &mut MemSource) {
+ /*
+ uint8_t major_revision = read_u8(src, 8 + 0);
+ uint8_t minor_revision = read_u8(src, 8 + 1);
+ */
+ let reserved1: u8 = read_u8(src, (8 + 2) as usize);
+ let reserved2: u8 = read_u8(src, (8 + 3) as usize);
+ /* Checking the version doesn't buy us anything
+ if (major_revision != 0x4) {
+ if (major_revision > 0x2)
+ invalid_source(src, "Unsupported major revision");
+ if (minor_revision > 0x40)
+ invalid_source(src, "Unsupported minor revision");
+ }
+ */
+ if reserved1 != 0 || reserved2 != 0 {
+ invalid_source(src, "Invalid reserved bytes");
+ };
+}
+
+const INPUT_DEVICE_PROFILE: u32 = 0x73636e72; // 'scnr'
+pub const DISPLAY_DEVICE_PROFILE: u32 = 0x6d6e7472; // 'mntr'
+const OUTPUT_DEVICE_PROFILE: u32 = 0x70727472; // 'prtr'
+const DEVICE_LINK_PROFILE: u32 = 0x6c696e6b; // 'link'
+const COLOR_SPACE_PROFILE: u32 = 0x73706163; // 'spac'
+const ABSTRACT_PROFILE: u32 = 0x61627374; // 'abst'
+const NAMED_COLOR_PROFILE: u32 = 0x6e6d636c; // 'nmcl'
+
+fn read_class_signature(profile: &mut Profile, mem: &mut MemSource) {
+ profile.class_type = read_u32(mem, 12);
+ match profile.class_type {
+ DISPLAY_DEVICE_PROFILE
+ | INPUT_DEVICE_PROFILE
+ | OUTPUT_DEVICE_PROFILE
+ | COLOR_SPACE_PROFILE => {}
+ _ => {
+ invalid_source(mem, "Invalid Profile/Device Class signature");
+ }
+ };
+}
+fn read_color_space(profile: &mut Profile, mem: &mut MemSource) {
+ profile.color_space = read_u32(mem, 16);
+ match profile.color_space {
+ RGB_SIGNATURE | GRAY_SIGNATURE => {}
+ #[cfg(feature = "cmyk")]
+ CMYK_SIGNATURE => {}
+ _ => {
+ invalid_source(mem, "Unsupported colorspace");
+ }
+ };
+}
+fn read_pcs(profile: &mut Profile, mem: &mut MemSource) {
+ profile.pcs = read_u32(mem, 20);
+ match profile.pcs {
+ XYZ_SIGNATURE | LAB_SIGNATURE => {}
+ _ => {
+ invalid_source(mem, "Unsupported pcs");
+ }
+ };
+}
+fn read_tag_table(_profile: &mut Profile, mem: &mut MemSource) -> Vec<Tag> {
+ let count = read_u32(mem, 128);
+ if count > MAX_TAG_COUNT {
+ invalid_source(mem, "max number of tags exceeded");
+ return Vec::new();
+ }
+ let mut index = Vec::with_capacity(count as usize);
+ for i in 0..count {
+ let tag_start = (128 + 4 + 4 * i * 3) as usize;
+ let offset = read_u32(mem, tag_start + 4);
+ if offset as usize > mem.buf.len() {
+ invalid_source(mem, "tag points beyond the end of the buffer");
+ }
+ index.push(Tag {
+ signature: read_u32(mem, tag_start),
+ offset,
+ size: read_u32(mem, tag_start + 8),
+ });
+ }
+
+ index
+}
+
+/// Checks a profile for obvious inconsistencies and returns
+/// true if the profile looks bogus and should probably be
+/// ignored.
+#[no_mangle]
+pub extern "C" fn qcms_profile_is_bogus(profile: &mut Profile) -> bool {
+ let mut sum: [f32; 3] = [0.; 3];
+ let mut target: [f32; 3] = [0.; 3];
+ let mut tolerance: [f32; 3] = [0.; 3];
+ let rX: f32;
+ let rY: f32;
+ let rZ: f32;
+ let gX: f32;
+ let gY: f32;
+ let gZ: f32;
+ let bX: f32;
+ let bY: f32;
+ let bZ: f32;
+ let negative: bool;
+ let mut i: u32;
+ // We currently only check the bogosity of RGB profiles
+ if profile.color_space != RGB_SIGNATURE {
+ return false;
+ }
+ if profile.A2B0.is_some()
+ || profile.B2A0.is_some()
+ || profile.mAB.is_some()
+ || profile.mBA.is_some()
+ {
+ return false;
+ }
+ rX = s15Fixed16Number_to_float(profile.redColorant.X);
+ rY = s15Fixed16Number_to_float(profile.redColorant.Y);
+ rZ = s15Fixed16Number_to_float(profile.redColorant.Z);
+ gX = s15Fixed16Number_to_float(profile.greenColorant.X);
+ gY = s15Fixed16Number_to_float(profile.greenColorant.Y);
+ gZ = s15Fixed16Number_to_float(profile.greenColorant.Z);
+ bX = s15Fixed16Number_to_float(profile.blueColorant.X);
+ bY = s15Fixed16Number_to_float(profile.blueColorant.Y);
+ bZ = s15Fixed16Number_to_float(profile.blueColorant.Z);
+ // Sum the values; they should add up to something close to white
+ sum[0] = rX + gX + bX;
+ sum[1] = rY + gY + bY;
+ sum[2] = rZ + gZ + bZ;
+ // Build our target vector (see mozilla bug 460629)
+ target[0] = 0.96420;
+ target[1] = 1.00000;
+ target[2] = 0.82491;
+ // Our tolerance vector - Recommended by Chris Murphy based on
+ // conversion from the LAB space criterion of no more than 3 in any one
+ // channel. This is similar to, but slightly more tolerant than Adobe's
+ // criterion.
+ tolerance[0] = 0.02;
+ tolerance[1] = 0.02;
+ tolerance[2] = 0.04;
+ // Compare with our tolerance
+ i = 0;
+ while i < 3 {
+ if !(sum[i as usize] - tolerance[i as usize] <= target[i as usize]
+ && sum[i as usize] + tolerance[i as usize] >= target[i as usize])
+ {
+ return true;
+ }
+ i += 1
+ }
+ if false {
+ negative = (rX < 0.)
+ || (rY < 0.)
+ || (rZ < 0.)
+ || (gX < 0.)
+ || (gY < 0.)
+ || (gZ < 0.)
+ || (bX < 0.)
+ || (bY < 0.)
+ || (bZ < 0.);
+ } else {
+ // Chromatic adaption to D50 can result in negative XYZ, but the white
+ // point D50 tolerance test has passed. Accept negative values herein.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=498245#c18 onwards
+ // for discussion about whether profile XYZ can or cannot be negative,
+ // per the spec. Also the https://bugzil.la/450923 user report.
+ // Also: https://bugzil.la/1799391 and https://bugzil.la/1792469
+ negative = false; // bogus
+ }
+ if negative {
+ return true;
+ }
+ // All Good
+ false
+}
+
+pub const TAG_bXYZ: u32 = 0x6258595a;
+pub const TAG_gXYZ: u32 = 0x6758595a;
+pub const TAG_rXYZ: u32 = 0x7258595a;
+pub const TAG_rTRC: u32 = 0x72545243;
+pub const TAG_bTRC: u32 = 0x62545243;
+pub const TAG_gTRC: u32 = 0x67545243;
+pub const TAG_kTRC: u32 = 0x6b545243;
+pub const TAG_A2B0: u32 = 0x41324230;
+pub const TAG_B2A0: u32 = 0x42324130;
+pub const TAG_CHAD: u32 = 0x63686164;
+
+fn find_tag(index: &TagIndex, tag_id: u32) -> Option<&Tag> {
+ for t in index {
+ if t.signature == tag_id {
+ return Some(t);
+ }
+ }
+ None
+}
+
+pub const XYZ_TYPE: u32 = 0x58595a20; // 'XYZ '
+pub const CURVE_TYPE: u32 = 0x63757276; // 'curv'
+pub const PARAMETRIC_CURVE_TYPE: u32 = 0x70617261; // 'para'
+pub const LUT16_TYPE: u32 = 0x6d667432; // 'mft2'
+pub const LUT8_TYPE: u32 = 0x6d667431; // 'mft1'
+pub const LUT_MAB_TYPE: u32 = 0x6d414220; // 'mAB '
+pub const LUT_MBA_TYPE: u32 = 0x6d424120; // 'mBA '
+pub const CHROMATIC_TYPE: u32 = 0x73663332; // 'sf32'
+
+fn read_tag_s15Fixed16ArrayType(src: &mut MemSource, tag: &Tag) -> Matrix {
+ let mut matrix: Matrix = Matrix { m: [[0.; 3]; 3] };
+ let offset: u32 = tag.offset;
+ let type_0: u32 = read_u32(src, offset as usize);
+ // Check mandatory type signature for s16Fixed16ArrayType
+ if type_0 != CHROMATIC_TYPE {
+ invalid_source(src, "unexpected type, expected \'sf32\'");
+ }
+ for i in 0..=8 {
+ matrix.m[(i / 3) as usize][(i % 3) as usize] = s15Fixed16Number_to_float(
+ read_s15Fixed16Number(src, (offset + 8 + (i * 4) as u32) as usize),
+ );
+ }
+ matrix
+}
+fn read_tag_XYZType(src: &mut MemSource, index: &TagIndex, tag_id: u32) -> XYZNumber {
+ let mut num = XYZNumber { X: 0, Y: 0, Z: 0 };
+ let tag = find_tag(&index, tag_id);
+ if let Some(tag) = tag {
+ let offset: u32 = tag.offset;
+ let type_0: u32 = read_u32(src, offset as usize);
+ if type_0 != XYZ_TYPE {
+ invalid_source(src, "unexpected type, expected XYZ");
+ }
+ num.X = read_s15Fixed16Number(src, (offset + 8) as usize);
+ num.Y = read_s15Fixed16Number(src, (offset + 12) as usize);
+ num.Z = read_s15Fixed16Number(src, (offset + 16) as usize)
+ } else {
+ invalid_source(src, "missing xyztag");
+ }
+ num
+}
+// Read the tag at a given offset rather then the tag_index.
+// This method is used when reading mAB tags where nested curveType are
+// present that are not part of the tag_index.
+fn read_curveType(src: &mut MemSource, offset: u32, len: &mut u32) -> Option<Box<curveType>> {
+ const COUNT_TO_LENGTH: [u32; 5] = [1, 3, 4, 5, 7]; //PARAMETRIC_CURVE_TYPE
+ let type_0: u32 = read_u32(src, offset as usize);
+ let count: u32;
+ if type_0 != CURVE_TYPE && type_0 != PARAMETRIC_CURVE_TYPE {
+ invalid_source(src, "unexpected type, expected CURV or PARA");
+ return None;
+ }
+ if type_0 == CURVE_TYPE {
+ count = read_u32(src, (offset + 8) as usize);
+ //arbitrary
+ if count > 40000 {
+ invalid_source(src, "curve size too large");
+ return None;
+ }
+ let mut table = Vec::with_capacity(count as usize);
+ for i in 0..count {
+ table.push(read_u16(src, (offset + 12 + i * 2) as usize));
+ }
+ *len = 12 + count * 2;
+ Some(Box::new(curveType::Curve(table)))
+ } else {
+ count = read_u16(src, (offset + 8) as usize) as u32;
+ if count > 4 {
+ invalid_source(src, "parametric function type not supported.");
+ return None;
+ }
+ let mut params = Vec::with_capacity(count as usize);
+ for i in 0..COUNT_TO_LENGTH[count as usize] {
+ params.push(s15Fixed16Number_to_float(read_s15Fixed16Number(
+ src,
+ (offset + 12 + i * 4) as usize,
+ )));
+ }
+ *len = 12 + COUNT_TO_LENGTH[count as usize] * 4;
+ if count == 1 || count == 2 {
+ /* we have a type 1 or type 2 function that has a division by 'a' */
+ let a: f32 = params[1];
+ if a == 0.0 {
+ invalid_source(src, "parametricCurve definition causes division by zero");
+ }
+ }
+ Some(Box::new(curveType::Parametric(params)))
+ }
+}
+fn read_tag_curveType(
+ src: &mut MemSource,
+ index: &TagIndex,
+ tag_id: u32,
+) -> Option<Box<curveType>> {
+ let tag = find_tag(index, tag_id);
+ if let Some(tag) = tag {
+ let mut len: u32 = 0;
+ return read_curveType(src, tag.offset, &mut len);
+ } else {
+ invalid_source(src, "missing curvetag");
+ }
+ None
+}
+
+const MAX_LUT_SIZE: u32 = 500000; // arbitrary
+const MAX_CHANNELS: usize = 10; // arbitrary
+fn read_nested_curveType(
+ src: &mut MemSource,
+ curveArray: &mut [Option<Box<curveType>>; MAX_CHANNELS],
+ num_channels: u8,
+ curve_offset: u32,
+) {
+ let mut channel_offset: u32 = 0;
+ #[allow(clippy::needless_range_loop)]
+ for i in 0..usize::from(num_channels) {
+ let mut tag_len: u32 = 0;
+ curveArray[i] = read_curveType(src, curve_offset + channel_offset, &mut tag_len);
+ if curveArray[i].is_none() {
+ invalid_source(src, "invalid nested curveType curve");
+ break;
+ } else {
+ channel_offset += tag_len;
+ // 4 byte aligned
+ if tag_len % 4 != 0 {
+ channel_offset += 4 - tag_len % 4
+ }
+ }
+ }
+}
+
+/* See section 10.10 for specs */
+fn read_tag_lutmABType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutmABType>> {
+ let offset: u32 = tag.offset;
+ let mut clut_size: u32 = 1;
+ let type_0: u32 = read_u32(src, offset as usize);
+ if type_0 != LUT_MAB_TYPE && type_0 != LUT_MBA_TYPE {
+ return None;
+ }
+ let num_in_channels = read_u8(src, (offset + 8) as usize);
+ let num_out_channels = read_u8(src, (offset + 9) as usize);
+ if num_in_channels > 10 || num_out_channels > 10 {
+ return None;
+ }
+ // We require 3in/out channels since we only support RGB->XYZ (or RGB->LAB)
+ // XXX: If we remove this restriction make sure that the number of channels
+ // is less or equal to the maximum number of mAB curves in qcmsint.h
+ // also check for clut_size overflow. Also make sure it's != 0
+ if num_in_channels != 3 || num_out_channels != 3 {
+ return None;
+ }
+ // some of this data is optional and is denoted by a zero offset
+ // we also use this to track their existance
+ let mut a_curve_offset = read_u32(src, (offset + 28) as usize);
+ let mut clut_offset = read_u32(src, (offset + 24) as usize);
+ let mut m_curve_offset = read_u32(src, (offset + 20) as usize);
+ let mut matrix_offset = read_u32(src, (offset + 16) as usize);
+ let mut b_curve_offset = read_u32(src, (offset + 12) as usize);
+ // Convert offsets relative to the tag to relative to the profile
+ // preserve zero for optional fields
+ if a_curve_offset != 0 {
+ a_curve_offset += offset
+ }
+ if clut_offset != 0 {
+ clut_offset += offset
+ }
+ if m_curve_offset != 0 {
+ m_curve_offset += offset
+ }
+ if matrix_offset != 0 {
+ matrix_offset += offset
+ }
+ if b_curve_offset != 0 {
+ b_curve_offset += offset
+ }
+ if clut_offset != 0 {
+ debug_assert!(num_in_channels == 3);
+ // clut_size can not overflow since lg(256^num_in_channels) = 24 bits.
+ for i in 0..u32::from(num_in_channels) {
+ clut_size *= read_u8(src, (clut_offset + i) as usize) as u32;
+ if clut_size == 0 {
+ invalid_source(src, "bad clut_size");
+ }
+ }
+ } else {
+ clut_size = 0
+ }
+ // 24bits * 3 won't overflow either
+ clut_size *= num_out_channels as u32;
+ if clut_size > MAX_LUT_SIZE {
+ return None;
+ }
+
+ let mut lut = Box::new(lutmABType::default());
+
+ if clut_offset != 0 {
+ for i in 0..usize::from(num_in_channels) {
+ lut.num_grid_points[i] = read_u8(src, clut_offset as usize + i);
+ if lut.num_grid_points[i] == 0 {
+ invalid_source(src, "bad grid_points");
+ }
+ }
+ }
+ // Reverse the processing of transformation elements for mBA type.
+ lut.reversed = type_0 == LUT_MBA_TYPE;
+ lut.num_in_channels = num_in_channels;
+ lut.num_out_channels = num_out_channels;
+ #[allow(clippy::identity_op, clippy::erasing_op)]
+ if matrix_offset != 0 {
+ // read the matrix if we have it
+ lut.e00 = read_s15Fixed16Number(src, (matrix_offset + (4 * 0) as u32) as usize); // the caller checks that this doesn't happen
+ lut.e01 = read_s15Fixed16Number(src, (matrix_offset + (4 * 1) as u32) as usize);
+ lut.e02 = read_s15Fixed16Number(src, (matrix_offset + (4 * 2) as u32) as usize);
+ lut.e10 = read_s15Fixed16Number(src, (matrix_offset + (4 * 3) as u32) as usize);
+ lut.e11 = read_s15Fixed16Number(src, (matrix_offset + (4 * 4) as u32) as usize);
+ lut.e12 = read_s15Fixed16Number(src, (matrix_offset + (4 * 5) as u32) as usize);
+ lut.e20 = read_s15Fixed16Number(src, (matrix_offset + (4 * 6) as u32) as usize);
+ lut.e21 = read_s15Fixed16Number(src, (matrix_offset + (4 * 7) as u32) as usize);
+ lut.e22 = read_s15Fixed16Number(src, (matrix_offset + (4 * 8) as u32) as usize);
+ lut.e03 = read_s15Fixed16Number(src, (matrix_offset + (4 * 9) as u32) as usize);
+ lut.e13 = read_s15Fixed16Number(src, (matrix_offset + (4 * 10) as u32) as usize);
+ lut.e23 = read_s15Fixed16Number(src, (matrix_offset + (4 * 11) as u32) as usize)
+ }
+ if a_curve_offset != 0 {
+ read_nested_curveType(src, &mut lut.a_curves, num_in_channels, a_curve_offset);
+ }
+ if m_curve_offset != 0 {
+ read_nested_curveType(src, &mut lut.m_curves, num_out_channels, m_curve_offset);
+ }
+ if b_curve_offset != 0 {
+ read_nested_curveType(src, &mut lut.b_curves, num_out_channels, b_curve_offset);
+ } else {
+ invalid_source(src, "B curves required");
+ }
+ if clut_offset != 0 {
+ let clut_precision = read_u8(src, (clut_offset + 16) as usize);
+ let mut clut_table = Vec::with_capacity(clut_size as usize);
+ if clut_precision == 1 {
+ for i in 0..clut_size {
+ clut_table.push(uInt8Number_to_float(read_uInt8Number(
+ src,
+ (clut_offset + 20 + i) as usize,
+ )));
+ }
+ lut.clut_table = Some(clut_table);
+ } else if clut_precision == 2 {
+ for i in 0..clut_size {
+ clut_table.push(uInt16Number_to_float(read_uInt16Number(
+ src,
+ (clut_offset + 20 + i * 2) as usize,
+ )));
+ }
+ lut.clut_table = Some(clut_table);
+ } else {
+ invalid_source(src, "Invalid clut precision");
+ }
+ }
+ if !src.valid {
+ return None;
+ }
+ Some(lut)
+}
+fn read_tag_lutType(src: &mut MemSource, tag: &Tag) -> Option<Box<lutType>> {
+ let offset: u32 = tag.offset;
+ let type_0: u32 = read_u32(src, offset as usize);
+ let num_input_table_entries: u16;
+ let num_output_table_entries: u16;
+ let input_offset: u32;
+ let entry_size: usize;
+ if type_0 == LUT8_TYPE {
+ num_input_table_entries = 256u16;
+ num_output_table_entries = 256u16;
+ entry_size = 1;
+ input_offset = 48
+ } else if type_0 == LUT16_TYPE {
+ num_input_table_entries = read_u16(src, (offset + 48) as usize);
+ num_output_table_entries = read_u16(src, (offset + 50) as usize);
+
+ // these limits come from the spec
+ if !(2..=4096).contains(&num_input_table_entries)
+ || !(2..=4096).contains(&num_output_table_entries)
+ {
+ invalid_source(src, "Bad channel count");
+ return None;
+ }
+ entry_size = 2;
+ input_offset = 52
+ } else {
+ debug_assert!(false);
+ invalid_source(src, "Unexpected lut type");
+ return None;
+ }
+ let in_chan = read_u8(src, (offset + 8) as usize);
+ let out_chan = read_u8(src, (offset + 9) as usize);
+ if !(in_chan == 3 || in_chan == 4) || out_chan != 3 {
+ invalid_source(src, "CLUT only supports RGB and CMYK");
+ return None;
+ }
+
+ let grid_points = read_u8(src, (offset + 10) as usize);
+ let clut_size = match (grid_points as u32).checked_pow(in_chan as u32) {
+ Some(clut_size) => clut_size,
+ _ => {
+ invalid_source(src, "CLUT size overflow");
+ return None;
+ }
+ };
+ match clut_size {
+ 1..=MAX_LUT_SIZE => {} // OK
+ 0 => {
+ invalid_source(src, "CLUT must not be empty.");
+ return None;
+ }
+ _ => {
+ invalid_source(src, "CLUT too large");
+ return None;
+ }
+ }
+
+ let e00 = read_s15Fixed16Number(src, (offset + 12) as usize);
+ let e01 = read_s15Fixed16Number(src, (offset + 16) as usize);
+ let e02 = read_s15Fixed16Number(src, (offset + 20) as usize);
+ let e10 = read_s15Fixed16Number(src, (offset + 24) as usize);
+ let e11 = read_s15Fixed16Number(src, (offset + 28) as usize);
+ let e12 = read_s15Fixed16Number(src, (offset + 32) as usize);
+ let e20 = read_s15Fixed16Number(src, (offset + 36) as usize);
+ let e21 = read_s15Fixed16Number(src, (offset + 40) as usize);
+ let e22 = read_s15Fixed16Number(src, (offset + 44) as usize);
+
+ let mut input_table = Vec::with_capacity((num_input_table_entries * in_chan as u16) as usize);
+ for i in 0..(num_input_table_entries * in_chan as u16) {
+ if type_0 == LUT8_TYPE {
+ input_table.push(uInt8Number_to_float(read_uInt8Number(
+ src,
+ (offset + input_offset) as usize + i as usize * entry_size,
+ )))
+ } else {
+ input_table.push(uInt16Number_to_float(read_uInt16Number(
+ src,
+ (offset + input_offset) as usize + i as usize * entry_size,
+ )))
+ }
+ }
+ let clut_offset = ((offset + input_offset) as usize
+ + (num_input_table_entries as i32 * in_chan as i32) as usize * entry_size)
+ as u32;
+
+ let mut clut_table = Vec::with_capacity((clut_size * out_chan as u32) as usize);
+ for i in 0..clut_size * out_chan as u32 {
+ if type_0 == LUT8_TYPE {
+ clut_table.push(uInt8Number_to_float(read_uInt8Number(
+ src,
+ clut_offset as usize + i as usize * entry_size,
+ )));
+ } else if type_0 == LUT16_TYPE {
+ clut_table.push(uInt16Number_to_float(read_uInt16Number(
+ src,
+ clut_offset as usize + i as usize * entry_size,
+ )));
+ }
+ }
+
+ let output_offset =
+ (clut_offset as usize + (clut_size * out_chan as u32) as usize * entry_size) as u32;
+
+ let mut output_table =
+ Vec::with_capacity((num_output_table_entries * out_chan as u16) as usize);
+ for i in 0..num_output_table_entries as i32 * out_chan as i32 {
+ if type_0 == LUT8_TYPE {
+ output_table.push(uInt8Number_to_float(read_uInt8Number(
+ src,
+ output_offset as usize + i as usize * entry_size,
+ )))
+ } else {
+ output_table.push(uInt16Number_to_float(read_uInt16Number(
+ src,
+ output_offset as usize + i as usize * entry_size,
+ )))
+ }
+ }
+ Some(Box::new(lutType {
+ num_input_table_entries,
+ num_output_table_entries,
+ num_input_channels: in_chan,
+ num_output_channels: out_chan,
+ num_clut_grid_points: grid_points,
+ e00,
+ e01,
+ e02,
+ e10,
+ e11,
+ e12,
+ e20,
+ e21,
+ e22,
+ input_table,
+ clut_table,
+ output_table,
+ }))
+}
+fn read_rendering_intent(profile: &mut Profile, src: &mut MemSource) {
+ let intent = read_u32(src, 64);
+ profile.rendering_intent = match intent {
+ x if x == Perceptual as u32 => Perceptual,
+ x if x == RelativeColorimetric as u32 => RelativeColorimetric,
+ x if x == Saturation as u32 => Saturation,
+ x if x == AbsoluteColorimetric as u32 => AbsoluteColorimetric,
+ _ => {
+ invalid_source(src, "unknown rendering intent");
+ Intent::default()
+ }
+ };
+}
+fn profile_create() -> Box<Profile> {
+ Box::new(Profile::default())
+}
+/* build sRGB gamma table */
+/* based on cmsBuildParametricGamma() */
+#[allow(clippy::many_single_char_names)]
+fn build_sRGB_gamma_table(num_entries: i32) -> Vec<u16> {
+ /* taken from lcms: Build_sRGBGamma() */
+ let gamma: f64 = 2.4;
+ let a: f64 = 1.0 / 1.055;
+ let b: f64 = 0.055 / 1.055;
+ let c: f64 = 1.0 / 12.92;
+ let d: f64 = 0.04045;
+
+ build_trc_table(
+ num_entries,
+ // IEC 61966-2.1 (sRGB)
+ // Y = (aX + b)^Gamma | X >= d
+ // Y = cX | X < d
+ |x| {
+ if x >= d {
+ let e: f64 = a * x + b;
+ if e > 0. {
+ e.powf(gamma)
+ } else {
+ 0.
+ }
+ } else {
+ c * x
+ }
+ },
+ )
+}
+
+/// eotf: electro-optical transfer characteristic function, maps from [0, 1]
+/// in non-linear (voltage) space to [0, 1] in linear (optical) space. Should
+/// generally be a concave up function.
+fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> {
+ let mut table = Vec::with_capacity(num_entries as usize);
+
+ for i in 0..num_entries {
+ let x: f64 = i as f64 / (num_entries - 1) as f64;
+ let y: f64 = eotf(x);
+ let mut output: f64;
+ // Saturate -- this could likely move to a separate function
+ output = y * 65535.0 + 0.5;
+ if output > 65535.0 {
+ output = 65535.0
+ }
+ if output < 0.0 {
+ output = 0.0
+ }
+ table.push(output.floor() as u16);
+ }
+ table
+}
+fn curve_from_table(table: &[u16]) -> Box<curveType> {
+ Box::new(curveType::Curve(table.to_vec()))
+}
+pub fn float_to_u8Fixed8Number(a: f32) -> u16 {
+ if a > 255.0 + 255.0 / 256f32 {
+ 0xffffu16
+ } else if a < 0.0 {
+ 0u16
+ } else {
+ (a * 256.0 + 0.5).floor() as u16
+ }
+}
+
+fn curve_from_gamma(gamma: f32) -> Box<curveType> {
+ Box::new(curveType::Curve(vec![float_to_u8Fixed8Number(gamma)]))
+}
+
+fn identity_curve() -> Box<curveType> {
+ Box::new(curveType::Curve(Vec::new()))
+}
+
+/* from lcms: cmsWhitePointFromTemp */
+/* tempK must be >= 4000. and <= 25000.
+ * Invalid values of tempK will return
+ * (x,y,Y) = (-1.0, -1.0, -1.0)
+ * similar to argyll: icx_DTEMP2XYZ() */
+fn white_point_from_temp(temp_K: i32) -> qcms_CIE_xyY {
+ let mut white_point: qcms_CIE_xyY = qcms_CIE_xyY {
+ x: 0.,
+ y: 0.,
+ Y: 0.,
+ };
+ // No optimization provided.
+ let T = temp_K as f64; // Square
+ let T2 = T * T; // Cube
+ let T3 = T2 * T;
+ // For correlated color temperature (T) between 4000K and 7000K:
+ let x = if (4000.0..=7000.0).contains(&T) {
+ -4.6070 * (1E9 / T3) + 2.9678 * (1E6 / T2) + 0.09911 * (1E3 / T) + 0.244063
+ } else if T > 7000.0 && T <= 25000.0 {
+ -2.0064 * (1E9 / T3) + 1.9018 * (1E6 / T2) + 0.24748 * (1E3 / T) + 0.237040
+ } else {
+ // or for correlated color temperature (T) between 7000K and 25000K:
+ // Invalid tempK
+ white_point.x = -1.0;
+ white_point.y = -1.0;
+ white_point.Y = -1.0;
+ debug_assert!(false, "invalid temp");
+ return white_point;
+ };
+ // Obtain y(x)
+ let y = -3.000 * (x * x) + 2.870 * x - 0.275;
+ // wave factors (not used, but here for futures extensions)
+ // let M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y);
+ // let M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y);
+ // Fill white_point struct
+ white_point.x = x;
+ white_point.y = y;
+ white_point.Y = 1.0;
+ white_point
+}
+#[no_mangle]
+pub extern "C" fn qcms_white_point_sRGB() -> qcms_CIE_xyY {
+ white_point_from_temp(6504)
+}
+
+/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 2
+/// Values 0, 3, 13–21, 23–255 are all reserved so all map to the same variant
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ColourPrimaries {
+ /// For future use by ITU-T | ISO/IEC
+ Reserved,
+ /// Rec. ITU-R BT.709-6<br />
+ /// Rec. ITU-R BT.1361-0 conventional colour gamut system and extended colour gamut system (historical)<br />
+ /// IEC 61966-2-1 sRGB or sYCC IEC 61966-2-4<br />
+ /// Society of Motion Picture and Television Engineers (MPTE) RP 177 (1993) Annex B<br />
+ Bt709 = 1,
+ /// Unspecified<br />
+ /// Image characteristics are unknown or are determined by the application.
+ Unspecified = 2,
+ /// Rec. ITU-R BT.470-6 System M (historical)<br />
+ /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
+ /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
+ Bt470M = 4,
+ /// Rec. ITU-R BT.470-6 System B, G (historical) Rec. ITU-R BT.601-7 625<br />
+ /// Rec. ITU-R BT.1358-0 625 (historical)<br />
+ /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
+ Bt470Bg = 5,
+ /// Rec. ITU-R BT.601-7 525<br />
+ /// Rec. ITU-R BT.1358-1 525 or 625 (historical) Rec. ITU-R BT.1700-0 NTSC<br />
+ /// SMPTE 170M (2004)<br />
+ /// (functionally the same as the value 7)<br />
+ Bt601 = 6,
+ /// SMPTE 240M (1999) (historical) (functionally the same as the value 6)<br />
+ Smpte240 = 7,
+ /// Generic film (colour filters using Illuminant C)<br />
+ Generic_film = 8,
+ /// Rec. ITU-R BT.2020-2<br />
+ /// Rec. ITU-R BT.2100-0<br />
+ Bt2020 = 9,
+ /// SMPTE ST 428-1<br />
+ /// (CIE 1931 XYZ as in ISO 11664-1)<br />
+ Xyz = 10,
+ /// SMPTE RP 431-2 (2011)<br />
+ Smpte431 = 11,
+ /// SMPTE EG 432-1 (2010)<br />
+ Smpte432 = 12,
+ /// EBU Tech. 3213-E (1975)<br />
+ Ebu3213 = 22,
+}
+
+impl From<u8> for ColourPrimaries {
+ fn from(value: u8) -> Self {
+ match value {
+ 0 | 3 | 13..=21 | 23..=255 => Self::Reserved,
+ 1 => Self::Bt709,
+ 2 => Self::Unspecified,
+ 4 => Self::Bt470M,
+ 5 => Self::Bt470Bg,
+ 6 => Self::Bt601,
+ 7 => Self::Smpte240,
+ 8 => Self::Generic_film,
+ 9 => Self::Bt2020,
+ 10 => Self::Xyz,
+ 11 => Self::Smpte431,
+ 12 => Self::Smpte432,
+ 22 => Self::Ebu3213,
+ }
+ }
+}
+
+#[test]
+fn colour_primaries() {
+ for value in 0..=u8::MAX {
+ match ColourPrimaries::from(value) {
+ ColourPrimaries::Reserved => {}
+ variant => assert_eq!(value, variant as u8),
+ }
+ }
+}
+
+impl From<ColourPrimaries> for qcms_CIE_xyYTRIPLE {
+ fn from(value: ColourPrimaries) -> Self {
+ let red;
+ let green;
+ let blue;
+
+ match value {
+ ColourPrimaries::Reserved => panic!("CP={} is reserved", value as u8),
+ ColourPrimaries::Bt709 => {
+ green = qcms_chromaticity { x: 0.300, y: 0.600 };
+ blue = qcms_chromaticity { x: 0.150, y: 0.060 };
+ red = qcms_chromaticity { x: 0.640, y: 0.330 };
+ }
+ ColourPrimaries::Unspecified => panic!("CP={} is unspecified", value as u8),
+ ColourPrimaries::Bt470M => {
+ green = qcms_chromaticity { x: 0.21, y: 0.71 };
+ blue = qcms_chromaticity { x: 0.14, y: 0.08 };
+ red = qcms_chromaticity { x: 0.67, y: 0.33 };
+ }
+ ColourPrimaries::Bt470Bg => {
+ green = qcms_chromaticity { x: 0.29, y: 0.60 };
+ blue = qcms_chromaticity { x: 0.15, y: 0.06 };
+ red = qcms_chromaticity { x: 0.64, y: 0.33 };
+ }
+ ColourPrimaries::Bt601 | ColourPrimaries::Smpte240 => {
+ green = qcms_chromaticity { x: 0.310, y: 0.595 };
+ blue = qcms_chromaticity { x: 0.155, y: 0.070 };
+ red = qcms_chromaticity { x: 0.630, y: 0.340 };
+ }
+ ColourPrimaries::Generic_film => {
+ green = qcms_chromaticity { x: 0.243, y: 0.692 };
+ blue = qcms_chromaticity { x: 0.145, y: 0.049 };
+ red = qcms_chromaticity { x: 0.681, y: 0.319 };
+ }
+ ColourPrimaries::Bt2020 => {
+ green = qcms_chromaticity { x: 0.170, y: 0.797 };
+ blue = qcms_chromaticity { x: 0.131, y: 0.046 };
+ red = qcms_chromaticity { x: 0.708, y: 0.292 };
+ }
+ ColourPrimaries::Xyz => {
+ green = qcms_chromaticity { x: 0.0, y: 1.0 };
+ blue = qcms_chromaticity { x: 0.0, y: 0.0 };
+ red = qcms_chromaticity { x: 1.0, y: 0.0 };
+ }
+ // These two share primaries, but have distinct white points
+ ColourPrimaries::Smpte431 | ColourPrimaries::Smpte432 => {
+ green = qcms_chromaticity { x: 0.265, y: 0.690 };
+ blue = qcms_chromaticity { x: 0.150, y: 0.060 };
+ red = qcms_chromaticity { x: 0.680, y: 0.320 };
+ }
+ ColourPrimaries::Ebu3213 => {
+ green = qcms_chromaticity { x: 0.295, y: 0.605 };
+ blue = qcms_chromaticity { x: 0.155, y: 0.077 };
+ red = qcms_chromaticity { x: 0.630, y: 0.340 };
+ }
+ }
+
+ Self {
+ red: red.into(),
+ green: green.into(),
+ blue: blue.into(),
+ }
+ }
+}
+
+impl ColourPrimaries {
+ fn white_point(self) -> qcms_CIE_xyY {
+ match self {
+ Self::Reserved => panic!("CP={} is reserved", self as u8),
+ Self::Bt709
+ | Self::Bt470Bg
+ | Self::Bt601
+ | Self::Smpte240
+ | Self::Bt2020
+ | Self::Smpte432
+ | Self::Ebu3213 => qcms_chromaticity::D65,
+ Self::Unspecified => panic!("CP={} is unspecified", self as u8),
+ Self::Bt470M => qcms_chromaticity { x: 0.310, y: 0.316 },
+ Self::Generic_film => qcms_chromaticity { x: 0.310, y: 0.316 },
+ Self::Xyz => qcms_chromaticity {
+ x: 1. / 3.,
+ y: 1. / 3.,
+ },
+ Self::Smpte431 => qcms_chromaticity { x: 0.314, y: 0.351 },
+ }
+ .into()
+ }
+}
+
+/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 3
+/// Values 0, 3, 19–255 are all reserved so all map to the same variant
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum TransferCharacteristics {
+ /// For future use by ITU-T | ISO/IEC
+ Reserved,
+ /// Rec. ITU-R BT.709-6<br />
+ /// Rec. ITU-R BT.1361-0 conventional colour gamut system (historical)<br />
+ /// (functionally the same as the values 6, 14 and 15) <br />
+ Bt709 = 1,
+ /// Image characteristics are unknown or are determined by the application.<br />
+ Unspecified = 2,
+ /// Rec. ITU-R BT.470-6 System M (historical)<br />
+ /// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
+ /// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
+ /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
+ Bt470M = 4,
+ /// Rec. ITU-R BT.470-6 System B, G (historical)<br />
+ Bt470Bg = 5,
+ /// Rec. ITU-R BT.601-7 525 or 625<br />
+ /// Rec. ITU-R BT.1358-1 525 or 625 (historical)<br />
+ /// Rec. ITU-R BT.1700-0 NTSC SMPTE 170M (2004)<br />
+ /// (functionally the same as the values 1, 14 and 15)<br />
+ Bt601 = 6,
+ /// SMPTE 240M (1999) (historical)<br />
+ Smpte240 = 7,
+ /// Linear transfer characteristics<br />
+ Linear = 8,
+ /// Logarithmic transfer characteristic (100:1 range)<br />
+ Log_100 = 9,
+ /// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range)<br />
+ Log_100_sqrt10 = 10,
+ /// IEC 61966-2-4<br />
+ Iec61966 = 11,
+ /// Rec. ITU-R BT.1361-0 extended colour gamut system (historical)<br />
+ Bt_1361 = 12,
+ /// IEC 61966-2-1 sRGB or sYCC<br />
+ Srgb = 13,
+ /// Rec. ITU-R BT.2020-2 (10-bit system)<br />
+ /// (functionally the same as the values 1, 6 and 15)<br />
+ Bt2020_10bit = 14,
+ /// Rec. ITU-R BT.2020-2 (12-bit system)<br />
+ /// (functionally the same as the values 1, 6 and 14)<br />
+ Bt2020_12bit = 15,
+ /// SMPTE ST 2084 for 10-, 12-, 14- and 16-bitsystems<br />
+ /// Rec. ITU-R BT.2100-0 perceptual quantization (PQ) system<br />
+ Smpte2084 = 16,
+ /// SMPTE ST 428-1<br />
+ Smpte428 = 17,
+ /// ARIB STD-B67<br />
+ /// Rec. ITU-R BT.2100-0 hybrid log- gamma (HLG) system<br />
+ Hlg = 18,
+}
+
+#[test]
+fn transfer_characteristics() {
+ for value in 0..=u8::MAX {
+ match TransferCharacteristics::from(value) {
+ TransferCharacteristics::Reserved => {}
+ variant => assert_eq!(value, variant as u8),
+ }
+ }
+}
+
+impl From<u8> for TransferCharacteristics {
+ fn from(value: u8) -> Self {
+ match value {
+ 0 | 3 | 19..=255 => Self::Reserved,
+ 1 => Self::Bt709,
+ 2 => Self::Unspecified,
+ 4 => Self::Bt470M,
+ 5 => Self::Bt470Bg,
+ 6 => Self::Bt601,
+ 7 => Self::Smpte240, // unimplemented
+ 8 => Self::Linear,
+ 9 => Self::Log_100,
+ 10 => Self::Log_100_sqrt10,
+ 11 => Self::Iec61966, // unimplemented
+ 12 => Self::Bt_1361, // unimplemented
+ 13 => Self::Srgb,
+ 14 => Self::Bt2020_10bit,
+ 15 => Self::Bt2020_12bit,
+ 16 => Self::Smpte2084,
+ 17 => Self::Smpte428, // unimplemented
+ 18 => Self::Hlg,
+ }
+ }
+}
+
+impl TryFrom<TransferCharacteristics> for curveType {
+ type Error = ();
+ /// See [ICC.1:2010](https://www.color.org/specification/ICC1v43_2010-12.pdf)
+ /// See [Rec. ITU-R BT.2100-2](https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf)
+ fn try_from(value: TransferCharacteristics) -> Result<Self, Self::Error> {
+ const NUM_TRC_TABLE_ENTRIES: i32 = 1024;
+
+ Ok(match value {
+ TransferCharacteristics::Reserved => panic!("TC={} is reserved", value as u8),
+ TransferCharacteristics::Bt709
+ | TransferCharacteristics::Bt601
+ | TransferCharacteristics::Bt2020_10bit
+ | TransferCharacteristics::Bt2020_12bit => {
+ // The opto-electronic transfer characteristic function (OETF)
+ // as defined in ITU-T H.273 table 3, row 1:
+ //
+ // V = (α * Lc^0.45) − (α − 1) for 1 >= Lc >= β
+ // V = 4.500 * Lc for β > Lc >= 0
+ //
+ // Inverting gives the electro-optical transfer characteristic
+ // function (EOTF) which can be represented as ICC
+ // parametricCurveType with 4 parameters (ICC.1:2010 Table 5).
+ // Converting between the two (Lc ↔︎ Y, V ↔︎ X):
+ //
+ // Y = (a * X + b)^g for (X >= d)
+ // Y = c * X for (X < d)
+ //
+ // g, a, b, c, d can then be defined in terms of α and β:
+ //
+ // g = 1 / 0.45
+ // a = 1 / α
+ // b = 1 - α
+ // c = 1 / 4.500
+ // d = 4.500 * β
+ //
+ // α and β are determined by solving the piecewise equations to
+ // ensure continuity of both value and slope at the value β.
+ // We use the values specified for 10-bit systems in
+ // https://www.itu.int/rec/R-REC-BT.2020-2-201510-I Table 4
+ // since this results in the similar values as available ICC
+ // profiles after converting to s15Fixed16Number, providing us
+ // good test coverage.
+
+ type Float = f32;
+
+ const alpha: Float = 1.099;
+ const beta: Float = 0.018;
+
+ const linear_coef: Float = 4.500;
+ const pow_exp: Float = 0.45;
+
+ const g: Float = 1. / pow_exp;
+ const a: Float = 1. / alpha;
+ const b: Float = 1. - a;
+ const c: Float = 1. / linear_coef;
+ const d: Float = linear_coef * beta;
+
+ curveType::Parametric(vec![g, a, b, c, d])
+ }
+ TransferCharacteristics::Unspecified => panic!("TC={} is unspecified", value as u8),
+ TransferCharacteristics::Bt470M => *curve_from_gamma(2.2),
+ TransferCharacteristics::Bt470Bg => *curve_from_gamma(2.8),
+ TransferCharacteristics::Smpte240 => return Err(()),
+ TransferCharacteristics::Linear => *curve_from_gamma(1.),
+ TransferCharacteristics::Log_100 => {
+ // See log_100_transfer_characteristics() for derivation
+ // The opto-electronic transfer characteristic function (OETF)
+ // as defined in ITU-T H.273 table 3, row 9:
+ //
+ // V = 1.0 + Log10(Lc) ÷ 2 for 1 >= Lc >= 0.01
+ // V = 0.0 for 0.01 > Lc >= 0
+ //
+ // Inverting this to give the EOTF required for the profile gives
+ //
+ // Lc = 10^(2*V - 2) for 1 >= V >= 0
+ let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2. * v - 2.));
+ curveType::Curve(table)
+ }
+ TransferCharacteristics::Log_100_sqrt10 => {
+ // The opto-electronic transfer characteristic function (OETF)
+ // as defined in ITU-T H.273 table 3, row 10:
+ //
+ // V = 1.0 + Log10(Lc) ÷ 2.5 for 1 >= Lc >= Sqrt(10) ÷ 1000
+ // V = 0.0 for Sqrt(10) ÷ 1000 > Lc >= 0
+ //
+ // Inverting this to give the EOTF required for the profile gives
+ //
+ // Lc = 10^(2.5*V - 2.5) for 1 >= V >= 0
+ let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |v| 10f64.powf(2.5 * v - 2.5));
+ curveType::Curve(table)
+ }
+ TransferCharacteristics::Iec61966 => return Err(()),
+ TransferCharacteristics::Bt_1361 => return Err(()),
+ TransferCharacteristics::Srgb => {
+ // Should we prefer this or curveType::Parametric?
+ curveType::Curve(build_sRGB_gamma_table(NUM_TRC_TABLE_ENTRIES))
+ }
+
+ TransferCharacteristics::Smpte2084 => {
+ // Despite using Lo rather than Lc, H.273 gives the OETF:
+ //
+ // V = ( ( c1 + c2 * (Lo)^n ) ÷ ( 1 + c3 * (Lo)^n ) )^m
+ const c1: f64 = 0.8359375;
+ const c2: f64 = 18.8515625;
+ const c3: f64 = 18.6875;
+ const m: f64 = 78.84375;
+ const n: f64 = 0.1593017578125;
+
+ // Inverting this to give the EOTF required for the profile
+ // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives
+ //
+ // Y = ( max[( X^(1/m) - c1 ), 0] ÷ ( c2 - c3 * X^(1/m) ) )^(1/n)
+ let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| {
+ ((x.powf(1. / m) - c1).max(0.) / (c2 - c3 * x.powf(1. / m))).powf(1. / n)
+ });
+ curveType::Curve(table)
+ }
+ TransferCharacteristics::Smpte428 => return Err(()),
+ TransferCharacteristics::Hlg => {
+ // The opto-electronic transfer characteristic function (OETF)
+ // as defined in ITU-T H.273 table 3, row 18:
+ //
+ // V = a * Ln(12 * Lc - b) + c for 1 >= Lc > 1 ÷ 12
+ // V = Sqrt(3) * Lc^0.5 for 1 ÷ 12 >= Lc >= 0
+ const a: f64 = 0.17883277;
+ const b: f64 = 0.28466892;
+ const c: f64 = 0.55991073;
+
+ // Inverting this to give the EOTF required for the profile
+ // (and confirmed by Rec. ITU-R BT.2100-2, Table 4) gives
+ //
+ // Y = (X^2) / 3 for 0 <= X <= 0.5
+ // Y = ((e^((X-c)/a))+b)/12 for 0.5 < X <= 1
+ let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, |x| {
+ if x <= 0.5 {
+ let y1 = x.powf(2.) / 3.;
+ assert!((0. ..=1. / 12.).contains(&y1));
+ y1
+ } else {
+ (std::f64::consts::E.powf((x - c) / a) + b) / 12.
+ }
+ });
+ curveType::Curve(table)
+ }
+ })
+ }
+}
+
+#[cfg(test)]
+fn check_transfer_characteristics(cicp: TransferCharacteristics, icc_path: &str) {
+ let mut cicp_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE];
+ let mut icc_out = [0u8; crate::transform::PRECACHE_OUTPUT_SIZE];
+ let cicp_tc = curveType::try_from(cicp).unwrap();
+ let icc = Profile::new_from_path(icc_path).unwrap();
+ let icc_tc = icc.redTRC.as_ref().unwrap();
+
+ eprintln!("cicp_tc: {:?}", cicp_tc);
+ eprintln!("icc_tc: {:?}", icc_tc);
+
+ crate::transform_util::compute_precache(icc_tc, &mut icc_out);
+ crate::transform_util::compute_precache(&cicp_tc, &mut cicp_out);
+
+ let mut off_by_one = 0;
+ for i in 0..cicp_out.len() {
+ match (cicp_out[i] as i16) - (icc_out[i] as i16) {
+ 0 => {}
+ 1 | -1 => {
+ off_by_one += 1;
+ }
+ _ => assert_eq!(cicp_out[i], icc_out[i], "difference at index {}", i),
+ }
+ }
+ eprintln!("{} / {} off by one", off_by_one, cicp_out.len());
+}
+
+#[test]
+fn srgb_transfer_characteristics() {
+ check_transfer_characteristics(TransferCharacteristics::Srgb, "sRGB_lcms.icc");
+}
+
+#[test]
+fn bt709_transfer_characteristics() {
+ check_transfer_characteristics(TransferCharacteristics::Bt709, "ITU-709.icc");
+}
+
+#[test]
+fn bt2020_10bit_transfer_characteristics() {
+ check_transfer_characteristics(TransferCharacteristics::Bt2020_10bit, "ITU-2020.icc");
+}
+
+#[test]
+fn bt2020_12bit_transfer_characteristics() {
+ check_transfer_characteristics(TransferCharacteristics::Bt2020_12bit, "ITU-2020.icc");
+}
+
+impl Profile {
+ //XXX: it would be nice if we had a way of ensuring
+ // everything in a profile was initialized regardless of how it was created
+ //XXX: should this also be taking a black_point?
+ /* similar to CGColorSpaceCreateCalibratedRGB */
+ pub fn new_rgb_with_table(
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+ table: &[u16],
+ ) -> Option<Box<Profile>> {
+ let mut profile = profile_create();
+ //XXX: should store the whitepoint
+ if !set_rgb_colorants(&mut profile, white_point, primaries) {
+ return None;
+ }
+ profile.redTRC = Some(curve_from_table(table));
+ profile.blueTRC = Some(curve_from_table(table));
+ profile.greenTRC = Some(curve_from_table(table));
+ profile.class_type = DISPLAY_DEVICE_PROFILE;
+ profile.rendering_intent = Perceptual;
+ profile.color_space = RGB_SIGNATURE;
+ profile.pcs = XYZ_TYPE;
+ Some(profile)
+ }
+ pub fn new_sRGB() -> Box<Profile> {
+ let D65 = qcms_white_point_sRGB();
+ let table = build_sRGB_gamma_table(1024);
+
+ let mut srgb = Profile::new_rgb_with_table(
+ D65,
+ qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709),
+ &table,
+ )
+ .unwrap();
+ srgb.is_srgb = true;
+ srgb
+ }
+
+ /// Returns true if this profile is sRGB
+ pub fn is_sRGB(&self) -> bool {
+ self.is_srgb
+ }
+
+ pub(crate) fn new_sRGB_parametric() -> Box<Profile> {
+ let primaries = qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Bt709);
+ let white_point = qcms_white_point_sRGB();
+ let mut profile = profile_create();
+ set_rgb_colorants(&mut profile, white_point, primaries);
+
+ let curve = Box::new(curveType::Parametric(vec![
+ 2.4,
+ 1. / 1.055,
+ 0.055 / 1.055,
+ 1. / 12.92,
+ 0.04045,
+ ]));
+ profile.redTRC = Some(curve.clone());
+ profile.blueTRC = Some(curve.clone());
+ profile.greenTRC = Some(curve);
+ profile.class_type = DISPLAY_DEVICE_PROFILE;
+ profile.rendering_intent = Perceptual;
+ profile.color_space = RGB_SIGNATURE;
+ profile.pcs = XYZ_TYPE;
+ profile.is_srgb = true;
+ profile
+ }
+
+ pub(crate) fn new_displayP3() -> Box<Profile> {
+ let primaries = qcms_CIE_xyYTRIPLE::from(ColourPrimaries::Smpte432);
+ let white_point = qcms_white_point_sRGB();
+ let mut profile = profile_create();
+ set_rgb_colorants(&mut profile, white_point, primaries);
+
+ let curve = Box::new(curveType::Parametric(vec![
+ 2.4,
+ 1. / 1.055,
+ 0.055 / 1.055,
+ 1. / 12.92,
+ 0.04045,
+ ]));
+ profile.redTRC = Some(curve.clone());
+ profile.blueTRC = Some(curve.clone());
+ profile.greenTRC = Some(curve);
+ profile.class_type = DISPLAY_DEVICE_PROFILE;
+ profile.rendering_intent = Perceptual;
+ profile.color_space = RGB_SIGNATURE;
+ profile.pcs = XYZ_TYPE;
+ profile.is_srgb = false;
+ profile
+ }
+
+ /// Create a new profile with D50 adopted white and identity transform functions
+ pub fn new_XYZD50() -> Box<Profile> {
+ let mut profile = profile_create();
+ profile.redColorant.X = double_to_s15Fixed16Number(1.);
+ profile.redColorant.Y = double_to_s15Fixed16Number(0.);
+ profile.redColorant.Z = double_to_s15Fixed16Number(0.);
+ profile.greenColorant.X = double_to_s15Fixed16Number(0.);
+ profile.greenColorant.Y = double_to_s15Fixed16Number(1.);
+ profile.greenColorant.Z = double_to_s15Fixed16Number(0.);
+ profile.blueColorant.X = double_to_s15Fixed16Number(0.);
+ profile.blueColorant.Y = double_to_s15Fixed16Number(0.);
+ profile.blueColorant.Z = double_to_s15Fixed16Number(1.);
+ profile.redTRC = Some(identity_curve());
+ profile.blueTRC = Some(identity_curve());
+ profile.greenTRC = Some(identity_curve());
+
+ profile.class_type = DISPLAY_DEVICE_PROFILE;
+ profile.rendering_intent = Perceptual;
+ profile.color_space = RGB_SIGNATURE;
+ profile.pcs = XYZ_TYPE;
+ profile
+ }
+
+ pub fn new_cicp(cp: ColourPrimaries, tc: TransferCharacteristics) -> Option<Box<Profile>> {
+ let mut profile = profile_create();
+ //XXX: should store the whitepoint
+ if !set_rgb_colorants(&mut profile, cp.white_point(), qcms_CIE_xyYTRIPLE::from(cp)) {
+ return None;
+ }
+ let curve = curveType::try_from(tc).ok()?;
+ profile.redTRC = Some(Box::new(curve.clone()));
+ profile.blueTRC = Some(Box::new(curve.clone()));
+ profile.greenTRC = Some(Box::new(curve));
+ profile.class_type = DISPLAY_DEVICE_PROFILE;
+ profile.rendering_intent = Perceptual;
+ profile.color_space = RGB_SIGNATURE;
+ profile.pcs = XYZ_TYPE;
+
+ profile.is_srgb = (cp, tc) == (ColourPrimaries::Bt709, TransferCharacteristics::Srgb);
+ Some(profile)
+ }
+
+ pub fn new_gray_with_gamma(gamma: f32) -> Box<Profile> {
+ let mut profile = profile_create();
+
+ profile.grayTRC = Some(curve_from_gamma(gamma));
+ profile.class_type = DISPLAY_DEVICE_PROFILE;
+ profile.rendering_intent = Perceptual;
+ profile.color_space = GRAY_SIGNATURE;
+ profile.pcs = XYZ_TYPE;
+ profile
+ }
+
+ pub fn new_rgb_with_gamma_set(
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+ redGamma: f32,
+ greenGamma: f32,
+ blueGamma: f32,
+ ) -> Option<Box<Profile>> {
+ let mut profile = profile_create();
+
+ //XXX: should store the whitepoint
+ if !set_rgb_colorants(&mut profile, white_point, primaries) {
+ return None;
+ }
+ profile.redTRC = Some(curve_from_gamma(redGamma));
+ profile.blueTRC = Some(curve_from_gamma(blueGamma));
+ profile.greenTRC = Some(curve_from_gamma(greenGamma));
+ profile.class_type = DISPLAY_DEVICE_PROFILE;
+ profile.rendering_intent = Perceptual;
+ profile.color_space = RGB_SIGNATURE;
+ profile.pcs = XYZ_TYPE;
+ Some(profile)
+ }
+
+ pub fn new_from_path(file: &str) -> Option<Box<Profile>> {
+ Profile::new_from_slice(&std::fs::read(file).ok()?, false)
+ }
+
+ pub fn new_from_slice(mem: &[u8], curves_only: bool) -> Option<Box<Profile>> {
+ let length: u32;
+ let mut source: MemSource = MemSource {
+ buf: mem,
+ valid: false,
+ invalid_reason: None,
+ };
+ let index;
+ source.valid = true;
+ let src: &mut MemSource = &mut source;
+ if mem.len() < 4 {
+ return None;
+ }
+ length = read_u32(src, 0);
+ if length as usize <= mem.len() {
+ // shrink the area that we can read if appropriate
+ src.buf = &src.buf[0..length as usize];
+ } else {
+ return None;
+ }
+ /* ensure that the profile size is sane so it's easier to reason about */
+ if src.buf.len() <= 64 || src.buf.len() >= MAX_PROFILE_SIZE {
+ return None;
+ }
+ let mut profile = profile_create();
+
+ check_CMM_type_signature(src);
+ check_profile_version(src);
+ read_class_signature(&mut profile, src);
+ read_rendering_intent(&mut profile, src);
+ read_color_space(&mut profile, src);
+ read_pcs(&mut profile, src);
+ //TODO read rest of profile stuff
+ if !src.valid {
+ return None;
+ }
+
+ index = read_tag_table(&mut profile, src);
+ if !src.valid || index.is_empty() {
+ return None;
+ }
+
+ if let Some(chad) = find_tag(&index, TAG_CHAD) {
+ profile.chromaticAdaption = Some(read_tag_s15Fixed16ArrayType(src, chad))
+ } else {
+ profile.chromaticAdaption = None; //Signal the data is not present
+ }
+
+ if profile.class_type == DISPLAY_DEVICE_PROFILE
+ || profile.class_type == INPUT_DEVICE_PROFILE
+ || profile.class_type == OUTPUT_DEVICE_PROFILE
+ || profile.class_type == COLOR_SPACE_PROFILE
+ {
+ if profile.color_space == RGB_SIGNATURE {
+ if !curves_only {
+ if let Some(A2B0) = find_tag(&index, TAG_A2B0) {
+ let lut_type = read_u32(src, A2B0.offset as usize);
+ if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
+ profile.A2B0 = read_tag_lutType(src, A2B0)
+ } else if lut_type == LUT_MAB_TYPE {
+ profile.mAB = read_tag_lutmABType(src, A2B0)
+ }
+ }
+ if let Some(B2A0) = find_tag(&index, TAG_B2A0) {
+ let lut_type = read_u32(src, B2A0.offset as usize);
+ if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
+ profile.B2A0 = read_tag_lutType(src, B2A0)
+ } else if lut_type == LUT_MBA_TYPE {
+ profile.mBA = read_tag_lutmABType(src, B2A0)
+ }
+ }
+ }
+ if find_tag(&index, TAG_rXYZ).is_some() || curves_only {
+ profile.redColorant = read_tag_XYZType(src, &index, TAG_rXYZ);
+ profile.greenColorant = read_tag_XYZType(src, &index, TAG_gXYZ);
+ profile.blueColorant = read_tag_XYZType(src, &index, TAG_bXYZ)
+ }
+ if !src.valid {
+ return None;
+ }
+
+ if find_tag(&index, TAG_rTRC).is_some() || curves_only {
+ profile.redTRC = read_tag_curveType(src, &index, TAG_rTRC);
+ profile.greenTRC = read_tag_curveType(src, &index, TAG_gTRC);
+ profile.blueTRC = read_tag_curveType(src, &index, TAG_bTRC);
+ if profile.redTRC.is_none()
+ || profile.blueTRC.is_none()
+ || profile.greenTRC.is_none()
+ {
+ return None;
+ }
+ }
+ } else if profile.color_space == GRAY_SIGNATURE {
+ profile.grayTRC = read_tag_curveType(src, &index, TAG_kTRC);
+ profile.grayTRC.as_ref()?;
+ } else if profile.color_space == CMYK_SIGNATURE {
+ if let Some(A2B0) = find_tag(&index, TAG_A2B0) {
+ let lut_type = read_u32(src, A2B0.offset as usize);
+ if lut_type == LUT8_TYPE || lut_type == LUT16_TYPE {
+ profile.A2B0 = read_tag_lutType(src, A2B0)
+ } else if lut_type == LUT_MBA_TYPE {
+ profile.mAB = read_tag_lutmABType(src, A2B0)
+ }
+ }
+ } else {
+ debug_assert!(false, "read_color_space protects against entering here");
+ return None;
+ }
+ } else {
+ return None;
+ }
+
+ if !src.valid {
+ return None;
+ }
+ Some(profile)
+ }
+ /// Precomputes the information needed for this profile to be
+ /// used as the output profile when constructing a `Transform`.
+ pub fn precache_output_transform(&mut self) {
+ crate::transform::qcms_profile_precache_output_transform(self);
+ }
+}
diff --git a/gfx/qcms/src/lib.rs b/gfx/qcms/src/lib.rs
new file mode 100644
index 0000000000..c311964ee3
--- /dev/null
+++ b/gfx/qcms/src/lib.rs
@@ -0,0 +1,72 @@
+/*! A pure Rust color management library.
+*/
+
+#![allow(dead_code)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+// These are needed for the neon SIMD code and can be removed once the MSRV supports the
+// instrinsics we use
+#![cfg_attr(feature = "neon", feature(stdsimd))]
+#![cfg_attr(
+ feature = "neon",
+ feature(arm_target_feature, raw_ref_op)
+
+)]
+
+/// These values match the Rendering Intent values from the ICC spec
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub enum Intent {
+ AbsoluteColorimetric = 3,
+ Saturation = 2,
+ RelativeColorimetric = 1,
+ Perceptual = 0,
+}
+
+use Intent::*;
+
+impl Default for Intent {
+ fn default() -> Self {
+ /* Chris Murphy (CM consultant) suggests this as a default in the event that we
+ * cannot reproduce relative + Black Point Compensation. BPC brings an
+ * unacceptable performance overhead, so we go with perceptual. */
+ Perceptual
+ }
+}
+
+pub(crate) type s15Fixed16Number = i32;
+
+/* produces the nearest float to 'a' with a maximum error
+ * of 1/1024 which happens for large values like 0x40000040 */
+#[inline]
+fn s15Fixed16Number_to_float(a: s15Fixed16Number) -> f32 {
+ a as f32 / 65536.0
+}
+
+#[inline]
+fn double_to_s15Fixed16Number(v: f64) -> s15Fixed16Number {
+ (v * 65536f64) as i32
+}
+
+#[cfg(feature = "c_bindings")]
+extern crate libc;
+#[cfg(feature = "c_bindings")]
+pub mod c_bindings;
+mod chain;
+mod gtest;
+mod iccread;
+mod matrix;
+mod transform;
+pub use iccread::qcms_CIE_xyY as CIE_xyY;
+pub use iccread::qcms_CIE_xyYTRIPLE as CIE_xyYTRIPLE;
+pub use iccread::Profile;
+pub use transform::DataType;
+pub use transform::Transform;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+mod transform_avx;
+#[cfg(all(any(target_arch = "aarch64", target_arch = "arm"), feature = "neon"))]
+mod transform_neon;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+mod transform_sse2;
+mod transform_util;
diff --git a/gfx/qcms/src/matrix.rs b/gfx/qcms/src/matrix.rs
new file mode 100644
index 0000000000..8cd450241e
--- /dev/null
+++ b/gfx/qcms/src/matrix.rs
@@ -0,0 +1,134 @@
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#[derive(Copy, Clone, Debug, Default)]
+pub struct Matrix {
+ pub m: [[f32; 3]; 3], // Three rows of three elems.
+}
+
+#[derive(Copy, Clone)]
+pub struct Vector {
+ pub v: [f32; 3],
+}
+
+impl Matrix {
+ pub fn eval(&self, v: Vector) -> Vector {
+ let mut result: Vector = Vector { v: [0.; 3] };
+ result.v[0] = self.m[0][0] * v.v[0] + self.m[0][1] * v.v[1] + self.m[0][2] * v.v[2];
+ result.v[1] = self.m[1][0] * v.v[0] + self.m[1][1] * v.v[1] + self.m[1][2] * v.v[2];
+ result.v[2] = self.m[2][0] * v.v[0] + self.m[2][1] * v.v[1] + self.m[2][2] * v.v[2];
+ result
+ }
+
+ pub fn row(&self, r: usize) -> [f32; 3] {
+ self.m[r]
+ }
+
+ //probably reuse this computation in matrix_invert
+ pub fn det(&self) -> f32 {
+ let det: f32 = self.m[0][0] * self.m[1][1] * self.m[2][2]
+ + self.m[0][1] * self.m[1][2] * self.m[2][0]
+ + self.m[0][2] * self.m[1][0] * self.m[2][1]
+ - self.m[0][0] * self.m[1][2] * self.m[2][1]
+ - self.m[0][1] * self.m[1][0] * self.m[2][2]
+ - self.m[0][2] * self.m[1][1] * self.m[2][0];
+ det
+ }
+ /* from pixman and cairo and Mathematics for Game Programmers */
+ /* lcms uses gauss-jordan elimination with partial pivoting which is
+ * less efficient and not as numerically stable. See Mathematics for
+ * Game Programmers. */
+ pub fn invert(&self) -> Option<Matrix> {
+ let mut dest_mat: Matrix = Matrix { m: [[0.; 3]; 3] };
+ let mut i: i32;
+
+ const a: [i32; 3] = [2, 2, 1];
+ const b: [i32; 3] = [1, 0, 0];
+ /* inv (A) = 1/det (A) * adj (A) */
+ let mut det: f32 = self.det();
+ if det == 0. {
+ return None;
+ }
+ det = 1. / det;
+ let mut j: i32 = 0;
+ while j < 3 {
+ i = 0;
+ while i < 3 {
+ let ai: i32 = a[i as usize];
+ let aj: i32 = a[j as usize];
+ let bi: i32 = b[i as usize];
+ let bj: i32 = b[j as usize];
+ let mut p: f64 = (self.m[ai as usize][aj as usize]
+ * self.m[bi as usize][bj as usize]
+ - self.m[ai as usize][bj as usize] * self.m[bi as usize][aj as usize])
+ as f64;
+ if ((i + j) & 1) != 0 {
+ p = -p
+ }
+ dest_mat.m[j as usize][i as usize] = (det as f64 * p) as f32;
+ i += 1
+ }
+ j += 1
+ }
+ Some(dest_mat)
+ }
+ pub fn identity() -> Matrix {
+ let mut i: Matrix = Matrix { m: [[0.; 3]; 3] };
+ i.m[0][0] = 1.;
+ i.m[0][1] = 0.;
+ i.m[0][2] = 0.;
+ i.m[1][0] = 0.;
+ i.m[1][1] = 1.;
+ i.m[1][2] = 0.;
+ i.m[2][0] = 0.;
+ i.m[2][1] = 0.;
+ i.m[2][2] = 1.;
+ i
+ }
+ pub fn invalid() -> Option<Matrix> {
+ None
+ }
+ /* from pixman */
+ /* MAT3per... */
+ pub fn multiply(a: Matrix, b: Matrix) -> Matrix {
+ let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
+ let mut dx: i32;
+
+ let mut o: i32;
+ let mut dy: i32 = 0;
+ while dy < 3 {
+ dx = 0;
+ while dx < 3 {
+ let mut v: f64 = 0f64;
+ o = 0;
+ while o < 3 {
+ v += (a.m[dy as usize][o as usize] * b.m[o as usize][dx as usize]) as f64;
+ o += 1
+ }
+ result.m[dy as usize][dx as usize] = v as f32;
+ dx += 1
+ }
+ dy += 1
+ }
+ result
+ }
+}
diff --git a/gfx/qcms/src/transform.rs b/gfx/qcms/src/transform.rs
new file mode 100644
index 0000000000..b0c8b60b18
--- /dev/null
+++ b/gfx/qcms/src/transform.rs
@@ -0,0 +1,1571 @@
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#![allow(clippy::missing_safety_doc)]
+#[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))]
+use crate::transform_neon::{
+ qcms_transform_data_bgra_out_lut_neon, qcms_transform_data_rgb_out_lut_neon,
+ qcms_transform_data_rgba_out_lut_neon,
+};
+use crate::{
+ chain::chain_transform,
+ double_to_s15Fixed16Number,
+ iccread::SUPPORTS_ICCV4,
+ matrix::*,
+ transform_util::{
+ build_colorant_matrix, build_input_gamma_table, build_output_lut, compute_precache,
+ lut_interp_linear,
+ },
+};
+use crate::{
+ iccread::{qcms_CIE_xyY, qcms_CIE_xyYTRIPLE, Profile, GRAY_SIGNATURE, RGB_SIGNATURE},
+ transform_util::clamp_float,
+ Intent,
+};
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use crate::{
+ transform_avx::{
+ qcms_transform_data_bgra_out_lut_avx, qcms_transform_data_rgb_out_lut_avx,
+ qcms_transform_data_rgba_out_lut_avx,
+ },
+ transform_sse2::{
+ qcms_transform_data_bgra_out_lut_sse2, qcms_transform_data_rgb_out_lut_sse2,
+ qcms_transform_data_rgba_out_lut_sse2,
+ },
+};
+
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
+#[cfg(all(target_arch = "arm", feature = "neon"))]
+use std::arch::is_arm_feature_detected;
+#[cfg(all(target_arch = "aarch64", feature = "neon"))]
+use std::arch::is_aarch64_feature_detected;
+
+pub const PRECACHE_OUTPUT_SIZE: usize = 8192;
+pub const PRECACHE_OUTPUT_MAX: usize = PRECACHE_OUTPUT_SIZE - 1;
+pub const FLOATSCALE: f32 = PRECACHE_OUTPUT_SIZE as f32;
+pub const CLAMPMAXVAL: f32 = ((PRECACHE_OUTPUT_SIZE - 1) as f32) / PRECACHE_OUTPUT_SIZE as f32;
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct PrecacheOuput {
+ /* We previously used a count of 65536 here but that seems like more
+ * precision than we actually need. By reducing the size we can
+ * improve startup performance and reduce memory usage. ColorSync on
+ * 10.5 uses 4097 which is perhaps because they use a fixed point
+ * representation where 1. is represented by 0x1000. */
+ pub data: [u8; PRECACHE_OUTPUT_SIZE],
+}
+
+impl Default for PrecacheOuput {
+ fn default() -> PrecacheOuput {
+ PrecacheOuput {
+ data: [0; PRECACHE_OUTPUT_SIZE],
+ }
+ }
+}
+
+/* used as a lookup table for the output transformation.
+ * we refcount them so we only need to have one around per output
+ * profile, instead of duplicating them per transform */
+
+#[repr(C)]
+#[repr(align(16))]
+#[derive(Clone, Default)]
+pub struct qcms_transform {
+ pub matrix: [[f32; 4]; 3],
+ pub input_gamma_table_r: Option<Box<[f32; 256]>>,
+ pub input_gamma_table_g: Option<Box<[f32; 256]>>,
+ pub input_gamma_table_b: Option<Box<[f32; 256]>>,
+ pub input_clut_table_length: u16,
+ pub clut: Option<Vec<f32>>,
+ pub grid_size: u16,
+ pub output_clut_table_length: u16,
+ pub input_gamma_table_gray: Option<Box<[f32; 256]>>,
+ pub out_gamma_r: f32,
+ pub out_gamma_g: f32,
+ pub out_gamma_b: f32,
+ pub out_gamma_gray: f32,
+ pub output_gamma_lut_r: Option<Vec<u16>>,
+ pub output_gamma_lut_g: Option<Vec<u16>>,
+ pub output_gamma_lut_b: Option<Vec<u16>>,
+ pub output_gamma_lut_gray: Option<Vec<u16>>,
+ pub output_gamma_lut_r_length: usize,
+ pub output_gamma_lut_g_length: usize,
+ pub output_gamma_lut_b_length: usize,
+ pub output_gamma_lut_gray_length: usize,
+ pub output_table_r: Option<Arc<PrecacheOuput>>,
+ pub output_table_g: Option<Arc<PrecacheOuput>>,
+ pub output_table_b: Option<Arc<PrecacheOuput>>,
+ pub transform_fn: transform_fn_t,
+}
+
+pub type transform_fn_t =
+ Option<unsafe fn(_: &qcms_transform, _: *const u8, _: *mut u8, _: usize) -> ()>;
+/// The format of pixel data
+#[repr(u32)]
+#[derive(PartialEq, Eq, Clone, Copy)]
+#[allow(clippy::upper_case_acronyms)]
+pub enum DataType {
+ RGB8 = 0,
+ RGBA8 = 1,
+ BGRA8 = 2,
+ Gray8 = 3,
+ GrayA8 = 4,
+ CMYK = 5,
+}
+
+impl DataType {
+ pub fn bytes_per_pixel(&self) -> usize {
+ match self {
+ RGB8 => 3,
+ RGBA8 => 4,
+ BGRA8 => 4,
+ Gray8 => 1,
+ GrayA8 => 2,
+ CMYK => 4,
+ }
+ }
+}
+
+use DataType::*;
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+#[allow(clippy::upper_case_acronyms)]
+pub struct CIE_XYZ {
+ pub X: f64,
+ pub Y: f64,
+ pub Z: f64,
+}
+
+pub trait Format {
+ const kRIndex: usize;
+ const kGIndex: usize;
+ const kBIndex: usize;
+ const kAIndex: usize;
+}
+
+#[allow(clippy::upper_case_acronyms)]
+pub struct BGRA;
+impl Format for BGRA {
+ const kBIndex: usize = 0;
+ const kGIndex: usize = 1;
+ const kRIndex: usize = 2;
+ const kAIndex: usize = 3;
+}
+
+#[allow(clippy::upper_case_acronyms)]
+pub struct RGBA;
+impl Format for RGBA {
+ const kRIndex: usize = 0;
+ const kGIndex: usize = 1;
+ const kBIndex: usize = 2;
+ const kAIndex: usize = 3;
+}
+
+#[allow(clippy::upper_case_acronyms)]
+pub struct RGB;
+impl Format for RGB {
+ const kRIndex: usize = 0;
+ const kGIndex: usize = 1;
+ const kBIndex: usize = 2;
+ const kAIndex: usize = 0xFF;
+}
+
+pub trait GrayFormat {
+ const has_alpha: bool;
+}
+
+pub struct Gray;
+impl GrayFormat for Gray {
+ const has_alpha: bool = false;
+}
+
+pub struct GrayAlpha;
+impl GrayFormat for GrayAlpha {
+ const has_alpha: bool = true;
+}
+
+#[inline]
+fn clamp_u8(v: f32) -> u8 {
+ if v > 255. {
+ 255
+ } else if v < 0. {
+ 0
+ } else {
+ (v + 0.5).floor() as u8
+ }
+}
+
+// Build a White point, primary chromas transfer matrix from RGB to CIE XYZ
+// This is just an approximation, I am not handling all the non-linear
+// aspects of the RGB to XYZ process, and assumming that the gamma correction
+// has transitive property in the tranformation chain.
+//
+// the alghoritm:
+//
+// - First I build the absolute conversion matrix using
+// primaries in XYZ. This matrix is next inverted
+// - Then I eval the source white point across this matrix
+// obtaining the coeficients of the transformation
+// - Then, I apply these coeficients to the original matrix
+fn build_RGB_to_XYZ_transfer_matrix(
+ white: qcms_CIE_xyY,
+ primrs: qcms_CIE_xyYTRIPLE,
+) -> Option<Matrix> {
+ let mut primaries: Matrix = Matrix { m: [[0.; 3]; 3] };
+
+ let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
+ let mut white_point: Vector = Vector { v: [0.; 3] };
+
+ let xn: f64 = white.x;
+ let yn: f64 = white.y;
+ if yn == 0.0f64 {
+ return None;
+ }
+
+ let xr: f64 = primrs.red.x;
+ let yr: f64 = primrs.red.y;
+ let xg: f64 = primrs.green.x;
+ let yg: f64 = primrs.green.y;
+ let xb: f64 = primrs.blue.x;
+ let yb: f64 = primrs.blue.y;
+ primaries.m[0][0] = xr as f32;
+ primaries.m[0][1] = xg as f32;
+ primaries.m[0][2] = xb as f32;
+ primaries.m[1][0] = yr as f32;
+ primaries.m[1][1] = yg as f32;
+ primaries.m[1][2] = yb as f32;
+ primaries.m[2][0] = (1f64 - xr - yr) as f32;
+ primaries.m[2][1] = (1f64 - xg - yg) as f32;
+ primaries.m[2][2] = (1f64 - xb - yb) as f32;
+ white_point.v[0] = (xn / yn) as f32;
+ white_point.v[1] = 1.;
+ white_point.v[2] = ((1.0f64 - xn - yn) / yn) as f32;
+ let primaries_invert: Matrix = primaries.invert()?;
+
+ let coefs: Vector = primaries_invert.eval(white_point);
+ result.m[0][0] = (coefs.v[0] as f64 * xr) as f32;
+ result.m[0][1] = (coefs.v[1] as f64 * xg) as f32;
+ result.m[0][2] = (coefs.v[2] as f64 * xb) as f32;
+ result.m[1][0] = (coefs.v[0] as f64 * yr) as f32;
+ result.m[1][1] = (coefs.v[1] as f64 * yg) as f32;
+ result.m[1][2] = (coefs.v[2] as f64 * yb) as f32;
+ result.m[2][0] = (coefs.v[0] as f64 * (1.0f64 - xr - yr)) as f32;
+ result.m[2][1] = (coefs.v[1] as f64 * (1.0f64 - xg - yg)) as f32;
+ result.m[2][2] = (coefs.v[2] as f64 * (1.0f64 - xb - yb)) as f32;
+ Some(result)
+}
+/* CIE Illuminant D50 */
+const D50_XYZ: CIE_XYZ = CIE_XYZ {
+ X: 0.9642f64,
+ Y: 1.0000f64,
+ Z: 0.8249f64,
+};
+/* from lcms: xyY2XYZ()
+ * corresponds to argyll: icmYxy2XYZ() */
+fn xyY2XYZ(source: qcms_CIE_xyY) -> CIE_XYZ {
+ let mut dest: CIE_XYZ = CIE_XYZ {
+ X: 0.,
+ Y: 0.,
+ Z: 0.,
+ };
+ dest.X = source.x / source.y * source.Y;
+ dest.Y = source.Y;
+ dest.Z = (1f64 - source.x - source.y) / source.y * source.Y;
+ dest
+}
+/* from lcms: ComputeChromaticAdaption */
+// Compute chromatic adaption matrix using chad as cone matrix
+fn compute_chromatic_adaption(
+ source_white_point: CIE_XYZ,
+ dest_white_point: CIE_XYZ,
+ chad: Matrix,
+) -> Option<Matrix> {
+ let mut cone_source_XYZ: Vector = Vector { v: [0.; 3] };
+
+ let mut cone_dest_XYZ: Vector = Vector { v: [0.; 3] };
+
+ let mut cone: Matrix = Matrix { m: [[0.; 3]; 3] };
+
+ let chad_inv: Matrix = chad.invert()?;
+ cone_source_XYZ.v[0] = source_white_point.X as f32;
+ cone_source_XYZ.v[1] = source_white_point.Y as f32;
+ cone_source_XYZ.v[2] = source_white_point.Z as f32;
+ cone_dest_XYZ.v[0] = dest_white_point.X as f32;
+ cone_dest_XYZ.v[1] = dest_white_point.Y as f32;
+ cone_dest_XYZ.v[2] = dest_white_point.Z as f32;
+
+ let cone_source_rgb: Vector = chad.eval(cone_source_XYZ);
+ let cone_dest_rgb: Vector = chad.eval(cone_dest_XYZ);
+ cone.m[0][0] = cone_dest_rgb.v[0] / cone_source_rgb.v[0];
+ cone.m[0][1] = 0.;
+ cone.m[0][2] = 0.;
+ cone.m[1][0] = 0.;
+ cone.m[1][1] = cone_dest_rgb.v[1] / cone_source_rgb.v[1];
+ cone.m[1][2] = 0.;
+ cone.m[2][0] = 0.;
+ cone.m[2][1] = 0.;
+ cone.m[2][2] = cone_dest_rgb.v[2] / cone_source_rgb.v[2];
+ // Normalize
+ Some(Matrix::multiply(chad_inv, Matrix::multiply(cone, chad)))
+}
+/* from lcms: cmsAdaptionMatrix */
+// Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll
+// Bradford is assumed
+fn adaption_matrix(source_illumination: CIE_XYZ, target_illumination: CIE_XYZ) -> Option<Matrix> {
+ let lam_rigg: Matrix = {
+ Matrix {
+ m: [
+ [0.8951, 0.2664, -0.1614],
+ [-0.7502, 1.7135, 0.0367],
+ [0.0389, -0.0685, 1.0296],
+ ],
+ }
+ };
+ compute_chromatic_adaption(source_illumination, target_illumination, lam_rigg)
+}
+/* from lcms: cmsAdaptMatrixToD50 */
+fn adapt_matrix_to_D50(r: Option<Matrix>, source_white_pt: qcms_CIE_xyY) -> Option<Matrix> {
+ if source_white_pt.y == 0.0f64 {
+ return None;
+ }
+
+ let Dn: CIE_XYZ = xyY2XYZ(source_white_pt);
+ let Bradford: Matrix = adaption_matrix(Dn, D50_XYZ)?;
+ Some(Matrix::multiply(Bradford, r?))
+}
+pub(crate) fn set_rgb_colorants(
+ profile: &mut Profile,
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+) -> bool {
+ let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
+ let colorants = match adapt_matrix_to_D50(colorants, white_point) {
+ Some(colorants) => colorants,
+ None => return false,
+ };
+
+ /* note: there's a transpose type of operation going on here */
+ profile.redColorant.X = double_to_s15Fixed16Number(colorants.m[0][0] as f64);
+ profile.redColorant.Y = double_to_s15Fixed16Number(colorants.m[1][0] as f64);
+ profile.redColorant.Z = double_to_s15Fixed16Number(colorants.m[2][0] as f64);
+ profile.greenColorant.X = double_to_s15Fixed16Number(colorants.m[0][1] as f64);
+ profile.greenColorant.Y = double_to_s15Fixed16Number(colorants.m[1][1] as f64);
+ profile.greenColorant.Z = double_to_s15Fixed16Number(colorants.m[2][1] as f64);
+ profile.blueColorant.X = double_to_s15Fixed16Number(colorants.m[0][2] as f64);
+ profile.blueColorant.Y = double_to_s15Fixed16Number(colorants.m[1][2] as f64);
+ profile.blueColorant.Z = double_to_s15Fixed16Number(colorants.m[2][2] as f64);
+ true
+}
+pub(crate) fn get_rgb_colorants(
+ white_point: qcms_CIE_xyY,
+ primaries: qcms_CIE_xyYTRIPLE,
+) -> Option<Matrix> {
+ let colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries);
+ adapt_matrix_to_D50(colorants, white_point)
+}
+/* Alpha is not corrected.
+ A rationale for this is found in Alvy Ray's "Should Alpha Be Nonlinear If
+ RGB Is?" Tech Memo 17 (December 14, 1998).
+ See: ftp://ftp.alvyray.com/Acrobat/17_Nonln.pdf
+*/
+unsafe extern "C" fn qcms_transform_data_gray_template_lut<I: GrayFormat, F: Format>(
+ transform: &qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ length: usize,
+) {
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+ let input_gamma_table_gray = transform.input_gamma_table_gray.as_ref().unwrap();
+
+ let mut i: u32 = 0;
+ while (i as usize) < length {
+ let fresh0 = src;
+ src = src.offset(1);
+ let device: u8 = *fresh0;
+ let mut alpha: u8 = 0xffu8;
+ if I::has_alpha {
+ let fresh1 = src;
+ src = src.offset(1);
+ alpha = *fresh1
+ }
+ let linear: f32 = input_gamma_table_gray[device as usize];
+
+ let out_device_r: f32 = lut_interp_linear(
+ linear as f64,
+ &(*transform).output_gamma_lut_r.as_ref().unwrap(),
+ );
+ let out_device_g: f32 = lut_interp_linear(
+ linear as f64,
+ &(*transform).output_gamma_lut_g.as_ref().unwrap(),
+ );
+ let out_device_b: f32 = lut_interp_linear(
+ linear as f64,
+ &(*transform).output_gamma_lut_b.as_ref().unwrap(),
+ );
+ *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
+ *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
+ *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha
+ }
+ dest = dest.offset(components as isize);
+ i += 1
+ }
+}
+unsafe fn qcms_transform_data_gray_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_lut::<Gray, RGB>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_gray_rgba_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_lut::<Gray, RGBA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_gray_bgra_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_lut::<Gray, BGRA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_graya_rgba_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_lut::<GrayAlpha, RGBA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_graya_bgra_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_lut::<GrayAlpha, BGRA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_gray_template_precache<I: GrayFormat, F: Format>(
+ transform: *const qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ length: usize,
+) {
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+ let output_table_r = ((*transform).output_table_r).as_deref().unwrap();
+ let output_table_g = ((*transform).output_table_g).as_deref().unwrap();
+ let output_table_b = ((*transform).output_table_b).as_deref().unwrap();
+
+ let input_gamma_table_gray = (*transform)
+ .input_gamma_table_gray
+ .as_ref()
+ .unwrap()
+ .as_ptr();
+
+ let mut i: u32 = 0;
+ while (i as usize) < length {
+ let fresh2 = src;
+ src = src.offset(1);
+ let device: u8 = *fresh2;
+ let mut alpha: u8 = 0xffu8;
+ if I::has_alpha {
+ let fresh3 = src;
+ src = src.offset(1);
+ alpha = *fresh3
+ }
+
+ let linear: f32 = *input_gamma_table_gray.offset(device as isize);
+ /* we could round here... */
+ let gray: u16 = (linear * PRECACHE_OUTPUT_MAX as f32) as u16;
+ *dest.add(F::kRIndex) = (output_table_r).data[gray as usize];
+ *dest.add(F::kGIndex) = (output_table_g).data[gray as usize];
+ *dest.add(F::kBIndex) = (output_table_b).data[gray as usize];
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha
+ }
+ dest = dest.offset(components as isize);
+ i += 1
+ }
+}
+unsafe fn qcms_transform_data_gray_out_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_precache::<Gray, RGB>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_gray_rgba_out_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_precache::<Gray, RGBA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_gray_bgra_out_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_precache::<Gray, BGRA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_graya_rgba_out_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_precache::<GrayAlpha, RGBA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_graya_bgra_out_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_gray_template_precache::<GrayAlpha, BGRA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_template_lut_precache<F: Format>(
+ transform: &qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ length: usize,
+) {
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+ let output_table_r = ((*transform).output_table_r).as_deref().unwrap();
+ let output_table_g = ((*transform).output_table_g).as_deref().unwrap();
+ let output_table_b = ((*transform).output_table_b).as_deref().unwrap();
+ let input_gamma_table_r = (*transform).input_gamma_table_r.as_ref().unwrap().as_ptr();
+ let input_gamma_table_g = (*transform).input_gamma_table_g.as_ref().unwrap().as_ptr();
+ let input_gamma_table_b = (*transform).input_gamma_table_b.as_ref().unwrap().as_ptr();
+
+ let mat = &transform.matrix;
+ let mut i: u32 = 0;
+ while (i as usize) < length {
+ let device_r: u8 = *src.add(F::kRIndex);
+ let device_g: u8 = *src.add(F::kGIndex);
+ let device_b: u8 = *src.add(F::kBIndex);
+ let mut alpha: u8 = 0;
+ if F::kAIndex != 0xff {
+ alpha = *src.add(F::kAIndex)
+ }
+ src = src.offset(components as isize);
+
+ let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize);
+ let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize);
+ let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize);
+ let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
+ let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
+ let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b;
+ out_linear_r = clamp_float(out_linear_r);
+ out_linear_g = clamp_float(out_linear_g);
+ out_linear_b = clamp_float(out_linear_b);
+ /* we could round here... */
+
+ let r: u16 = (out_linear_r * PRECACHE_OUTPUT_MAX as f32) as u16;
+ let g: u16 = (out_linear_g * PRECACHE_OUTPUT_MAX as f32) as u16;
+ let b: u16 = (out_linear_b * PRECACHE_OUTPUT_MAX as f32) as u16;
+ *dest.add(F::kRIndex) = (output_table_r).data[r as usize];
+ *dest.add(F::kGIndex) = (output_table_g).data[g as usize];
+ *dest.add(F::kBIndex) = (output_table_b).data[b as usize];
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha
+ }
+ dest = dest.offset(components as isize);
+ i += 1
+ }
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_rgb_out_lut_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_precache::<RGB>(transform, src, dest, length);
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_rgba_out_lut_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_precache::<RGBA>(transform, src, dest, length);
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_bgra_out_lut_precache(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_precache::<BGRA>(transform, src, dest, length);
+}
+// Not used
+/*
+static void qcms_transform_data_clut(const qcms_transform *transform, const unsigned char *src, unsigned char *dest, size_t length) {
+ unsigned int i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ const float* r_table = transform->r_clut;
+ const float* g_table = transform->g_clut;
+ const float* b_table = transform->b_clut;
+
+ for (i = 0; i < length; i++) {
+ unsigned char in_r = *src++;
+ unsigned char in_g = *src++;
+ unsigned char in_b = *src++;
+ float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f;
+
+ int x = floorf(linear_r * (transform->grid_size-1));
+ int y = floorf(linear_g * (transform->grid_size-1));
+ int z = floorf(linear_b * (transform->grid_size-1));
+ int x_n = ceilf(linear_r * (transform->grid_size-1));
+ int y_n = ceilf(linear_g * (transform->grid_size-1));
+ int z_n = ceilf(linear_b * (transform->grid_size-1));
+ float x_d = linear_r * (transform->grid_size-1) - x;
+ float y_d = linear_g * (transform->grid_size-1) - y;
+ float z_d = linear_b * (transform->grid_size-1) - z;
+
+ float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d);
+ float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d);
+ float r_y1 = lerp(r_x1, r_x2, y_d);
+ float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d);
+ float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d);
+ float r_y2 = lerp(r_x3, r_x4, y_d);
+ float clut_r = lerp(r_y1, r_y2, z_d);
+
+ float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d);
+ float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d);
+ float g_y1 = lerp(g_x1, g_x2, y_d);
+ float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d);
+ float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d);
+ float g_y2 = lerp(g_x3, g_x4, y_d);
+ float clut_g = lerp(g_y1, g_y2, z_d);
+
+ float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d);
+ float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d);
+ float b_y1 = lerp(b_x1, b_x2, y_d);
+ float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d);
+ float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d);
+ float b_y2 = lerp(b_x3, b_x4, y_d);
+ float clut_b = lerp(b_y1, b_y2, z_d);
+
+ *dest++ = clamp_u8(clut_r*255.0f);
+ *dest++ = clamp_u8(clut_g*255.0f);
+ *dest++ = clamp_u8(clut_b*255.0f);
+ }
+}
+*/
+fn int_div_ceil(value: i32, div: i32) -> i32 {
+ (value + div - 1) / div
+}
+// Using lcms' tetra interpolation algorithm.
+unsafe extern "C" fn qcms_transform_data_tetra_clut_template<F: Format>(
+ transform: *const qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ length: usize,
+) {
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+
+ let xy_len: i32 = 1;
+ let x_len: i32 = (*transform).grid_size as i32;
+ let len: i32 = x_len * x_len;
+ let table = (*transform).clut.as_ref().unwrap().as_ptr();
+ let r_table: *const f32 = table;
+ let g_table: *const f32 = table.offset(1);
+ let b_table: *const f32 = table.offset(2);
+
+ let mut i: u32 = 0;
+ while (i as usize) < length {
+ let c0_r: f32;
+ let c1_r: f32;
+ let c2_r: f32;
+ let c3_r: f32;
+ let c0_g: f32;
+ let c1_g: f32;
+ let c2_g: f32;
+ let c3_g: f32;
+ let c0_b: f32;
+ let c1_b: f32;
+ let c2_b: f32;
+ let c3_b: f32;
+ let in_r: u8 = *src.add(F::kRIndex);
+ let in_g: u8 = *src.add(F::kGIndex);
+ let in_b: u8 = *src.add(F::kBIndex);
+ let mut in_a: u8 = 0;
+ if F::kAIndex != 0xff {
+ in_a = *src.add(F::kAIndex)
+ }
+ src = src.offset(components as isize);
+ let linear_r: f32 = in_r as i32 as f32 / 255.0;
+ let linear_g: f32 = in_g as i32 as f32 / 255.0;
+ let linear_b: f32 = in_b as i32 as f32 / 255.0;
+ let x: i32 = in_r as i32 * ((*transform).grid_size as i32 - 1) / 255;
+ let y: i32 = in_g as i32 * ((*transform).grid_size as i32 - 1) / 255;
+ let z: i32 = in_b as i32 * ((*transform).grid_size as i32 - 1) / 255;
+ let x_n: i32 = int_div_ceil(in_r as i32 * ((*transform).grid_size as i32 - 1), 255);
+ let y_n: i32 = int_div_ceil(in_g as i32 * ((*transform).grid_size as i32 - 1), 255);
+ let z_n: i32 = int_div_ceil(in_b as i32 * ((*transform).grid_size as i32 - 1), 255);
+ let rx: f32 = linear_r * ((*transform).grid_size as i32 - 1) as f32 - x as f32;
+ let ry: f32 = linear_g * ((*transform).grid_size as i32 - 1) as f32 - y as f32;
+ let rz: f32 = linear_b * ((*transform).grid_size as i32 - 1) as f32 - z as f32;
+ let CLU = |table: *const f32, x, y, z| {
+ *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
+ };
+
+ c0_r = CLU(r_table, x, y, z);
+ c0_g = CLU(g_table, x, y, z);
+ c0_b = CLU(b_table, x, y, z);
+ if rx >= ry {
+ if ry >= rz {
+ //rx >= ry && ry >= rz
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else if rx >= rz {
+ //rx >= rz && rz >= ry
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
+ } else {
+ //rz > rx && rx >= ry
+ c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ } else if rx >= rz {
+ //ry > rx && rx >= rz
+ c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else if ry >= rz {
+ //ry >= rz && rz > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
+ } else {
+ //rz > ry && ry > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz;
+ let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz;
+ let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz;
+ *dest.add(F::kRIndex) = clamp_u8(clut_r * 255.0);
+ *dest.add(F::kGIndex) = clamp_u8(clut_g * 255.0);
+ *dest.add(F::kBIndex) = clamp_u8(clut_b * 255.0);
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = in_a
+ }
+ dest = dest.offset(components as isize);
+ i += 1
+ }
+}
+
+unsafe fn tetra(
+ transform: &qcms_transform,
+ table: *const f32,
+ in_r: u8,
+ in_g: u8,
+ in_b: u8,
+) -> (f32, f32, f32) {
+ let r_table: *const f32 = table;
+ let g_table: *const f32 = table.offset(1);
+ let b_table: *const f32 = table.offset(2);
+ let linear_r: f32 = in_r as i32 as f32 / 255.0;
+ let linear_g: f32 = in_g as i32 as f32 / 255.0;
+ let linear_b: f32 = in_b as i32 as f32 / 255.0;
+ let xy_len: i32 = 1;
+ let x_len: i32 = (*transform).grid_size as i32;
+ let len: i32 = x_len * x_len;
+ let x: i32 = in_r as i32 * ((*transform).grid_size as i32 - 1) / 255;
+ let y: i32 = in_g as i32 * ((*transform).grid_size as i32 - 1) / 255;
+ let z: i32 = in_b as i32 * ((*transform).grid_size as i32 - 1) / 255;
+ let x_n: i32 = int_div_ceil(in_r as i32 * ((*transform).grid_size as i32 - 1), 255);
+ let y_n: i32 = int_div_ceil(in_g as i32 * ((*transform).grid_size as i32 - 1), 255);
+ let z_n: i32 = int_div_ceil(in_b as i32 * ((*transform).grid_size as i32 - 1), 255);
+ let rx: f32 = linear_r * ((*transform).grid_size as i32 - 1) as f32 - x as f32;
+ let ry: f32 = linear_g * ((*transform).grid_size as i32 - 1) as f32 - y as f32;
+ let rz: f32 = linear_b * ((*transform).grid_size as i32 - 1) as f32 - z as f32;
+ let CLU = |table: *const f32, x, y, z| {
+ *table.offset(((x * len + y * x_len + z * xy_len) * 3) as isize)
+ };
+ let c0_r: f32;
+ let c1_r: f32;
+ let c2_r: f32;
+ let c3_r: f32;
+ let c0_g: f32;
+ let c1_g: f32;
+ let c2_g: f32;
+ let c3_g: f32;
+ let c0_b: f32;
+ let c1_b: f32;
+ let c2_b: f32;
+ let c3_b: f32;
+ c0_r = CLU(r_table, x, y, z);
+ c0_g = CLU(g_table, x, y, z);
+ c0_b = CLU(b_table, x, y, z);
+ if rx >= ry {
+ if ry >= rz {
+ //rx >= ry && ry >= rz
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else if rx >= rz {
+ //rx >= rz && rz >= ry
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
+ } else {
+ //rz > rx && rx >= ry
+ c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ } else if rx >= rz {
+ //ry > rx && rx >= rz
+ c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else if ry >= rz {
+ //ry >= rz && rz > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
+ } else {
+ //rz > ry && ry > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ let clut_r = c0_r + c1_r * rx + c2_r * ry + c3_r * rz;
+ let clut_g = c0_g + c1_g * rx + c2_g * ry + c3_g * rz;
+ let clut_b = c0_b + c1_b * rx + c2_b * ry + c3_b * rz;
+ (clut_r, clut_g, clut_b)
+}
+
+#[inline]
+fn lerp(a: f32, b: f32, t: f32) -> f32 {
+ a * (1.0 - t) + b * t
+}
+
+// lerp between two tetrahedral interpolations
+// See lcms:Eval4InputsFloat
+#[allow(clippy::many_single_char_names)]
+unsafe fn qcms_transform_data_tetra_clut_cmyk(
+ transform: &qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ length: usize,
+) {
+ let table = (*transform).clut.as_ref().unwrap().as_ptr();
+ assert!(
+ (*transform).clut.as_ref().unwrap().len()
+ >= ((transform.grid_size as i32).pow(4) * 3) as usize
+ );
+ for _ in 0..length {
+ let c: u8 = *src.add(0);
+ let m: u8 = *src.add(1);
+ let y: u8 = *src.add(2);
+ let k: u8 = *src.add(3);
+ src = src.offset(4);
+ let linear_k: f32 = k as i32 as f32 / 255.0;
+ let grid_size = (*transform).grid_size as i32;
+ let w: i32 = k as i32 * ((*transform).grid_size as i32 - 1) / 255;
+ let w_n: i32 = int_div_ceil(k as i32 * ((*transform).grid_size as i32 - 1), 255);
+ let t: f32 = linear_k * ((*transform).grid_size as i32 - 1) as f32 - w as f32;
+
+ let table1 = table.offset((w * grid_size * grid_size * grid_size * 3) as isize);
+ let table2 = table.offset((w_n * grid_size * grid_size * grid_size * 3) as isize);
+
+ let (r1, g1, b1) = tetra(transform, table1, c, m, y);
+ let (r2, g2, b2) = tetra(transform, table2, c, m, y);
+ let r = lerp(r1, r2, t);
+ let g = lerp(g1, g2, t);
+ let b = lerp(b1, b2, t);
+ *dest.add(0) = clamp_u8(r * 255.0);
+ *dest.add(1) = clamp_u8(g * 255.0);
+ *dest.add(2) = clamp_u8(b * 255.0);
+ dest = dest.offset(3);
+ }
+}
+
+unsafe fn qcms_transform_data_tetra_clut_rgb(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_tetra_clut_template::<RGB>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_tetra_clut_rgba(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_tetra_clut_template::<RGBA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_tetra_clut_bgra(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_tetra_clut_template::<BGRA>(transform, src, dest, length);
+}
+unsafe fn qcms_transform_data_template_lut<F: Format>(
+ transform: &qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ length: usize,
+) {
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+
+ let mat = &transform.matrix;
+ let mut i: u32 = 0;
+ let input_gamma_table_r = (*transform).input_gamma_table_r.as_ref().unwrap().as_ptr();
+ let input_gamma_table_g = (*transform).input_gamma_table_g.as_ref().unwrap().as_ptr();
+ let input_gamma_table_b = (*transform).input_gamma_table_b.as_ref().unwrap().as_ptr();
+ while (i as usize) < length {
+ let device_r: u8 = *src.add(F::kRIndex);
+ let device_g: u8 = *src.add(F::kGIndex);
+ let device_b: u8 = *src.add(F::kBIndex);
+ let mut alpha: u8 = 0;
+ if F::kAIndex != 0xff {
+ alpha = *src.add(F::kAIndex)
+ }
+ src = src.offset(components as isize);
+
+ let linear_r: f32 = *input_gamma_table_r.offset(device_r as isize);
+ let linear_g: f32 = *input_gamma_table_g.offset(device_g as isize);
+ let linear_b: f32 = *input_gamma_table_b.offset(device_b as isize);
+ let mut out_linear_r = mat[0][0] * linear_r + mat[1][0] * linear_g + mat[2][0] * linear_b;
+ let mut out_linear_g = mat[0][1] * linear_r + mat[1][1] * linear_g + mat[2][1] * linear_b;
+ let mut out_linear_b = mat[0][2] * linear_r + mat[1][2] * linear_g + mat[2][2] * linear_b;
+ out_linear_r = clamp_float(out_linear_r);
+ out_linear_g = clamp_float(out_linear_g);
+ out_linear_b = clamp_float(out_linear_b);
+
+ let out_device_r: f32 = lut_interp_linear(
+ out_linear_r as f64,
+ &(*transform).output_gamma_lut_r.as_ref().unwrap(),
+ );
+ let out_device_g: f32 = lut_interp_linear(
+ out_linear_g as f64,
+ (*transform).output_gamma_lut_g.as_ref().unwrap(),
+ );
+ let out_device_b: f32 = lut_interp_linear(
+ out_linear_b as f64,
+ (*transform).output_gamma_lut_b.as_ref().unwrap(),
+ );
+ *dest.add(F::kRIndex) = clamp_u8(out_device_r * 255f32);
+ *dest.add(F::kGIndex) = clamp_u8(out_device_g * 255f32);
+ *dest.add(F::kBIndex) = clamp_u8(out_device_b * 255f32);
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha
+ }
+ dest = dest.offset(components as isize);
+ i += 1
+ }
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_rgb_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut::<RGB>(transform, src, dest, length);
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_rgba_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut::<RGBA>(transform, src, dest, length);
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_bgra_out_lut(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut::<BGRA>(transform, src, dest, length);
+}
+
+fn precache_create() -> Arc<PrecacheOuput> {
+ Arc::new(PrecacheOuput::default())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn qcms_transform_release(t: *mut qcms_transform) {
+ drop(Box::from_raw(t));
+}
+
+const bradford_matrix: Matrix = Matrix {
+ m: [
+ [0.8951, 0.2664, -0.1614],
+ [-0.7502, 1.7135, 0.0367],
+ [0.0389, -0.0685, 1.0296],
+ ],
+};
+
+const bradford_matrix_inv: Matrix = Matrix {
+ m: [
+ [0.9869929, -0.1470543, 0.1599627],
+ [0.4323053, 0.5183603, 0.0492912],
+ [-0.0085287, 0.0400428, 0.9684867],
+ ],
+};
+
+// See ICCv4 E.3
+fn compute_whitepoint_adaption(X: f32, Y: f32, Z: f32) -> Matrix {
+ let p: f32 = (0.96422 * bradford_matrix.m[0][0]
+ + 1.000 * bradford_matrix.m[1][0]
+ + 0.82521 * bradford_matrix.m[2][0])
+ / (X * bradford_matrix.m[0][0] + Y * bradford_matrix.m[1][0] + Z * bradford_matrix.m[2][0]);
+ let y: f32 = (0.96422 * bradford_matrix.m[0][1]
+ + 1.000 * bradford_matrix.m[1][1]
+ + 0.82521 * bradford_matrix.m[2][1])
+ / (X * bradford_matrix.m[0][1] + Y * bradford_matrix.m[1][1] + Z * bradford_matrix.m[2][1]);
+ let b: f32 = (0.96422 * bradford_matrix.m[0][2]
+ + 1.000 * bradford_matrix.m[1][2]
+ + 0.82521 * bradford_matrix.m[2][2])
+ / (X * bradford_matrix.m[0][2] + Y * bradford_matrix.m[1][2] + Z * bradford_matrix.m[2][2]);
+ let white_adaption = Matrix {
+ m: [[p, 0., 0.], [0., y, 0.], [0., 0., b]],
+ };
+ Matrix::multiply(
+ bradford_matrix_inv,
+ Matrix::multiply(white_adaption, bradford_matrix),
+ )
+}
+#[no_mangle]
+pub extern "C" fn qcms_profile_precache_output_transform(profile: &mut Profile) {
+ /* we only support precaching on rgb profiles */
+ if profile.color_space != RGB_SIGNATURE {
+ return;
+ }
+ if SUPPORTS_ICCV4.load(Ordering::Relaxed) {
+ /* don't precache since we will use the B2A LUT */
+ if profile.B2A0.is_some() {
+ return;
+ }
+ /* don't precache since we will use the mBA LUT */
+ if profile.mBA.is_some() {
+ return;
+ }
+ }
+ /* don't precache if we do not have the TRC curves */
+ if profile.redTRC.is_none() || profile.greenTRC.is_none() || profile.blueTRC.is_none() {
+ return;
+ }
+ if profile.output_table_r.is_none() {
+ let mut output_table_r = precache_create();
+ if compute_precache(
+ profile.redTRC.as_deref().unwrap(),
+ &mut Arc::get_mut(&mut output_table_r).unwrap().data,
+ ) {
+ profile.output_table_r = Some(output_table_r);
+ }
+ }
+ if profile.output_table_g.is_none() {
+ let mut output_table_g = precache_create();
+ if compute_precache(
+ profile.greenTRC.as_deref().unwrap(),
+ &mut Arc::get_mut(&mut output_table_g).unwrap().data,
+ ) {
+ profile.output_table_g = Some(output_table_g);
+ }
+ }
+ if profile.output_table_b.is_none() {
+ let mut output_table_b = precache_create();
+ if compute_precache(
+ profile.blueTRC.as_deref().unwrap(),
+ &mut Arc::get_mut(&mut output_table_b).unwrap().data,
+ ) {
+ profile.output_table_b = Some(output_table_b);
+ }
+ };
+}
+/* Replace the current transformation with a LUT transformation using a given number of sample points */
+fn transform_precacheLUT_float(
+ mut transform: Box<qcms_transform>,
+ input: &Profile,
+ output: &Profile,
+ samples: i32,
+ in_type: DataType,
+) -> Option<Box<qcms_transform>> {
+ /* The range between which 2 consecutive sample points can be used to interpolate */
+ let lutSize: u32 = (3 * samples * samples * samples) as u32;
+
+ let mut src = Vec::with_capacity(lutSize as usize);
+ let dest = vec![0.; lutSize as usize];
+ /* Prepare a list of points we want to sample */
+ for x in 0..samples {
+ for y in 0..samples {
+ for z in 0..samples {
+ src.push(x as f32 / (samples - 1) as f32);
+ src.push(y as f32 / (samples - 1) as f32);
+ src.push(z as f32 / (samples - 1) as f32);
+ }
+ }
+ }
+ let lut = chain_transform(input, output, src, dest, lutSize as usize);
+ if let Some(lut) = lut {
+ (*transform).clut = Some(lut);
+ (*transform).grid_size = samples as u16;
+ if in_type == RGBA8 {
+ (*transform).transform_fn = Some(qcms_transform_data_tetra_clut_rgba)
+ } else if in_type == BGRA8 {
+ (*transform).transform_fn = Some(qcms_transform_data_tetra_clut_bgra)
+ } else if in_type == RGB8 {
+ (*transform).transform_fn = Some(qcms_transform_data_tetra_clut_rgb)
+ }
+ debug_assert!((*transform).transform_fn.is_some());
+ } else {
+ return None;
+ }
+
+ Some(transform)
+}
+
+fn transform_precacheLUT_cmyk_float(
+ mut transform: Box<qcms_transform>,
+ input: &Profile,
+ output: &Profile,
+ samples: i32,
+ in_type: DataType,
+) -> Option<Box<qcms_transform>> {
+ /* The range between which 2 consecutive sample points can be used to interpolate */
+ let lutSize: u32 = (4 * samples * samples * samples * samples) as u32;
+
+ let mut src = Vec::with_capacity(lutSize as usize);
+ let dest = vec![0.; lutSize as usize];
+ /* Prepare a list of points we want to sample */
+ for k in 0..samples {
+ for c in 0..samples {
+ for m in 0..samples {
+ for y in 0..samples {
+ src.push(c as f32 / (samples - 1) as f32);
+ src.push(m as f32 / (samples - 1) as f32);
+ src.push(y as f32 / (samples - 1) as f32);
+ src.push(k as f32 / (samples - 1) as f32);
+ }
+ }
+ }
+ }
+ let lut = chain_transform(input, output, src, dest, lutSize as usize);
+ if let Some(lut) = lut {
+ transform.clut = Some(lut);
+ transform.grid_size = samples as u16;
+ assert!(in_type == DataType::CMYK);
+ transform.transform_fn = Some(qcms_transform_data_tetra_clut_cmyk)
+ } else {
+ return None;
+ }
+
+ Some(transform)
+}
+
+pub fn transform_create(
+ input: &Profile,
+ in_type: DataType,
+ output: &Profile,
+ out_type: DataType,
+ _intent: Intent,
+) -> Option<Box<qcms_transform>> {
+ // Ensure the requested input and output types make sense.
+ let matching_format = match (in_type, out_type) {
+ (RGB8, RGB8) => true,
+ (RGBA8, RGBA8) => true,
+ (BGRA8, BGRA8) => true,
+ (Gray8, out_type) => matches!(out_type, RGB8 | RGBA8 | BGRA8),
+ (GrayA8, out_type) => matches!(out_type, RGBA8 | BGRA8),
+ (CMYK, RGB8) => true,
+ _ => false,
+ };
+ if !matching_format {
+ debug_assert!(false, "input/output type");
+ return None;
+ }
+ let mut transform: Box<qcms_transform> = Box::new(Default::default());
+ let mut precache: bool = false;
+ if output.output_table_r.is_some()
+ && output.output_table_g.is_some()
+ && output.output_table_b.is_some()
+ {
+ precache = true
+ }
+ // This precache assumes RGB_SIGNATURE (fails on GRAY_SIGNATURE, for instance)
+ if SUPPORTS_ICCV4.load(Ordering::Relaxed)
+ && (in_type == RGB8 || in_type == RGBA8 || in_type == BGRA8 || in_type == CMYK)
+ && (input.A2B0.is_some()
+ || output.B2A0.is_some()
+ || input.mAB.is_some()
+ || output.mAB.is_some())
+ {
+ if in_type == CMYK {
+ return transform_precacheLUT_cmyk_float(transform, input, output, 17, in_type);
+ }
+ // Precache the transformation to a CLUT 33x33x33 in size.
+ // 33 is used by many profiles and works well in pratice.
+ // This evenly divides 256 into blocks of 8x8x8.
+ // TODO For transforming small data sets of about 200x200 or less
+ // precaching should be avoided.
+ let result = transform_precacheLUT_float(transform, input, output, 33, in_type);
+ debug_assert!(result.is_some(), "precacheLUT failed");
+ return result;
+ }
+ if precache {
+ transform.output_table_r = Some(Arc::clone(output.output_table_r.as_ref().unwrap()));
+ transform.output_table_g = Some(Arc::clone(output.output_table_g.as_ref().unwrap()));
+ transform.output_table_b = Some(Arc::clone(output.output_table_b.as_ref().unwrap()));
+ } else {
+ if output.redTRC.is_none() || output.greenTRC.is_none() || output.blueTRC.is_none() {
+ return None;
+ }
+ transform.output_gamma_lut_r = build_output_lut(output.redTRC.as_deref().unwrap());
+ transform.output_gamma_lut_g = build_output_lut(output.greenTRC.as_deref().unwrap());
+ transform.output_gamma_lut_b = build_output_lut(output.blueTRC.as_deref().unwrap());
+
+ if transform.output_gamma_lut_r.is_none()
+ || transform.output_gamma_lut_g.is_none()
+ || transform.output_gamma_lut_b.is_none()
+ {
+ return None;
+ }
+ }
+ if input.color_space == RGB_SIGNATURE {
+ if precache {
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ if is_x86_feature_detected!("avx") {
+ if in_type == RGB8 {
+ transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_avx)
+ } else if in_type == RGBA8 {
+ transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_avx)
+ } else if in_type == BGRA8 {
+ transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_avx)
+ }
+ } else if cfg!(not(miri)) && is_x86_feature_detected!("sse2") {
+ if in_type == RGB8 {
+ transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_sse2)
+ } else if in_type == RGBA8 {
+ transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_sse2)
+ } else if in_type == BGRA8 {
+ transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_sse2)
+ }
+ }
+
+ #[cfg(all(target_arch = "arm", feature = "neon"))]
+ let neon_supported = is_arm_feature_detected!("neon");
+ #[cfg(all(target_arch = "aarch64", feature = "neon"))]
+ let neon_supported = is_aarch64_feature_detected!("neon");
+
+ #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "neon"))]
+ if neon_supported {
+ if in_type == RGB8 {
+ transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_neon)
+ } else if in_type == RGBA8 {
+ transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_neon)
+ } else if in_type == BGRA8 {
+ transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_neon)
+ }
+ }
+
+ if transform.transform_fn.is_none() {
+ if in_type == RGB8 {
+ transform.transform_fn = Some(qcms_transform_data_rgb_out_lut_precache)
+ } else if in_type == RGBA8 {
+ transform.transform_fn = Some(qcms_transform_data_rgba_out_lut_precache)
+ } else if in_type == BGRA8 {
+ transform.transform_fn = Some(qcms_transform_data_bgra_out_lut_precache)
+ }
+ }
+ } else if in_type == RGB8 {
+ transform.transform_fn = Some(qcms_transform_data_rgb_out_lut)
+ } else if in_type == RGBA8 {
+ transform.transform_fn = Some(qcms_transform_data_rgba_out_lut)
+ } else if in_type == BGRA8 {
+ transform.transform_fn = Some(qcms_transform_data_bgra_out_lut)
+ }
+ //XXX: avoid duplicating tables if we can
+ transform.input_gamma_table_r = build_input_gamma_table(input.redTRC.as_deref());
+ transform.input_gamma_table_g = build_input_gamma_table(input.greenTRC.as_deref());
+ transform.input_gamma_table_b = build_input_gamma_table(input.blueTRC.as_deref());
+ if transform.input_gamma_table_r.is_none()
+ || transform.input_gamma_table_g.is_none()
+ || transform.input_gamma_table_b.is_none()
+ {
+ return None;
+ }
+ /* build combined colorant matrix */
+
+ let in_matrix: Matrix = build_colorant_matrix(input);
+ let mut out_matrix: Matrix = build_colorant_matrix(output);
+ out_matrix = out_matrix.invert()?;
+
+ let result_0: Matrix = Matrix::multiply(out_matrix, in_matrix);
+ /* check for NaN values in the matrix and bail if we find any */
+ let mut i: u32 = 0;
+ while i < 3 {
+ let mut j: u32 = 0;
+ while j < 3 {
+ #[allow(clippy::eq_op, clippy::float_cmp)]
+ if result_0.m[i as usize][j as usize].is_nan() {
+ return None;
+ }
+ j += 1
+ }
+ i += 1
+ }
+ /* store the results in column major mode
+ * this makes doing the multiplication with sse easier */
+ transform.matrix[0][0] = result_0.m[0][0];
+ transform.matrix[1][0] = result_0.m[0][1];
+ transform.matrix[2][0] = result_0.m[0][2];
+ transform.matrix[0][1] = result_0.m[1][0];
+ transform.matrix[1][1] = result_0.m[1][1];
+ transform.matrix[2][1] = result_0.m[1][2];
+ transform.matrix[0][2] = result_0.m[2][0];
+ transform.matrix[1][2] = result_0.m[2][1];
+ transform.matrix[2][2] = result_0.m[2][2]
+ } else if input.color_space == GRAY_SIGNATURE {
+ transform.input_gamma_table_gray = build_input_gamma_table(input.grayTRC.as_deref());
+ transform.input_gamma_table_gray.as_ref()?;
+ if precache {
+ if out_type == RGB8 {
+ transform.transform_fn = Some(qcms_transform_data_gray_out_precache)
+ } else if out_type == RGBA8 {
+ if in_type == Gray8 {
+ transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_precache)
+ } else {
+ transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_precache)
+ }
+ } else if out_type == BGRA8 {
+ if in_type == Gray8 {
+ transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_precache)
+ } else {
+ transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_precache)
+ }
+ }
+ } else if out_type == RGB8 {
+ transform.transform_fn = Some(qcms_transform_data_gray_out_lut)
+ } else if out_type == RGBA8 {
+ if in_type == Gray8 {
+ transform.transform_fn = Some(qcms_transform_data_gray_rgba_out_lut)
+ } else {
+ transform.transform_fn = Some(qcms_transform_data_graya_rgba_out_lut)
+ }
+ } else if out_type == BGRA8 {
+ if in_type == Gray8 {
+ transform.transform_fn = Some(qcms_transform_data_gray_bgra_out_lut)
+ } else {
+ transform.transform_fn = Some(qcms_transform_data_graya_bgra_out_lut)
+ }
+ }
+ } else {
+ debug_assert!(false, "unexpected colorspace");
+ return None;
+ }
+ debug_assert!(transform.transform_fn.is_some());
+ Some(transform)
+}
+/// A transform from an input profile to an output one.
+pub struct Transform {
+ src_ty: DataType,
+ dst_ty: DataType,
+ xfm: Box<qcms_transform>,
+}
+
+impl Transform {
+ /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent`
+ pub fn new(input: &Profile, output: &Profile, ty: DataType, intent: Intent) -> Option<Self> {
+ transform_create(input, ty, output, ty, intent).map(|xfm| Transform {
+ src_ty: ty,
+ dst_ty: ty,
+ xfm,
+ })
+ }
+
+ /// Create a new transform from `input` to `output` for pixels of `DataType` `ty` with `intent`
+ pub fn new_to(
+ input: &Profile,
+ output: &Profile,
+ src_ty: DataType,
+ dst_ty: DataType,
+ intent: Intent,
+ ) -> Option<Self> {
+ transform_create(input, src_ty, output, dst_ty, intent).map(|xfm| Transform {
+ src_ty,
+ dst_ty,
+ xfm,
+ })
+ }
+
+ /// Apply the color space transform to `data`
+ pub fn apply(&self, data: &mut [u8]) {
+ if data.len() % self.src_ty.bytes_per_pixel() != 0 {
+ panic!(
+ "incomplete pixels: should be a multiple of {} got {}",
+ self.src_ty.bytes_per_pixel(),
+ data.len()
+ )
+ }
+ unsafe {
+ self.xfm.transform_fn.expect("non-null function pointer")(
+ &*self.xfm,
+ data.as_ptr(),
+ data.as_mut_ptr(),
+ data.len() / self.src_ty.bytes_per_pixel(),
+ );
+ }
+ }
+
+ /// Apply the color space transform to `data`
+ pub fn convert(&self, src: &[u8], dst: &mut [u8]) {
+ if src.len() % self.src_ty.bytes_per_pixel() != 0 {
+ panic!(
+ "incomplete pixels: should be a multiple of {} got {}",
+ self.src_ty.bytes_per_pixel(),
+ src.len()
+ )
+ }
+ if dst.len() % self.dst_ty.bytes_per_pixel() != 0 {
+ panic!(
+ "incomplete pixels: should be a multiple of {} got {}",
+ self.dst_ty.bytes_per_pixel(),
+ dst.len()
+ )
+ }
+ assert_eq!(
+ src.len() / self.src_ty.bytes_per_pixel(),
+ dst.len() / self.dst_ty.bytes_per_pixel()
+ );
+ unsafe {
+ self.xfm.transform_fn.expect("non-null function pointer")(
+ &*self.xfm,
+ src.as_ptr(),
+ dst.as_mut_ptr(),
+ src.len() / self.src_ty.bytes_per_pixel(),
+ );
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn qcms_enable_iccv4() {
+ SUPPORTS_ICCV4.store(true, Ordering::Relaxed);
+}
diff --git a/gfx/qcms/src/transform_avx.rs b/gfx/qcms/src/transform_avx.rs
new file mode 100644
index 0000000000..3a000e56ba
--- /dev/null
+++ b/gfx/qcms/src/transform_avx.rs
@@ -0,0 +1,228 @@
+use crate::transform::{qcms_transform, Format, BGRA, CLAMPMAXVAL, FLOATSCALE, RGB, RGBA};
+#[cfg(target_arch = "x86")]
+pub use std::arch::x86::{
+ __m128, __m128i, __m256, __m256i, _mm256_add_ps, _mm256_broadcast_ps, _mm256_castps128_ps256,
+ _mm256_castps256_ps128, _mm256_cvtps_epi32, _mm256_insertf128_ps, _mm256_max_ps, _mm256_min_ps,
+ _mm256_mul_ps, _mm256_set1_ps, _mm256_setzero_ps, _mm256_store_si256, _mm_add_ps,
+ _mm_broadcast_ss, _mm_cvtps_epi32, _mm_max_ps, _mm_min_ps, _mm_mul_ps, _mm_store_si128,
+};
+#[cfg(target_arch = "x86_64")]
+pub use std::arch::x86_64::{
+ __m128, __m128i, __m256, __m256i, _mm256_add_ps, _mm256_broadcast_ps, _mm256_castps128_ps256,
+ _mm256_castps256_ps128, _mm256_cvtps_epi32, _mm256_insertf128_ps, _mm256_max_ps, _mm256_min_ps,
+ _mm256_mul_ps, _mm256_set1_ps, _mm256_setzero_ps, _mm256_store_si256, _mm_add_ps,
+ _mm_broadcast_ss, _mm_cvtps_epi32, _mm_max_ps, _mm_min_ps, _mm_mul_ps, _mm_store_si128,
+};
+
+#[repr(align(32))]
+struct Output([u32; 8]);
+
+#[target_feature(enable = "avx")]
+unsafe extern "C" fn qcms_transform_data_template_lut_avx<F: Format>(
+ transform: &qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ mut length: usize,
+) {
+ let mat: *const [f32; 4] = (*transform).matrix.as_ptr();
+ let mut input: Output = std::mem::zeroed();
+ /* share input and output locations to save having to keep the
+ * locations in separate registers */
+ let output: *const u32 = &mut input as *mut Output as *mut u32;
+ /* deref *transform now to avoid it in loop */
+ let igtbl_r: *const f32 = (*transform).input_gamma_table_r.as_ref().unwrap().as_ptr();
+ let igtbl_g: *const f32 = (*transform).input_gamma_table_g.as_ref().unwrap().as_ptr();
+ let igtbl_b: *const f32 = (*transform).input_gamma_table_b.as_ref().unwrap().as_ptr();
+ /* deref *transform now to avoid it in loop */
+ let otdata_r: *const u8 = (*transform)
+ .output_table_r
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ let otdata_g: *const u8 = (*transform)
+ .output_table_g
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ let otdata_b: *const u8 = (*transform)
+ .output_table_b
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ /* input matrix values never change */
+ let mat0: __m256 = _mm256_broadcast_ps(&*((*mat.offset(0isize)).as_ptr() as *const __m128));
+ let mat1: __m256 = _mm256_broadcast_ps(&*((*mat.offset(1isize)).as_ptr() as *const __m128));
+ let mat2: __m256 = _mm256_broadcast_ps(&*((*mat.offset(2isize)).as_ptr() as *const __m128));
+ /* these values don't change, either */
+ let max: __m256 = _mm256_set1_ps(CLAMPMAXVAL);
+ let min: __m256 = _mm256_setzero_ps();
+ let scale: __m256 = _mm256_set1_ps(FLOATSCALE);
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+ /* working variables */
+ let mut vec_r: __m256 = _mm256_setzero_ps();
+ let mut vec_g: __m256 = _mm256_setzero_ps();
+ let mut vec_b: __m256 = _mm256_setzero_ps();
+ let mut result: __m256;
+ let mut vec_r0: __m128;
+ let mut vec_g0: __m128;
+ let mut vec_b0: __m128;
+ let mut vec_r1: __m128;
+ let mut vec_g1: __m128;
+ let mut vec_b1: __m128;
+ let mut alpha1: u8 = 0;
+ let mut alpha2: u8 = 0;
+ /* CYA */
+ if length == 0 {
+ return;
+ }
+ /* If there are at least 2 pixels, then we can load their components into
+ a single 256-bit register for processing. */
+ if length > 1 {
+ vec_r0 = _mm_broadcast_ss(&*igtbl_r.offset(*src.add(F::kRIndex) as isize));
+ vec_g0 = _mm_broadcast_ss(&*igtbl_g.offset(*src.add(F::kGIndex) as isize));
+ vec_b0 = _mm_broadcast_ss(&*igtbl_b.offset(*src.add(F::kBIndex) as isize));
+ vec_r1 =
+ _mm_broadcast_ss(&*igtbl_r.offset(*src.add(F::kRIndex + components as usize) as isize));
+ vec_g1 =
+ _mm_broadcast_ss(&*igtbl_g.offset(*src.add(F::kGIndex + components as usize) as isize));
+ vec_b1 =
+ _mm_broadcast_ss(&*igtbl_b.offset(*src.add(F::kBIndex + components as usize) as isize));
+ vec_r = _mm256_insertf128_ps(_mm256_castps128_ps256(vec_r0), vec_r1, 1);
+ vec_g = _mm256_insertf128_ps(_mm256_castps128_ps256(vec_g0), vec_g1, 1);
+ vec_b = _mm256_insertf128_ps(_mm256_castps128_ps256(vec_b0), vec_b1, 1);
+ if F::kAIndex != 0xff {
+ alpha1 = *src.add(F::kAIndex);
+ alpha2 = *src.add(F::kAIndex + components as usize)
+ }
+ }
+ /* If there are at least 4 pixels, then we can iterate and preload the
+ next 2 while we store the result of the current 2. */
+ while length > 3 {
+ /* Ensure we are pointing at the next 2 pixels for the next load. */
+ src = src.offset((2 * components) as isize);
+ /* gamma * matrix */
+ vec_r = _mm256_mul_ps(vec_r, mat0);
+ vec_g = _mm256_mul_ps(vec_g, mat1);
+ vec_b = _mm256_mul_ps(vec_b, mat2);
+ /* store alpha for these pixels; load alpha for next two */
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha1;
+ *dest.add(F::kAIndex + components as usize) = alpha2;
+ alpha1 = *src.add(F::kAIndex);
+ alpha2 = *src.add(F::kAIndex + components as usize)
+ }
+ /* crunch, crunch, crunch */
+ vec_r = _mm256_add_ps(vec_r, _mm256_add_ps(vec_g, vec_b));
+ vec_r = _mm256_max_ps(min, vec_r);
+ vec_r = _mm256_min_ps(max, vec_r);
+ result = _mm256_mul_ps(vec_r, scale);
+ /* store calc'd output tables indices */
+ _mm256_store_si256(output as *mut __m256i, _mm256_cvtps_epi32(result));
+ /* load gamma values for next loop while store completes */
+ vec_r0 = _mm_broadcast_ss(&*igtbl_r.offset(*src.add(F::kRIndex) as isize));
+ vec_g0 = _mm_broadcast_ss(&*igtbl_g.offset(*src.add(F::kGIndex) as isize));
+ vec_b0 = _mm_broadcast_ss(&*igtbl_b.offset(*src.add(F::kBIndex) as isize));
+ vec_r1 =
+ _mm_broadcast_ss(&*igtbl_r.offset(*src.add(F::kRIndex + components as usize) as isize));
+ vec_g1 =
+ _mm_broadcast_ss(&*igtbl_g.offset(*src.add(F::kGIndex + components as usize) as isize));
+ vec_b1 =
+ _mm_broadcast_ss(&*igtbl_b.offset(*src.add(F::kBIndex + components as usize) as isize));
+ vec_r = _mm256_insertf128_ps(_mm256_castps128_ps256(vec_r0), vec_r1, 1);
+ vec_g = _mm256_insertf128_ps(_mm256_castps128_ps256(vec_g0), vec_g1, 1);
+ vec_b = _mm256_insertf128_ps(_mm256_castps128_ps256(vec_b0), vec_b1, 1);
+ /* use calc'd indices to output RGB values */
+ *dest.add(F::kRIndex) = *otdata_r.offset(*output.offset(0isize) as isize);
+ *dest.add(F::kGIndex) = *otdata_g.offset(*output.offset(1isize) as isize);
+ *dest.add(F::kBIndex) = *otdata_b.offset(*output.offset(2isize) as isize);
+ *dest.add(F::kRIndex + components as usize) =
+ *otdata_r.offset(*output.offset(4isize) as isize);
+ *dest.add(F::kGIndex + components as usize) =
+ *otdata_g.offset(*output.offset(5isize) as isize);
+ *dest.add(F::kBIndex + components as usize) =
+ *otdata_b.offset(*output.offset(6isize) as isize);
+ dest = dest.offset((2 * components) as isize);
+ length -= 2
+ }
+ /* There are 0-3 pixels remaining. If there are 2-3 remaining, then we know
+ we have already populated the necessary registers to start the transform. */
+ if length > 1 {
+ vec_r = _mm256_mul_ps(vec_r, mat0);
+ vec_g = _mm256_mul_ps(vec_g, mat1);
+ vec_b = _mm256_mul_ps(vec_b, mat2);
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha1;
+ *dest.add(F::kAIndex + components as usize) = alpha2
+ }
+ vec_r = _mm256_add_ps(vec_r, _mm256_add_ps(vec_g, vec_b));
+ vec_r = _mm256_max_ps(min, vec_r);
+ vec_r = _mm256_min_ps(max, vec_r);
+ result = _mm256_mul_ps(vec_r, scale);
+ _mm256_store_si256(output as *mut __m256i, _mm256_cvtps_epi32(result));
+ *dest.add(F::kRIndex) = *otdata_r.offset(*output.offset(0isize) as isize);
+ *dest.add(F::kGIndex) = *otdata_g.offset(*output.offset(1isize) as isize);
+ *dest.add(F::kBIndex) = *otdata_b.offset(*output.offset(2isize) as isize);
+ *dest.add(F::kRIndex + components as usize) =
+ *otdata_r.offset(*output.offset(4isize) as isize);
+ *dest.add(F::kGIndex + components as usize) =
+ *otdata_g.offset(*output.offset(5isize) as isize);
+ *dest.add(F::kBIndex + components as usize) =
+ *otdata_b.offset(*output.offset(6isize) as isize);
+ src = src.offset((2 * components) as isize);
+ dest = dest.offset((2 * components) as isize);
+ length -= 2
+ }
+ /* There may be 0-1 pixels remaining. */
+ if length == 1 {
+ vec_r0 = _mm_broadcast_ss(&*igtbl_r.offset(*src.add(F::kRIndex) as isize));
+ vec_g0 = _mm_broadcast_ss(&*igtbl_g.offset(*src.add(F::kGIndex) as isize));
+ vec_b0 = _mm_broadcast_ss(&*igtbl_b.offset(*src.add(F::kBIndex) as isize));
+ vec_r0 = _mm_mul_ps(vec_r0, _mm256_castps256_ps128(mat0));
+ vec_g0 = _mm_mul_ps(vec_g0, _mm256_castps256_ps128(mat1));
+ vec_b0 = _mm_mul_ps(vec_b0, _mm256_castps256_ps128(mat2));
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = *src.add(F::kAIndex)
+ }
+ vec_r0 = _mm_add_ps(vec_r0, _mm_add_ps(vec_g0, vec_b0));
+ vec_r0 = _mm_max_ps(_mm256_castps256_ps128(min), vec_r0);
+ vec_r0 = _mm_min_ps(_mm256_castps256_ps128(max), vec_r0);
+ vec_r0 = _mm_mul_ps(vec_r0, _mm256_castps256_ps128(scale));
+ _mm_store_si128(output as *mut __m128i, _mm_cvtps_epi32(vec_r0));
+ *dest.add(F::kRIndex) = *otdata_r.offset(*output.offset(0isize) as isize);
+ *dest.add(F::kGIndex) = *otdata_g.offset(*output.offset(1isize) as isize);
+ *dest.add(F::kBIndex) = *otdata_b.offset(*output.offset(2isize) as isize)
+ };
+}
+#[no_mangle]
+#[target_feature(enable = "avx")]
+pub unsafe fn qcms_transform_data_rgb_out_lut_avx(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_avx::<RGB>(transform, src, dest, length);
+}
+#[no_mangle]
+#[target_feature(enable = "avx")]
+pub unsafe fn qcms_transform_data_rgba_out_lut_avx(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_avx::<RGBA>(transform, src, dest, length);
+}
+#[no_mangle]
+#[target_feature(enable = "avx")]
+pub unsafe fn qcms_transform_data_bgra_out_lut_avx(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_avx::<BGRA>(transform, src, dest, length);
+}
diff --git a/gfx/qcms/src/transform_neon.rs b/gfx/qcms/src/transform_neon.rs
new file mode 100644
index 0000000000..b14b67107d
--- /dev/null
+++ b/gfx/qcms/src/transform_neon.rs
@@ -0,0 +1,155 @@
+use crate::transform::{qcms_transform, Format, BGRA, CLAMPMAXVAL, FLOATSCALE, RGB, RGBA};
+#[cfg(target_arch = "aarch64")]
+use core::arch::aarch64::{
+ float32x4_t, int32x4_t, vaddq_f32, vcvtq_s32_f32, vgetq_lane_s32, vld1q_dup_f32, vld1q_f32,
+ vmaxq_f32, vminq_f32, vmulq_f32,
+};
+#[cfg(target_arch = "arm")]
+use core::arch::arm::{
+ float32x4_t, int32x4_t, vaddq_f32, vcvtq_s32_f32, vgetq_lane_s32, vld1q_dup_f32, vld1q_f32,
+ vmaxq_f32, vminq_f32, vmulq_f32,
+};
+use std::mem::zeroed;
+
+#[target_feature(enable = "neon")]
+#[cfg_attr(target_arch = "arm", target_feature(enable = "v7"))]
+unsafe fn qcms_transform_data_template_lut_neon<F: Format>(
+ transform: &qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ mut length: usize,
+) {
+ let mat: *const [f32; 4] = (*transform).matrix.as_ptr();
+ /* deref *transform now to avoid it in loop */
+ let igtbl_r: *const f32 = (*transform).input_gamma_table_r.as_ref().unwrap().as_ptr();
+ let igtbl_g: *const f32 = (*transform).input_gamma_table_g.as_ref().unwrap().as_ptr();
+ let igtbl_b: *const f32 = (*transform).input_gamma_table_b.as_ref().unwrap().as_ptr();
+ /* deref *transform now to avoid it in loop */
+ let otdata_r: *const u8 = (*transform)
+ .output_table_r
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ let otdata_g: *const u8 = (*transform)
+ .output_table_g
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ let otdata_b: *const u8 = (*transform)
+ .output_table_b
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ /* input matrix values never change */
+ let mat0: float32x4_t = vld1q_f32((*mat.offset(0isize)).as_ptr());
+ let mat1: float32x4_t = vld1q_f32((*mat.offset(1isize)).as_ptr());
+ let mat2: float32x4_t = vld1q_f32((*mat.offset(2isize)).as_ptr());
+ /* these values don't change, either */
+ let max: float32x4_t = vld1q_dup_f32(&CLAMPMAXVAL);
+ let min: float32x4_t = zeroed();
+ let scale: float32x4_t = vld1q_dup_f32(&FLOATSCALE);
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+ /* working variables */
+ let mut vec_r: float32x4_t;
+ let mut vec_g: float32x4_t;
+ let mut vec_b: float32x4_t;
+ let mut result: int32x4_t;
+ let mut alpha: u8 = 0;
+ /* CYA */
+ if length == 0 {
+ return;
+ }
+ /* one pixel is handled outside of the loop */
+ length = length.wrapping_sub(1);
+ /* setup for transforming 1st pixel */
+ vec_r = vld1q_dup_f32(&*igtbl_r.offset(*src.offset(F::kRIndex as isize) as isize));
+ vec_g = vld1q_dup_f32(&*igtbl_g.offset(*src.offset(F::kGIndex as isize) as isize));
+ vec_b = vld1q_dup_f32(&*igtbl_b.offset(*src.offset(F::kBIndex as isize) as isize));
+ if F::kAIndex != 0xff {
+ alpha = *src.offset(F::kAIndex as isize)
+ }
+ src = src.offset(components as isize);
+ let mut i: u32 = 0;
+ while (i as usize) < length {
+ /* gamma * matrix */
+ vec_r = vmulq_f32(vec_r, mat0);
+ vec_g = vmulq_f32(vec_g, mat1);
+ vec_b = vmulq_f32(vec_b, mat2);
+ /* store alpha for this pixel; load alpha for next */
+ if F::kAIndex != 0xff {
+ *dest.offset(F::kAIndex as isize) = alpha;
+ alpha = *src.offset(F::kAIndex as isize)
+ }
+ /* crunch, crunch, crunch */
+ vec_r = vaddq_f32(vec_r, vaddq_f32(vec_g, vec_b));
+ vec_r = vmaxq_f32(min, vec_r);
+ vec_r = vminq_f32(max, vec_r);
+ result = vcvtq_s32_f32(vmulq_f32(vec_r, scale));
+
+ /* use calc'd indices to output RGB values */
+ *dest.offset(F::kRIndex as isize) = *otdata_r.offset(vgetq_lane_s32(result, 0) as isize);
+ *dest.offset(F::kGIndex as isize) = *otdata_g.offset(vgetq_lane_s32(result, 1) as isize);
+ *dest.offset(F::kBIndex as isize) = *otdata_b.offset(vgetq_lane_s32(result, 2) as isize);
+
+ /* load gamma values for next loop while store completes */
+ vec_r = vld1q_dup_f32(&*igtbl_r.offset(*src.offset(F::kRIndex as isize) as isize));
+ vec_g = vld1q_dup_f32(&*igtbl_g.offset(*src.offset(F::kGIndex as isize) as isize));
+ vec_b = vld1q_dup_f32(&*igtbl_b.offset(*src.offset(F::kBIndex as isize) as isize));
+
+ dest = dest.offset(components as isize);
+ src = src.offset(components as isize);
+ i = i.wrapping_add(1)
+ }
+ /* handle final (maybe only) pixel */
+ vec_r = vmulq_f32(vec_r, mat0);
+ vec_g = vmulq_f32(vec_g, mat1);
+ vec_b = vmulq_f32(vec_b, mat2);
+ if F::kAIndex != 0xff {
+ *dest.offset(F::kAIndex as isize) = alpha
+ }
+ vec_r = vaddq_f32(vec_r, vaddq_f32(vec_g, vec_b));
+ vec_r = vmaxq_f32(min, vec_r);
+ vec_r = vminq_f32(max, vec_r);
+ result = vcvtq_s32_f32(vmulq_f32(vec_r, scale));
+
+ *dest.offset(F::kRIndex as isize) = *otdata_r.offset(vgetq_lane_s32(result, 0) as isize);
+ *dest.offset(F::kGIndex as isize) = *otdata_g.offset(vgetq_lane_s32(result, 1) as isize);
+ *dest.offset(F::kBIndex as isize) = *otdata_b.offset(vgetq_lane_s32(result, 2) as isize);
+}
+#[no_mangle]
+#[target_feature(enable = "neon")]
+#[cfg_attr(target_arch = "arm", target_feature(enable = "v7"))]
+pub unsafe fn qcms_transform_data_rgb_out_lut_neon(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_neon::<RGB>(transform, src, dest, length);
+}
+#[no_mangle]
+#[target_feature(enable = "neon")]
+#[cfg_attr(target_arch = "arm", target_feature(enable = "v7"))]
+pub unsafe fn qcms_transform_data_rgba_out_lut_neon(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_neon::<RGBA>(transform, src, dest, length);
+}
+
+#[no_mangle]
+#[target_feature(enable = "neon")]
+#[cfg_attr(target_arch = "arm", target_feature(enable = "v7"))]
+pub unsafe fn qcms_transform_data_bgra_out_lut_neon(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_neon::<BGRA>(transform, src, dest, length);
+}
diff --git a/gfx/qcms/src/transform_sse2.rs b/gfx/qcms/src/transform_sse2.rs
new file mode 100644
index 0000000000..f6bccaadc3
--- /dev/null
+++ b/gfx/qcms/src/transform_sse2.rs
@@ -0,0 +1,159 @@
+use crate::transform::{qcms_transform, Format, BGRA, CLAMPMAXVAL, FLOATSCALE, RGB, RGBA};
+#[cfg(target_arch = "x86")]
+pub use std::arch::x86::{
+ __m128, __m128i, _mm_add_ps, _mm_cvtps_epi32, _mm_load_ps, _mm_load_ss, _mm_max_ps, _mm_min_ps,
+ _mm_mul_ps, _mm_set1_ps, _mm_setzero_ps, _mm_shuffle_ps, _mm_store_si128,
+};
+#[cfg(target_arch = "x86_64")]
+pub use std::arch::x86_64::{
+ __m128, __m128i, _mm_add_ps, _mm_cvtps_epi32, _mm_load_ps, _mm_load_ss, _mm_max_ps, _mm_min_ps,
+ _mm_mul_ps, _mm_set1_ps, _mm_setzero_ps, _mm_shuffle_ps, _mm_store_si128,
+};
+
+#[repr(align(16))]
+struct Output([u32; 4]);
+
+unsafe extern "C" fn qcms_transform_data_template_lut_sse2<F: Format>(
+ transform: &qcms_transform,
+ mut src: *const u8,
+ mut dest: *mut u8,
+ mut length: usize,
+) {
+ let mat: *const [f32; 4] = (*transform).matrix.as_ptr();
+ let mut input: Output = std::mem::zeroed();
+ /* share input and output locations to save having to keep the
+ * locations in separate registers */
+ let output: *const u32 = &mut input as *mut Output as *mut u32;
+ /* deref *transform now to avoid it in loop */
+ let igtbl_r: *const f32 = (*transform).input_gamma_table_r.as_ref().unwrap().as_ptr();
+ let igtbl_g: *const f32 = (*transform).input_gamma_table_g.as_ref().unwrap().as_ptr();
+ let igtbl_b: *const f32 = (*transform).input_gamma_table_b.as_ref().unwrap().as_ptr();
+ /* deref *transform now to avoid it in loop */
+ let otdata_r: *const u8 = (*transform)
+ .output_table_r
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ let otdata_g: *const u8 = (*transform)
+ .output_table_g
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ let otdata_b: *const u8 = (*transform)
+ .output_table_b
+ .as_deref()
+ .unwrap()
+ .data
+ .as_ptr();
+ /* input matrix values never change */
+ let mat0: __m128 = _mm_load_ps((*mat.offset(0isize)).as_ptr());
+ let mat1: __m128 = _mm_load_ps((*mat.offset(1isize)).as_ptr());
+ let mat2: __m128 = _mm_load_ps((*mat.offset(2isize)).as_ptr());
+ /* these values don't change, either */
+ let max: __m128 = _mm_set1_ps(CLAMPMAXVAL);
+ let min: __m128 = _mm_setzero_ps();
+ let scale: __m128 = _mm_set1_ps(FLOATSCALE);
+ let components: u32 = if F::kAIndex == 0xff { 3 } else { 4 } as u32;
+ /* working variables */
+ let mut vec_r: __m128;
+ let mut vec_g: __m128;
+ let mut vec_b: __m128;
+ let mut result: __m128;
+ let mut alpha: u8 = 0;
+ /* CYA */
+ if length == 0 {
+ return;
+ }
+ /* one pixel is handled outside of the loop */
+ length -= 1;
+ /* setup for transforming 1st pixel */
+ vec_r = _mm_load_ss(&*igtbl_r.offset(*src.add(F::kRIndex) as isize));
+ vec_g = _mm_load_ss(&*igtbl_g.offset(*src.add(F::kGIndex) as isize));
+ vec_b = _mm_load_ss(&*igtbl_b.offset(*src.add(F::kBIndex) as isize));
+ if F::kAIndex != 0xff {
+ alpha = *src.add(F::kAIndex)
+ }
+ src = src.offset(components as isize);
+ let mut i: u32 = 0;
+ while (i as usize) < length {
+ /* position values from gamma tables */
+ vec_r = _mm_shuffle_ps(vec_r, vec_r, 0);
+ vec_g = _mm_shuffle_ps(vec_g, vec_g, 0);
+ vec_b = _mm_shuffle_ps(vec_b, vec_b, 0);
+ /* gamma * matrix */
+ vec_r = _mm_mul_ps(vec_r, mat0);
+ vec_g = _mm_mul_ps(vec_g, mat1);
+ vec_b = _mm_mul_ps(vec_b, mat2);
+ /* store alpha for this pixel; load alpha for next */
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha;
+ alpha = *src.add(F::kAIndex)
+ }
+ /* crunch, crunch, crunch */
+ vec_r = _mm_add_ps(vec_r, _mm_add_ps(vec_g, vec_b));
+ vec_r = _mm_max_ps(min, vec_r);
+ vec_r = _mm_min_ps(max, vec_r);
+ result = _mm_mul_ps(vec_r, scale);
+ /* store calc'd output tables indices */
+ _mm_store_si128(output as *mut __m128i, _mm_cvtps_epi32(result));
+ /* load gamma values for next loop while store completes */
+ vec_r = _mm_load_ss(&*igtbl_r.offset(*src.add(F::kRIndex) as isize));
+ vec_g = _mm_load_ss(&*igtbl_g.offset(*src.add(F::kGIndex) as isize));
+ vec_b = _mm_load_ss(&*igtbl_b.offset(*src.add(F::kBIndex) as isize));
+ src = src.offset(components as isize);
+ /* use calc'd indices to output RGB values */
+ *dest.add(F::kRIndex) = *otdata_r.offset(*output.offset(0isize) as isize);
+ *dest.add(F::kGIndex) = *otdata_g.offset(*output.offset(1isize) as isize);
+ *dest.add(F::kBIndex) = *otdata_b.offset(*output.offset(2isize) as isize);
+ dest = dest.offset(components as isize);
+ i += 1
+ }
+ /* handle final (maybe only) pixel */
+ vec_r = _mm_shuffle_ps(vec_r, vec_r, 0);
+ vec_g = _mm_shuffle_ps(vec_g, vec_g, 0);
+ vec_b = _mm_shuffle_ps(vec_b, vec_b, 0);
+ vec_r = _mm_mul_ps(vec_r, mat0);
+ vec_g = _mm_mul_ps(vec_g, mat1);
+ vec_b = _mm_mul_ps(vec_b, mat2);
+ if F::kAIndex != 0xff {
+ *dest.add(F::kAIndex) = alpha
+ }
+ vec_r = _mm_add_ps(vec_r, _mm_add_ps(vec_g, vec_b));
+ vec_r = _mm_max_ps(min, vec_r);
+ vec_r = _mm_min_ps(max, vec_r);
+ result = _mm_mul_ps(vec_r, scale);
+ _mm_store_si128(output as *mut __m128i, _mm_cvtps_epi32(result));
+ *dest.add(F::kRIndex) = *otdata_r.offset(*output.offset(0isize) as isize);
+ *dest.add(F::kGIndex) = *otdata_g.offset(*output.offset(1isize) as isize);
+ *dest.add(F::kBIndex) = *otdata_b.offset(*output.offset(2isize) as isize);
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_rgb_out_lut_sse2(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_sse2::<RGB>(transform, src, dest, length);
+}
+#[no_mangle]
+pub unsafe fn qcms_transform_data_rgba_out_lut_sse2(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_sse2::<RGBA>(transform, src, dest, length);
+}
+
+#[no_mangle]
+pub unsafe fn qcms_transform_data_bgra_out_lut_sse2(
+ transform: &qcms_transform,
+ src: *const u8,
+ dest: *mut u8,
+ length: usize,
+) {
+ qcms_transform_data_template_lut_sse2::<BGRA>(transform, src, dest, length);
+}
diff --git a/gfx/qcms/src/transform_util.rs b/gfx/qcms/src/transform_util.rs
new file mode 100644
index 0000000000..75fd2ca0e2
--- /dev/null
+++ b/gfx/qcms/src/transform_util.rs
@@ -0,0 +1,608 @@
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+use std::convert::TryInto;
+
+use crate::{
+ iccread::{curveType, Profile},
+ s15Fixed16Number_to_float,
+};
+use crate::{matrix::Matrix, transform::PRECACHE_OUTPUT_MAX, transform::PRECACHE_OUTPUT_SIZE};
+
+//XXX: could use a bettername
+pub type uint16_fract_t = u16;
+
+#[inline]
+fn u8Fixed8Number_to_float(x: u16) -> f32 {
+ // 0x0000 = 0.
+ // 0x0100 = 1.
+ // 0xffff = 255 + 255/256
+ (x as i32 as f64 / 256.0f64) as f32
+}
+#[inline]
+pub fn clamp_float(a: f32) -> f32 {
+ /* One would naturally write this function as the following:
+ if (a > 1.)
+ return 1.;
+ else if (a < 0)
+ return 0;
+ else
+ return a;
+
+ However, that version will let NaNs pass through which is undesirable
+ for most consumers.
+ */
+ if a > 1. {
+ 1.
+ } else if a >= 0. {
+ a
+ } else {
+ // a < 0 or a is NaN
+ 0.
+ }
+}
+/* value must be a value between 0 and 1 */
+//XXX: is the above a good restriction to have?
+// the output range of this functions is 0..1
+pub fn lut_interp_linear(mut input_value: f64, table: &[u16]) -> f32 {
+ input_value *= (table.len() - 1) as f64;
+
+ let upper: i32 = input_value.ceil() as i32;
+ let lower: i32 = input_value.floor() as i32;
+ let value: f32 = ((table[upper as usize] as f64) * (1. - (upper as f64 - input_value))
+ + (table[lower as usize] as f64 * (upper as f64 - input_value)))
+ as f32;
+ /* scale the value */
+ value * (1.0 / 65535.0)
+}
+/* same as above but takes and returns a uint16_t value representing a range from 0..1 */
+#[no_mangle]
+pub fn lut_interp_linear16(input_value: u16, table: &[u16]) -> u16 {
+ /* Start scaling input_value to the length of the array: 65535*(length-1).
+ * We'll divide out the 65535 next */
+ let mut value: u32 = (input_value as i32 * (table.len() as i32 - 1)) as u32; /* equivalent to ceil(value/65535) */
+ let upper: u32 = (value + 65534) / 65535; /* equivalent to floor(value/65535) */
+ let lower: u32 = value / 65535;
+ /* interp is the distance from upper to value scaled to 0..65535 */
+ let interp: u32 = value % 65535; // 0..65535*65535
+ value = (table[upper as usize] as u32 * interp
+ + table[lower as usize] as u32 * (65535 - interp))
+ / 65535;
+ value as u16
+}
+/* same as above but takes an input_value from 0..PRECACHE_OUTPUT_MAX
+ * and returns a uint8_t value representing a range from 0..1 */
+fn lut_interp_linear_precache_output(input_value: u32, table: &[u16]) -> u8 {
+ /* Start scaling input_value to the length of the array: PRECACHE_OUTPUT_MAX*(length-1).
+ * We'll divide out the PRECACHE_OUTPUT_MAX next */
+ let mut value: u32 = input_value * (table.len() - 1) as u32;
+ /* equivalent to ceil(value/PRECACHE_OUTPUT_MAX) */
+ let upper: u32 = (value + PRECACHE_OUTPUT_MAX as u32 - 1) / PRECACHE_OUTPUT_MAX as u32;
+ /* equivalent to floor(value/PRECACHE_OUTPUT_MAX) */
+ let lower: u32 = value / PRECACHE_OUTPUT_MAX as u32;
+ /* interp is the distance from upper to value scaled to 0..PRECACHE_OUTPUT_MAX */
+ let interp: u32 = value % PRECACHE_OUTPUT_MAX as u32;
+ /* the table values range from 0..65535 */
+ value = table[upper as usize] as u32 * interp
+ + table[lower as usize] as u32 * (PRECACHE_OUTPUT_MAX as u32 - interp); // 0..(65535*PRECACHE_OUTPUT_MAX)
+ /* round and scale */
+ value += (PRECACHE_OUTPUT_MAX * 65535 / 255 / 2) as u32; // scale to 0..255
+ value /= (PRECACHE_OUTPUT_MAX * 65535 / 255) as u32;
+ value as u8
+}
+/* value must be a value between 0 and 1 */
+//XXX: is the above a good restriction to have?
+pub fn lut_interp_linear_float(mut value: f32, table: &[f32]) -> f32 {
+ value *= (table.len() - 1) as f32;
+
+ let upper: i32 = value.ceil() as i32;
+ let lower: i32 = value.floor() as i32;
+ //XXX: can we be more performant here?
+ value = (table[upper as usize] as f64 * (1.0f64 - (upper as f32 - value) as f64)
+ + (table[lower as usize] * (upper as f32 - value)) as f64) as f32;
+ /* scale the value */
+ value
+}
+fn compute_curve_gamma_table_type1(gamma: u16) -> Box<[f32; 256]> {
+ let mut gamma_table = Vec::with_capacity(256);
+ let gamma_float: f32 = u8Fixed8Number_to_float(gamma);
+ for i in 0..256 {
+ // 0..1^(0..255 + 255/256) will always be between 0 and 1
+ gamma_table.push((i as f64 / 255.0f64).powf(gamma_float as f64) as f32);
+ }
+ gamma_table.into_boxed_slice().try_into().unwrap()
+}
+fn compute_curve_gamma_table_type2(table: &[u16]) -> Box<[f32; 256]> {
+ let mut gamma_table = Vec::with_capacity(256);
+ for i in 0..256 {
+ gamma_table.push(lut_interp_linear(i as f64 / 255.0f64, table));
+ }
+ gamma_table.into_boxed_slice().try_into().unwrap()
+}
+fn compute_curve_gamma_table_type_parametric(params: &[f32]) -> Box<[f32; 256]> {
+ let params = Param::new(params);
+ let mut gamma_table = Vec::with_capacity(256);
+ for i in 0..256 {
+ let X = i as f32 / 255.;
+ gamma_table.push(clamp_float(params.eval(X)));
+ }
+ gamma_table.into_boxed_slice().try_into().unwrap()
+}
+
+fn compute_curve_gamma_table_type0() -> Box<[f32; 256]> {
+ let mut gamma_table = Vec::with_capacity(256);
+ for i in 0..256 {
+ gamma_table.push((i as f64 / 255.0f64) as f32);
+ }
+ gamma_table.into_boxed_slice().try_into().unwrap()
+}
+pub(crate) fn build_input_gamma_table(TRC: Option<&curveType>) -> Option<Box<[f32; 256]>> {
+ let TRC = match TRC {
+ Some(TRC) => TRC,
+ None => return None,
+ };
+ Some(match TRC {
+ curveType::Parametric(params) => compute_curve_gamma_table_type_parametric(params),
+ curveType::Curve(data) => match data.len() {
+ 0 => compute_curve_gamma_table_type0(),
+ 1 => compute_curve_gamma_table_type1(data[0]),
+ _ => compute_curve_gamma_table_type2(data),
+ },
+ })
+}
+pub fn build_colorant_matrix(p: &Profile) -> Matrix {
+ let mut result: Matrix = Matrix { m: [[0.; 3]; 3] };
+ result.m[0][0] = s15Fixed16Number_to_float(p.redColorant.X);
+ result.m[0][1] = s15Fixed16Number_to_float(p.greenColorant.X);
+ result.m[0][2] = s15Fixed16Number_to_float(p.blueColorant.X);
+ result.m[1][0] = s15Fixed16Number_to_float(p.redColorant.Y);
+ result.m[1][1] = s15Fixed16Number_to_float(p.greenColorant.Y);
+ result.m[1][2] = s15Fixed16Number_to_float(p.blueColorant.Y);
+ result.m[2][0] = s15Fixed16Number_to_float(p.redColorant.Z);
+ result.m[2][1] = s15Fixed16Number_to_float(p.greenColorant.Z);
+ result.m[2][2] = s15Fixed16Number_to_float(p.blueColorant.Z);
+ result
+}
+
+/** Parametric representation of transfer function */
+#[derive(Debug)]
+struct Param {
+ g: f32,
+ a: f32,
+ b: f32,
+ c: f32,
+ d: f32,
+ e: f32,
+ f: f32,
+}
+
+impl Param {
+ #[allow(clippy::many_single_char_names)]
+ fn new(params: &[f32]) -> Param {
+ // convert from the variable number of parameters
+ // contained in profiles to a unified representation.
+ let g: f32 = params[0];
+ match params[1..] {
+ [] => Param {
+ g,
+ a: 1.,
+ b: 0.,
+ c: 1.,
+ d: 0.,
+ e: 0.,
+ f: 0.,
+ },
+ [a, b] => Param {
+ g,
+ a,
+ b,
+ c: 0.,
+ d: -b / a,
+ e: 0.,
+ f: 0.,
+ },
+ [a, b, c] => Param {
+ g,
+ a,
+ b,
+ c: 0.,
+ d: -b / a,
+ e: c,
+ f: c,
+ },
+ [a, b, c, d] => Param {
+ g,
+ a,
+ b,
+ c,
+ d,
+ e: 0.,
+ f: 0.,
+ },
+ [a, b, c, d, e, f] => Param {
+ g,
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ },
+ _ => panic!(),
+ }
+ }
+
+ fn eval(&self, x: f32) -> f32 {
+ if x < self.d {
+ self.c * x + self.f
+ } else {
+ (self.a * x + self.b).powf(self.g) + self.e
+ }
+ }
+ #[allow(clippy::many_single_char_names)]
+ fn invert(&self) -> Option<Param> {
+ // First check if the function is continuous at the cross-over point d.
+ let d1 = (self.a * self.d + self.b).powf(self.g) + self.e;
+ let d2 = self.c * self.d + self.f;
+
+ if (d1 - d2).abs() > 0.1 {
+ return None;
+ }
+ let d = d1;
+
+ // y = (a * x + b)^g + e
+ // y - e = (a * x + b)^g
+ // (y - e)^(1/g) = a*x + b
+ // (y - e)^(1/g) - b = a*x
+ // (y - e)^(1/g)/a - b/a = x
+ // ((y - e)/a^g)^(1/g) - b/a = x
+ // ((1/(a^g)) * y - e/(a^g))^(1/g) - b/a = x
+ let a = 1. / self.a.powf(self.g);
+ let b = -self.e / self.a.powf(self.g);
+ let g = 1. / self.g;
+ let e = -self.b / self.a;
+
+ // y = c * x + f
+ // y - f = c * x
+ // y/c - f/c = x
+ let (c, f);
+ if d <= 0. {
+ c = 1.;
+ f = 0.;
+ } else {
+ c = 1. / self.c;
+ f = -self.f / self.c;
+ }
+
+ // if self.d > 0. and self.c == 0 as is likely with type 1 and 2 parametric function
+ // then c and f will not be finite.
+ if !(g.is_finite()
+ && a.is_finite()
+ && b.is_finite()
+ && c.is_finite()
+ && d.is_finite()
+ && e.is_finite()
+ && f.is_finite())
+ {
+ return None;
+ }
+
+ Some(Param {
+ g,
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ })
+ }
+}
+
+#[test]
+fn param_invert() {
+ let p3 = Param::new(&[2.4, 0.948, 0.052, 0.077, 0.04]);
+ p3.invert().unwrap();
+ let g2_2 = Param::new(&[2.2]);
+ g2_2.invert().unwrap();
+ let g2_2 = Param::new(&[2.2, 0.9, 0.052]);
+ g2_2.invert().unwrap();
+ let g2_2 = dbg!(Param::new(&[2.2, 0.9, -0.52]));
+ g2_2.invert().unwrap();
+ let g2_2 = dbg!(Param::new(&[2.2, 0.9, -0.52, 0.1]));
+ assert!(g2_2.invert().is_none());
+}
+
+/* The following code is copied nearly directly from lcms.
+ * I think it could be much better. For example, Argyll seems to have better code in
+ * icmTable_lookup_bwd and icmTable_setup_bwd. However, for now this is a quick way
+ * to a working solution and allows for easy comparing with lcms. */
+#[no_mangle]
+#[allow(clippy::many_single_char_names)]
+pub fn lut_inverse_interp16(Value: u16, LutTable: &[u16]) -> uint16_fract_t {
+ let mut l: i32 = 1; // 'int' Give spacing for negative values
+ let mut r: i32 = 0x10000;
+ let mut x: i32 = 0;
+ let mut res: i32;
+ let length = LutTable.len() as i32;
+
+ let mut NumZeroes: i32 = 0;
+ while LutTable[NumZeroes as usize] as i32 == 0 && NumZeroes < length - 1 {
+ NumZeroes += 1
+ }
+ // There are no zeros at the beginning and we are trying to find a zero, so
+ // return anything. It seems zero would be the less destructive choice
+ /* I'm not sure that this makes sense, but oh well... */
+ if NumZeroes == 0 && Value as i32 == 0 {
+ return 0u16;
+ }
+ let mut NumPoles: i32 = 0;
+ while LutTable[(length - 1 - NumPoles) as usize] as i32 == 0xffff && NumPoles < length - 1 {
+ NumPoles += 1
+ }
+ // Does the curve belong to this case?
+ if NumZeroes > 1 || NumPoles > 1 {
+ let a_0: i32;
+ let b_0: i32;
+ // Identify if value fall downto 0 or FFFF zone
+ if Value as i32 == 0 {
+ return 0u16;
+ }
+ // if (Value == 0xFFFF) return 0xFFFF;
+ // else restrict to valid zone
+ if NumZeroes > 1 {
+ a_0 = (NumZeroes - 1) * 0xffff / (length - 1);
+ l = a_0 - 1
+ }
+ if NumPoles > 1 {
+ b_0 = (length - 1 - NumPoles) * 0xffff / (length - 1);
+ r = b_0 + 1
+ }
+ }
+ if r <= l {
+ // If this happens LutTable is not invertible
+ return 0u16;
+ }
+ // Seems not a degenerated case... apply binary search
+ while r > l {
+ x = (l + r) / 2;
+ res = lut_interp_linear16((x - 1) as uint16_fract_t, LutTable) as i32;
+ if res == Value as i32 {
+ // Found exact match.
+ return (x - 1) as uint16_fract_t;
+ }
+ if res > Value as i32 {
+ r = x - 1
+ } else {
+ l = x + 1
+ }
+ }
+
+ // Not found, should we interpolate?
+
+ // Get surrounding nodes
+ debug_assert!(x >= 1);
+
+ let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0f64);
+ let cell0: i32 = val2.floor() as i32;
+ let cell1: i32 = val2.ceil() as i32;
+ if cell0 == cell1 {
+ return x as uint16_fract_t;
+ }
+
+ let y0: f64 = LutTable[cell0 as usize] as f64;
+ let x0: f64 = 65535.0f64 * cell0 as f64 / (length - 1) as f64;
+ let y1: f64 = LutTable[cell1 as usize] as f64;
+ let x1: f64 = 65535.0f64 * cell1 as f64 / (length - 1) as f64;
+ let a: f64 = (y1 - y0) / (x1 - x0);
+ let b: f64 = y0 - a * x0;
+ if a.abs() < 0.01f64 {
+ return x as uint16_fract_t;
+ }
+ let f: f64 = (Value as i32 as f64 - b) / a;
+ if f < 0.0f64 {
+ return 0u16;
+ }
+ if f >= 65535.0f64 {
+ return 0xffffu16;
+ }
+ (f + 0.5f64).floor() as uint16_fract_t
+}
+/*
+The number of entries needed to invert a lookup table should not
+necessarily be the same as the original number of entries. This is
+especially true of lookup tables that have a small number of entries.
+
+For example:
+Using a table like:
+ {0, 3104, 14263, 34802, 65535}
+invert_lut will produce an inverse of:
+ {3, 34459, 47529, 56801, 65535}
+which has an maximum error of about 9855 (pixel difference of ~38.346)
+
+For now, we punt the decision of output size to the caller. */
+fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> {
+ /* for now we invert the lut by creating a lut of size out_length
+ * and attempting to lookup a value for each entry using lut_inverse_interp16 */
+ let mut output = Vec::with_capacity(out_length);
+ for i in 0..out_length {
+ let x: f64 = i as f64 * 65535.0f64 / (out_length - 1) as f64;
+ let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
+ output.push(lut_inverse_interp16(input, table));
+ }
+ output
+}
+#[allow(clippy::needless_range_loop)]
+fn compute_precache_pow(output: &mut [u8; PRECACHE_OUTPUT_SIZE], gamma: f32) {
+ for v in 0..PRECACHE_OUTPUT_SIZE {
+ //XXX: don't do integer/float conversion... and round?
+ output[v] = (255. * (v as f32 / PRECACHE_OUTPUT_MAX as f32).powf(gamma)) as u8;
+ }
+}
+#[allow(clippy::needless_range_loop)]
+pub fn compute_precache_lut(output: &mut [u8; PRECACHE_OUTPUT_SIZE], table: &[u16]) {
+ for v in 0..PRECACHE_OUTPUT_SIZE {
+ output[v] = lut_interp_linear_precache_output(v as u32, table);
+ }
+}
+#[allow(clippy::needless_range_loop)]
+pub fn compute_precache_linear(output: &mut [u8; PRECACHE_OUTPUT_SIZE]) {
+ for v in 0..PRECACHE_OUTPUT_SIZE {
+ //XXX: round?
+ output[v] = (v / (PRECACHE_OUTPUT_SIZE / 256)) as u8;
+ }
+}
+pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPUT_SIZE]) -> bool {
+ match trc {
+ curveType::Parametric(params) => {
+ let mut gamma_table_uint: [u16; 256] = [0; 256];
+
+ let mut inverted_size: usize = 256;
+ let gamma_table = compute_curve_gamma_table_type_parametric(params);
+ let mut i: u16 = 0u16;
+ while (i as i32) < 256 {
+ gamma_table_uint[i as usize] = (gamma_table[i as usize] * 65535f32) as u16;
+ i += 1
+ }
+ //XXX: the choice of a minimum of 256 here is not backed by any theory,
+ // measurement or data, however it is what lcms uses.
+ // the maximum number we would need is 65535 because that's the
+ // accuracy used for computing the pre cache table
+ if inverted_size < 256 {
+ inverted_size = 256
+ }
+ let inverted = invert_lut(&gamma_table_uint, inverted_size);
+ compute_precache_lut(output, &inverted);
+ }
+ curveType::Curve(data) => {
+ match data.len() {
+ 0 => compute_precache_linear(output),
+ 1 => compute_precache_pow(output, 1. / u8Fixed8Number_to_float(data[0])),
+ _ => {
+ let mut inverted_size = data.len();
+ //XXX: the choice of a minimum of 256 here is not backed by any theory,
+ // measurement or data, however it is what lcms uses.
+ // the maximum number we would need is 65535 because that's the
+ // accuracy used for computing the pre cache table
+ if inverted_size < 256 {
+ inverted_size = 256
+ } //XXX turn this conversion into a function
+ let inverted = invert_lut(data, inverted_size);
+ compute_precache_lut(output, &inverted);
+ }
+ }
+ }
+ }
+ true
+}
+fn build_linear_table(length: usize) -> Vec<u16> {
+ let mut output = Vec::with_capacity(length);
+ for i in 0..length {
+ let x: f64 = i as f64 * 65535.0f64 / (length - 1) as f64;
+ let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
+ output.push(input);
+ }
+ output
+}
+fn build_pow_table(gamma: f32, length: usize) -> Vec<u16> {
+ let mut output = Vec::with_capacity(length);
+ for i in 0..length {
+ let mut x: f64 = i as f64 / (length - 1) as f64;
+ x = x.powf(gamma as f64);
+ let result: uint16_fract_t = (x * 65535.0f64 + 0.5f64).floor() as uint16_fract_t;
+ output.push(result);
+ }
+ output
+}
+
+fn to_lut(params: &Param, len: usize) -> Vec<u16> {
+ let mut output = Vec::with_capacity(len);
+ for i in 0..len {
+ let X = i as f32 / (len-1) as f32;
+ output.push((params.eval(X) * 65535.) as u16);
+ }
+ output
+}
+
+pub(crate) fn build_lut_for_linear_from_tf(trc: &curveType,
+ lut_len: Option<usize>) -> Vec<u16> {
+ match trc {
+ curveType::Parametric(params) => {
+ let lut_len = lut_len.unwrap_or(256);
+ let params = Param::new(params);
+ to_lut(&params, lut_len)
+ },
+ curveType::Curve(data) => {
+ let autogen_lut_len = lut_len.unwrap_or(4096);
+ match data.len() {
+ 0 => build_linear_table(autogen_lut_len),
+ 1 => {
+ let gamma = u8Fixed8Number_to_float(data[0]);
+ build_pow_table(gamma, autogen_lut_len)
+ }
+ _ => {
+ let lut_len = lut_len.unwrap_or(data.len());
+ assert_eq!(lut_len, data.len());
+ data.clone() // I feel bad about this.
+ }
+ }
+ },
+ }
+}
+
+pub(crate) fn build_lut_for_tf_from_linear(trc: &curveType) -> Option<Vec<u16>> {
+ match trc {
+ curveType::Parametric(params) => {
+ let lut_len = 256;
+ let params = Param::new(params);
+ if let Some(inv_params) = params.invert() {
+ return Some(to_lut(&inv_params, lut_len));
+ }
+ // else return None instead of fallthrough to generic lut inversion.
+ return None;
+ },
+ curveType::Curve(data) => {
+ let autogen_lut_len = 4096;
+ match data.len() {
+ 0 => {
+ return Some(build_linear_table(autogen_lut_len));
+ },
+ 1 => {
+ let gamma = 1. / u8Fixed8Number_to_float(data[0]);
+ return Some(build_pow_table(gamma, autogen_lut_len));
+ },
+ _ => {},
+ }
+ },
+ }
+
+ let linear_from_tf = build_lut_for_linear_from_tf(trc, None);
+
+ //XXX: the choice of a minimum of 256 here is not backed by any theory,
+ // measurement or data, however it is what lcms uses.
+ let inverted_lut_len = std::cmp::max(linear_from_tf.len(), 256);
+ Some(invert_lut(&linear_from_tf, inverted_lut_len))
+}
+
+pub(crate) fn build_output_lut(trc: &curveType) -> Option<Vec<u16>> {
+ build_lut_for_tf_from_linear(trc)
+}