move patched deps to separate repository

This commit is contained in:
2025-10-19 02:00:29 -07:00
parent b593507cb2
commit 83792e586e
93 changed files with 6 additions and 30009 deletions

2
Cargo.lock generated
View File

@@ -132,6 +132,7 @@ dependencies = [
[[package]]
name = "moxcms"
version = "0.7.6"
source = "git+https://github.com/edera-dev/sprout-patched-deps.git?rev=2c4fcc84b50d40c28f540d4271109ea0ca7e1268#2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
dependencies = [
"num-traits",
"pxfm",
@@ -248,6 +249,7 @@ dependencies = [
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "git+https://github.com/edera-dev/sprout-patched-deps.git?rev=2c4fcc84b50d40c28f540d4271109ea0ca7e1268#2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
[[package]]
name = "sprout"

View File

@@ -44,7 +44,9 @@ strip = "debuginfo"
debug = 0
[patch.crates-io.simd-adler32]
path = "deps/simd-adler32"
git = "https://github.com/edera-dev/sprout-patched-deps.git"
rev = "2c4fcc84b50d40c28f540d4271109ea0ca7e1268"
[patch.crates-io.moxcms]
path = "deps/moxcms"
git = "https://github.com/edera-dev/sprout-patched-deps.git"
rev = "2c4fcc84b50d40c28f540d4271109ea0ca7e1268"

13
deps/README.md vendored
View File

@@ -1,13 +0,0 @@
# Vendored Dependencies
Currently, sprout requires some vendored dependencies to work around usage of simd.
Both `moxcms` and `simd-adler32` are used for the image library for the splash screen feature.
## moxcms
- Removed NEON, SSE, and AVX support.
## simd-adler2
- Made compilation function on UEFI targets.

View File

@@ -1,9 +0,0 @@
/target
Cargo.lock
.idea
app/target
flamegraph.svg
perf.data
profile.json.gz
.cargo
rust-toolchain.toml

View File

@@ -1,49 +0,0 @@
workspace = { members = ["app", "fuzz"] }
[package]
name = "moxcms"
version = "0.7.6"
edition = "2024"
description = "Simple Color Management in Rust"
readme = "./README.md"
keywords = ["icc", "cms", "color", "cmyk"]
license = "BSD-3-Clause OR Apache-2.0"
authors = ["Radzivon Bartoshyk"]
documentation = "https://github.com/awxkee/moxcms"
categories = ["multimedia::images"]
homepage = "https://github.com/awxkee/moxcms"
repository = "https://github.com/awxkee/moxcms.git"
exclude = ["*.jpg", "../../assets/*", "*.png", "*.icc", "./assets/*"]
rust-version = "1.85.0"
[dependencies]
num-traits = "0.2"
pxfm = "^0.1.1"
[dev-dependencies]
rand = "0.9"
[features]
# If no unsafe intrinsics active then `forbid(unsafe)` will be used.
default = []
# Enables AVX2 acceleration where possible
avx = []
# Enables SSE4.1 acceleration where possible
sse = []
# Enables NEON intrinsics where possible
neon = []
# Enables AVX-512 acceleration where possible. This will work only from 1.89 on stable.
avx512 = []
# Allows configuring interpolation methods and LUT weights precision.
# Disabled by default to prevent binary bloat.
options = []
[package.metadata.docs.rs]
# To build locally:
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --open --manifest-path ./Cargo.toml
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[profile.profiling]
inherits = "release"
debug = true

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024 Radzivon Bartoshyk
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,26 +0,0 @@
Copyright (c) Radzivon Bartoshyk. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

74
deps/moxcms/README.md vendored
View File

@@ -1,74 +0,0 @@
# Rust ICC Management
Fast and safe conversion between ICC profiles; in pure Rust.
Supports CMYK⬌RGBX, RGBX⬌RGBX, RGBX⬌GRAY, LAB⬌RGBX and CMYK⬌LAB, GRAY⬌RGB, any 3/4 color profiles to RGB and vice versa. Also supports almost any to any Display Class ICC profiles up to 16 inks.
## Example
```rust
let f_str = "./assets/dci_p3_profile.jpeg";
let file = File::open(f_str).expect("Failed to open file");
let img = image::ImageReader::open(f_str).unwrap().decode().unwrap();
let rgb = img.to_rgb8();
let mut decoder = JpegDecoder::new(BufReader::new(file)).unwrap();
let icc = decoder.icc_profile().unwrap().unwrap();
let color_profile = ColorProfile::new_from_slice(&icc).unwrap();
let dest_profile = ColorProfile::new_srgb();
let transform = color_profile
.create_transform_8bit(&dest_profile, Layout::Rgb8, TransformOptions::default())
.unwrap();
let mut dst = vec![0u8; rgb.len()];
for (src, dst) in rgb
.chunks_exact(img.width() as usize * 3)
.zip(dst.chunks_exact_mut(img.dimensions().0 as usize * 3))
{
transform
.transform(
&src[..img.dimensions().0 as usize * 3],
&mut dst[..img.dimensions().0 as usize * 3],
)
.unwrap();
}
image::save_buffer(
"v1.jpg",
&dst,
img.dimensions().0,
img.dimensions().1,
image::ExtendedColorType::Rgb8,
)
.unwrap();
```
## Benchmarks
### ICC Transform 8-Bit
Tests were ran with a 1997×1331 resolution image.
| Conversion | time(NEON) | Time(AVX2) |
|--------------------|:----------:|:----------:|
| moxcms RGB⮕RGB | 2.68ms | 4.52ms |
| moxcms LUT RGB⮕RGB | 7.18ms | 17.50ms |
| moxcms RGBA⮕RGBA | 2.96ms | 4.83ms |
| moxcms CMYK⮕RGBA | 11.86ms | 27.98ms |
| lcms2 RGB⮕RGB | 13.1ms | 27.73ms |
| lcms2 LUT RGB⮕RGB | 27.60ms | 58.26ms |
| lcms2 RGBA⮕RGBA | 21.97ms | 35.70ms |
| lcms2 CMYK⮕RGBA | 39.71ms | 79.40ms |
| qcms RGB⮕RGB | 6.47ms | 4.59ms |
| qcms LUT RGB⮕RGB | 26.72ms | 60.80ms |
| qcms RGBA⮕RGBA | 6.83ms | 4.99ms |
| qcms CMYK⮕RGBA | 25.97ms | 61.54ms |
## License
This project is licensed under either of
- BSD-3-Clause License (see [LICENSE](LICENSE.md))
- Apache License, Version 2.0 (see [LICENSE](LICENSE-APACHE.md))
at your option.

View File

@@ -1,172 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::matrix::{Matrix3f, Vector3f, Xyz};
use crate::{Chromaticity, Matrix3d, Vector3d, XyY};
pub(crate) const BRADFORD_D: Matrix3d = Matrix3d {
v: [
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
],
};
pub(crate) const BRADFORD_F: Matrix3f = BRADFORD_D.to_f32();
#[inline]
pub(crate) const fn compute_chromatic_adaption(
source_white_point: Xyz,
dest_white_point: Xyz,
chad: Matrix3f,
) -> Matrix3f {
let cone_source_xyz = Vector3f {
v: [
source_white_point.x,
source_white_point.y,
source_white_point.z,
],
};
let cone_source_rgb = chad.mul_vector(cone_source_xyz);
let cone_dest_xyz = Vector3f {
v: [dest_white_point.x, dest_white_point.y, dest_white_point.z],
};
let cone_dest_rgb = chad.mul_vector(cone_dest_xyz);
let cone = Matrix3f {
v: [
[cone_dest_rgb.v[0] / cone_source_rgb.v[0], 0., 0.],
[0., cone_dest_rgb.v[1] / cone_source_rgb.v[1], 0.],
[0., 0., cone_dest_rgb.v[2] / cone_source_rgb.v[2]],
],
};
let chad_inv = chad.inverse();
let p0 = cone.mat_mul_const(chad);
chad_inv.mat_mul_const(p0)
}
#[inline]
pub(crate) const fn compute_chromatic_adaption_d(
source_white_point: Xyz,
dest_white_point: Xyz,
chad: Matrix3d,
) -> Matrix3d {
let cone_source_xyz = Vector3d {
v: [
source_white_point.x as f64,
source_white_point.y as f64,
source_white_point.z as f64,
],
};
let cone_source_rgb = chad.mul_vector(cone_source_xyz);
let cone_dest_xyz = Vector3d {
v: [
dest_white_point.x as f64,
dest_white_point.y as f64,
dest_white_point.z as f64,
],
};
let cone_dest_rgb = chad.mul_vector(cone_dest_xyz);
let cone = Matrix3d {
v: [
[cone_dest_rgb.v[0] / cone_source_rgb.v[0], 0., 0.],
[0., cone_dest_rgb.v[1] / cone_source_rgb.v[1], 0.],
[0., 0., cone_dest_rgb.v[2] / cone_source_rgb.v[2]],
],
};
let chad_inv = chad.inverse();
let p0 = cone.mat_mul_const(chad);
chad_inv.mat_mul_const(p0)
}
pub const fn adaption_matrix(source_illumination: Xyz, target_illumination: Xyz) -> Matrix3f {
compute_chromatic_adaption(source_illumination, target_illumination, BRADFORD_F)
}
pub const fn adaption_matrix_d(source_illumination: Xyz, target_illumination: Xyz) -> Matrix3d {
compute_chromatic_adaption_d(source_illumination, target_illumination, BRADFORD_D)
}
pub const fn adapt_to_d50(r: Matrix3f, source_white_pt: XyY) -> Matrix3f {
adapt_to_illuminant(r, source_white_pt, Chromaticity::D50.to_xyz())
}
pub const fn adapt_to_d50_d(r: Matrix3d, source_white_pt: XyY) -> Matrix3d {
adapt_to_illuminant_d(r, source_white_pt, Chromaticity::D50.to_xyz())
}
pub const fn adapt_to_illuminant(
r: Matrix3f,
source_white_pt: XyY,
illuminant_xyz: Xyz,
) -> Matrix3f {
let bradford = adaption_matrix(source_white_pt.to_xyz(), illuminant_xyz);
bradford.mat_mul_const(r)
}
pub const fn adapt_to_illuminant_d(
r: Matrix3d,
source_white_pt: XyY,
illuminant_xyz: Xyz,
) -> Matrix3d {
let bradford = adaption_matrix_d(source_white_pt.to_xyz(), illuminant_xyz);
bradford.mat_mul_const(r)
}
pub const fn adapt_to_illuminant_xyz(
r: Matrix3f,
source_white_pt: Xyz,
illuminant_xyz: Xyz,
) -> Matrix3f {
if source_white_pt.y == 0.0 {
return r;
}
let bradford = adaption_matrix(source_white_pt, illuminant_xyz);
bradford.mat_mul_const(r)
}
pub const fn adapt_to_illuminant_xyz_d(
r: Matrix3d,
source_white_pt: Xyz,
illuminant_xyz: Xyz,
) -> Matrix3d {
if source_white_pt.y == 0.0 {
return r;
}
let bradford = adaption_matrix_d(source_white_pt, illuminant_xyz);
bradford.mat_mul_const(r)
}

View File

@@ -1,143 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 8/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::{CmsError, XyY, XyYRepresentable, Xyz, Xyzd};
#[derive(Clone, Debug, Copy)]
#[repr(C)]
pub struct Chromaticity {
pub x: f32,
pub y: f32,
}
impl Chromaticity {
#[inline]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
/// Converts this chromaticity (`x`, `y`) to a tristimulus [`Xyz`] value,
/// normalized such that `y = 1.0`.
#[inline]
pub const fn to_xyz(&self) -> Xyz {
let reciprocal = if self.y != 0. { 1. / self.y } else { 0. };
Xyz {
x: self.x * reciprocal,
y: 1f32,
z: (1f32 - self.x - self.y) * reciprocal,
}
}
/// Get the color representation with component sum `1`.
///
/// In contrast to the XYZ representation defined through setting `Y` to a known
/// value (such as `1` in [`Self::to_xyz`]) this representation can be uniquely
/// derived from the `xy` coordinates with no ambiguities. It is scaled from the
/// original XYZ color by diving by `X + Y + Z`. Note that, in particular, this
/// method is well-defined even if the original color had pure chromamatic
/// information with no luminance (Y = `0`) and will preserve that information,
/// whereas [`Self::to_xyz`] is ill-defined and returns an incorrect value.
#[inline]
pub const fn to_scaled_xyzd(&self) -> Xyzd {
let z = 1.0 - self.x as f64 - self.y as f64;
Xyzd::new(self.x as f64, self.y as f64, z)
}
/// Get the color representation with component sum `1`.
///
/// In contrast to the XYZ representation defined through setting `Y` to a known
/// value (such as `1` in [`Self::to_xyz`]) this representation can be uniquely
/// derived from the `xy` coordinates with no ambiguities. It is scaled from the
/// original XYZ color by diving by `X + Y + Z`. Note that, in particular, this
/// method is well-defined even if the original color had pure chromamatic
/// information with no luminance (Y = `0`) and will preserve that information,
/// whereas [`Self::to_xyz`] is ill-defined and returns an incorrect value.
#[inline]
pub const fn to_scaled_xyz(&self) -> Xyz {
let z = 1.0 - self.x - self.y;
Xyz::new(self.x, self.y, z)
}
#[inline]
pub const fn to_xyzd(&self) -> Xyzd {
let reciprocal = if self.y != 0. { 1. / self.y } else { 0. };
Xyzd {
x: self.x as f64 * reciprocal as f64,
y: 1f64,
z: (1f64 - self.x as f64 - self.y as f64) * reciprocal as f64,
}
}
#[inline]
pub const fn to_xyyb(&self) -> XyY {
XyY {
x: self.x as f64,
y: self.y as f64,
yb: 1.,
}
}
pub const D65: Chromaticity = Chromaticity {
x: 0.31272,
y: 0.32903,
};
pub const D50: Chromaticity = Chromaticity {
x: 0.34567,
y: 0.35850,
};
}
impl XyYRepresentable for Chromaticity {
fn to_xyy(self) -> XyY {
self.to_xyyb()
}
}
impl TryFrom<Xyz> for Chromaticity {
type Error = CmsError;
#[inline]
fn try_from(xyz: Xyz) -> Result<Self, Self::Error> {
let sum = xyz.x + xyz.y + xyz.z;
// Avoid division by zero or invalid XYZ values
if sum == 0.0 {
return Err(CmsError::DivisionByZero);
}
let rec = 1f32 / sum;
let chromaticity_x = xyz.x * rec;
let chromaticity_y = xyz.y * rec;
Ok(Chromaticity {
x: chromaticity_x,
y: chromaticity_y,
})
}
}

View File

@@ -1,642 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::gamma::{
bt1361_to_linear, hlg_to_linear, iec61966_to_linear, log100_sqrt10_to_linear, log100_to_linear,
pq_to_linear, smpte240_to_linear, smpte428_to_linear,
};
use crate::{
Chromaticity, ColorProfile, Matrix3d, Matrix3f, XyYRepresentable,
err::CmsError,
trc::{ToneReprCurve, build_trc_table, curve_from_gamma},
};
use std::convert::TryFrom;
/// 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, 1321, 23255 are all reserved so all map to the same variant
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CicpColorPrimaries {
/// 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 />
GenericFilm = 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 TryFrom<u8> for CicpColorPrimaries {
type Error = CmsError;
#[allow(unreachable_patterns)]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
// Values 0, 3, 1321, 23255 are all reserved so all map to the
// same variant.
0 | 3 | 13..=21 | 23..=255 => Ok(Self::Reserved),
1 => Ok(Self::Bt709),
2 => Ok(Self::Unspecified),
4 => Ok(Self::Bt470M),
5 => Ok(Self::Bt470Bg),
6 => Ok(Self::Bt601),
7 => Ok(Self::Smpte240),
8 => Ok(Self::GenericFilm),
9 => Ok(Self::Bt2020),
10 => Ok(Self::Xyz),
11 => Ok(Self::Smpte431),
12 => Ok(Self::Smpte432),
22 => Ok(Self::Ebu3213),
_ => Err(CmsError::InvalidCicp),
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct ColorPrimaries {
pub red: Chromaticity,
pub green: Chromaticity,
pub blue: Chromaticity,
}
/// See [Rec. ITU-T H.273 (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) Table 2.
impl ColorPrimaries {
/// [ACEScg](https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#ACEScg).
pub const ACES_CG: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.713, y: 0.293 },
green: Chromaticity { x: 0.165, y: 0.830 },
blue: Chromaticity { x: 0.128, y: 0.044 },
};
/// [ACES2065-1](https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#ACES2065-1).
pub const ACES_2065_1: ColorPrimaries = ColorPrimaries {
red: Chromaticity {
x: 0.7347,
y: 0.2653,
},
green: Chromaticity {
x: 0.0000,
y: 1.0000,
},
blue: Chromaticity {
x: 0.0001,
y: -0.0770,
},
};
/// [Adobe RGB](https://en.wikipedia.org/wiki/Adobe_RGB_color_space) (1998).
pub const ADOBE_RGB: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.64, y: 0.33 },
green: Chromaticity { x: 0.21, y: 0.71 },
blue: Chromaticity { x: 0.15, y: 0.06 },
};
/// [DCI P3](https://en.wikipedia.org/wiki/DCI-P3#DCI_P3).
///
/// This is the same as [`DISPLAY_P3`](Self::DISPLAY_P3),
/// [`SMPTE_431`](Self::SMPTE_431) and [`SMPTE_432`](Self::SMPTE_432).
pub const DCI_P3: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.680, y: 0.320 },
green: Chromaticity { x: 0.265, y: 0.690 },
blue: Chromaticity { x: 0.150, y: 0.060 },
};
/// [Diplay P3](https://en.wikipedia.org/wiki/DCI-P3#Display_P3).
///
/// This is the same as [`DCI_P3`](Self::DCI_P3),
/// [`SMPTE_431`](Self::SMPTE_431) and [`SMPTE_432`](Self::SMPTE_432).
pub const DISPLAY_P3: ColorPrimaries = Self::DCI_P3;
/// SMPTE RP 431-2 (2011).
///
/// This is the same as [`DCI_P3`](Self::DCI_P3),
/// [`DISPLAY_P3`](Self::DISPLAY_P3) and [`SMPTE_432`](Self::SMPTE_432).
pub const SMPTE_431: ColorPrimaries = Self::DCI_P3;
/// SMPTE EG 432-1 (2010).
///
/// This is the same as [`DCI_P3`](Self::DCI_P3),
/// [`DISPLAY_P3`](Self::DISPLAY_P3) and [`SMPTE_431`](Self::SMPTE_431).
pub const SMPTE_432: ColorPrimaries = Self::DCI_P3;
/// [ProPhoto RGB](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space).
pub const PRO_PHOTO_RGB: ColorPrimaries = ColorPrimaries {
red: Chromaticity {
x: 0.734699,
y: 0.265301,
},
green: Chromaticity {
x: 0.159597,
y: 0.840403,
},
blue: Chromaticity {
x: 0.036598,
y: 0.000105,
},
};
/// Rec. ITU-R BT.709-6
///
/// Rec. ITU-R BT.1361-0 conventional colour gamut system and extended
/// colour gamut system (historical).
///
/// IEC 61966-2-1 sRGB or sYCC IEC 61966-2-4).
///
/// Society of Motion Picture and Television Engineers (MPTE) RP 177 (1993) Annex B.
pub const BT_709: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.64, y: 0.33 },
green: Chromaticity { x: 0.30, y: 0.60 },
blue: Chromaticity { x: 0.15, y: 0.06 },
};
/// Rec. ITU-R BT.470-6 System M (historical).
///
/// United States National Television System Committee 1953 Recommendation
/// for transmission standards for color television.
///
/// United States Federal Communications Commission (2003) Title 47 Code of
/// Federal Regulations 73.682 (a) (20).
pub const BT_470M: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.67, y: 0.33 },
green: Chromaticity { x: 0.21, y: 0.71 },
blue: Chromaticity { x: 0.14, y: 0.08 },
};
/// Rec. ITU-R BT.470-6 System B, G (historical) Rec. ITU-R BT.601-7 625.
///
/// Rec. ITU-R BT.1358-0 625 (historical).
/// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM.
pub const BT_470BG: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.64, y: 0.33 },
green: Chromaticity { x: 0.29, y: 0.60 },
blue: Chromaticity { x: 0.15, y: 0.06 },
};
/// Rec. ITU-R BT.601-7 525.
///
/// Rec. ITU-R BT.1358-1 525 or 625 (historical) Rec. ITU-R BT.1700-0 NTSC.
///
/// SMPTE 170M (2004) (functionally the same as the [`SMPTE_240`](Self::SMPTE_240)).
pub const BT_601: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.630, y: 0.340 },
green: Chromaticity { x: 0.310, y: 0.595 },
blue: Chromaticity { x: 0.155, y: 0.070 },
};
/// SMPTE 240M (1999) (historical) (functionally the same as [`BT_601`](Self::BT_601)).
pub const SMPTE_240: ColorPrimaries = Self::BT_601;
/// Generic film (colour filters using Illuminant C).
pub const GENERIC_FILM: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.681, y: 0.319 },
green: Chromaticity { x: 0.243, y: 0.692 },
blue: Chromaticity { x: 0.145, y: 0.049 },
};
/// Rec. ITU-R BT.2020-2.
///
/// Rec. ITU-R BT.2100-0.
pub const BT_2020: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.708, y: 0.292 },
green: Chromaticity { x: 0.170, y: 0.797 },
blue: Chromaticity { x: 0.131, y: 0.046 },
};
/// SMPTE ST 428-1 (CIE 1931 XYZ as in ISO 11664-1).
pub const XYZ: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 1.0, y: 0.0 },
green: Chromaticity { x: 0.0, y: 1.0 },
blue: Chromaticity { x: 0.0, y: 0.0 },
};
/// EBU Tech. 3213-E (1975).
pub const EBU_3213: ColorPrimaries = ColorPrimaries {
red: Chromaticity { x: 0.630, y: 0.340 },
green: Chromaticity { x: 0.295, y: 0.605 },
blue: Chromaticity { x: 0.155, y: 0.077 },
};
}
impl ColorPrimaries {
/// Returns RGB -> XYZ conversion matrix
///
/// # Arguments
///
/// * `white_point`: [Chromaticity] or [crate::XyY] or any item conforming [XyYRepresentable]
///
/// returns: [Matrix3d]
pub fn transform_to_xyz_d(self, white_point: impl XyYRepresentable) -> Matrix3d {
let red_xyz = self.red.to_scaled_xyzd();
let green_xyz = self.green.to_scaled_xyzd();
let blue_xyz = self.blue.to_scaled_xyzd();
let xyz_matrix = Matrix3d {
v: [
[red_xyz.x, green_xyz.x, blue_xyz.x],
[red_xyz.y, green_xyz.y, blue_xyz.y],
[red_xyz.z, green_xyz.z, blue_xyz.z],
],
};
ColorProfile::rgb_to_xyz_d(xyz_matrix, white_point.to_xyy().to_xyzd())
}
/// Returns RGB -> XYZ conversion matrix
///
/// # Arguments
///
/// * `white_point`: [Chromaticity] or [crate::XyY] or any item conforming [XyYRepresentable]
///
/// returns: [Matrix3f]
pub fn transform_to_xyz(self, white_point: impl XyYRepresentable) -> Matrix3f {
let red_xyz = self.red.to_scaled_xyz();
let green_xyz = self.green.to_scaled_xyz();
let blue_xyz = self.blue.to_scaled_xyz();
let xyz_matrix = Matrix3f {
v: [
[red_xyz.x, green_xyz.x, blue_xyz.x],
[red_xyz.y, green_xyz.y, blue_xyz.y],
[red_xyz.z, green_xyz.z, blue_xyz.z],
],
};
ColorProfile::rgb_to_xyz_static(xyz_matrix, white_point.to_xyy().to_xyz())
}
}
/// 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, 19255 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 />
Log100 = 9,
/// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range)<br />
Log100sqrt10 = 10,
/// IEC 61966-2-4<br />
Iec61966 = 11,
/// Rec. ITU-R BT.1361-0 extended colour gamut system (historical)<br />
Bt1361 = 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 />
Bt202010bit = 14,
/// Rec. ITU-R BT.2020-2 (12-bit system)<br />
/// (functionally the same as the values 1, 6 and 14)<br />
Bt202012bit = 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,
}
impl TryFrom<u8> for TransferCharacteristics {
type Error = CmsError;
#[allow(unreachable_patterns)]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 | 3 | 19..=255 => Ok(Self::Reserved),
1 => Ok(Self::Bt709),
2 => Ok(Self::Unspecified),
4 => Ok(Self::Bt470M),
5 => Ok(Self::Bt470Bg),
6 => Ok(Self::Bt601),
7 => Ok(Self::Smpte240), // unimplemented
8 => Ok(Self::Linear),
9 => Ok(Self::Log100),
10 => Ok(Self::Log100sqrt10),
11 => Ok(Self::Iec61966), // unimplemented
12 => Ok(Self::Bt1361), // unimplemented
13 => Ok(Self::Srgb),
14 => Ok(Self::Bt202010bit),
15 => Ok(Self::Bt202012bit),
16 => Ok(Self::Smpte2084),
17 => Ok(Self::Smpte428), // unimplemented
18 => Ok(Self::Hlg),
_ => Err(CmsError::InvalidCicp),
}
}
}
impl CicpColorPrimaries {
pub(crate) const fn has_chromaticity(self) -> bool {
self as u8 != Self::Reserved as u8 && self as u8 != Self::Unspecified as u8
}
pub(crate) const fn white_point(self) -> Result<Chromaticity, CmsError> {
Ok(match self {
Self::Reserved => return Err(CmsError::UnsupportedColorPrimaries(self as u8)),
Self::Bt709
| Self::Bt470Bg
| Self::Bt601
| Self::Smpte240
| Self::Bt2020
| Self::Smpte432
| Self::Ebu3213 => Chromaticity::D65,
Self::Unspecified => return Err(CmsError::UnsupportedColorPrimaries(self as u8)),
Self::Bt470M => Chromaticity { x: 0.310, y: 0.316 },
Self::GenericFilm => Chromaticity { x: 0.310, y: 0.316 },
Self::Xyz => Chromaticity {
x: 1. / 3.,
y: 1. / 3.,
},
Self::Smpte431 => Chromaticity { x: 0.314, y: 0.351 },
})
}
}
impl TryFrom<CicpColorPrimaries> for ColorPrimaries {
type Error = CmsError;
fn try_from(value: CicpColorPrimaries) -> Result<Self, Self::Error> {
match value {
CicpColorPrimaries::Reserved => Err(CmsError::UnsupportedColorPrimaries(value as u8)),
CicpColorPrimaries::Bt709 => Ok(ColorPrimaries::BT_709),
CicpColorPrimaries::Unspecified => {
Err(CmsError::UnsupportedColorPrimaries(value as u8))
}
CicpColorPrimaries::Bt470M => Ok(ColorPrimaries::BT_470M),
CicpColorPrimaries::Bt470Bg => Ok(ColorPrimaries::BT_470BG),
CicpColorPrimaries::Bt601 | CicpColorPrimaries::Smpte240 => Ok(ColorPrimaries::BT_601),
CicpColorPrimaries::GenericFilm => Ok(ColorPrimaries::GENERIC_FILM),
CicpColorPrimaries::Bt2020 => Ok(ColorPrimaries::BT_2020),
CicpColorPrimaries::Xyz => Ok(ColorPrimaries::XYZ),
// These two share primaries, but have distinct white points
CicpColorPrimaries::Smpte431 | CicpColorPrimaries::Smpte432 => {
Ok(ColorPrimaries::SMPTE_431)
}
CicpColorPrimaries::Ebu3213 => Ok(ColorPrimaries::EBU_3213),
}
}
}
impl TransferCharacteristics {
pub(crate) fn has_transfer_curve(self) -> bool {
self != Self::Reserved && self != Self::Unspecified
}
}
pub(crate) fn create_rec709_parametric() -> [f32; 5] {
const POW_EXP: f32 = 0.45;
const G: f32 = 1. / POW_EXP;
const B: f32 = (0.09929682680944f64 / 1.09929682680944f64) as f32;
const C: f32 = 1f32 / 4.5f32;
const D: f32 = (4.5f64 * 0.018053968510807f64) as f32;
const A: f32 = (1. / 1.09929682680944f64) as f32;
[G, A, B, C, D]
}
impl TryFrom<TransferCharacteristics> for ToneReprCurve {
type Error = CmsError;
/// 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 => {
return Err(CmsError::UnsupportedTrc(value as u8));
}
TransferCharacteristics::Bt709
| TransferCharacteristics::Bt601
| TransferCharacteristics::Bt202010bit
| TransferCharacteristics::Bt202012bit => {
// 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.
ToneReprCurve::Parametric(create_rec709_parametric().to_vec())
}
TransferCharacteristics::Unspecified => {
return Err(CmsError::UnsupportedTrc(value as u8));
}
TransferCharacteristics::Bt470M => curve_from_gamma(2.2),
TransferCharacteristics::Bt470Bg => curve_from_gamma(2.8),
TransferCharacteristics::Smpte240 => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, smpte240_to_linear);
ToneReprCurve::Lut(table)
}
TransferCharacteristics::Linear => curve_from_gamma(1.),
TransferCharacteristics::Log100 => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, log100_to_linear);
ToneReprCurve::Lut(table)
}
TransferCharacteristics::Log100sqrt10 => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, log100_sqrt10_to_linear);
ToneReprCurve::Lut(table)
}
TransferCharacteristics::Iec61966 => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, iec61966_to_linear);
ToneReprCurve::Lut(table)
}
TransferCharacteristics::Bt1361 => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, bt1361_to_linear);
ToneReprCurve::Lut(table)
}
TransferCharacteristics::Srgb => {
ToneReprCurve::Parametric(vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045])
}
TransferCharacteristics::Smpte2084 => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, pq_to_linear);
ToneReprCurve::Lut(table)
}
TransferCharacteristics::Smpte428 => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, smpte428_to_linear);
ToneReprCurve::Lut(table)
}
TransferCharacteristics::Hlg => {
let table = build_trc_table(NUM_TRC_TABLE_ENTRIES, hlg_to_linear);
ToneReprCurve::Lut(table)
}
})
}
}
/// Matrix Coefficients Enum (from ISO/IEC 23091-4 / MPEG CICP)
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
pub enum MatrixCoefficients {
Identity = 0, // RGB (Identity matrix)
Bt709 = 1, // Rec. 709
Unspecified = 2, // Unspecified
Reserved = 3, // Reserved
Fcc = 4, // FCC
Bt470Bg = 5, // BT.470BG / BT.601-625
Smpte170m = 6, // SMPTE 170M / BT.601-525
Smpte240m = 7, // SMPTE 240M
YCgCo = 8, // YCgCo
Bt2020Ncl = 9, // BT.2020 (non-constant luminance)
Bt2020Cl = 10, // BT.2020 (constant luminance)
Smpte2085 = 11, // SMPTE ST 2085
ChromaticityDerivedNCL = 12, // Chromaticity-derived non-constant luminance
ChromaticityDerivedCL = 13, // Chromaticity-derived constant luminance
ICtCp = 14, // ICtCp
}
impl TryFrom<u8> for MatrixCoefficients {
type Error = CmsError;
fn try_from(value: u8) -> Result<Self, CmsError> {
match value {
0 => Ok(MatrixCoefficients::Identity),
1 => Ok(MatrixCoefficients::Bt709),
2 => Ok(MatrixCoefficients::Unspecified),
3 => Ok(MatrixCoefficients::Reserved),
4 => Ok(MatrixCoefficients::Fcc),
5 => Ok(MatrixCoefficients::Bt470Bg),
6 => Ok(MatrixCoefficients::Smpte170m),
7 => Ok(MatrixCoefficients::Smpte240m),
8 => Ok(MatrixCoefficients::YCgCo),
9 => Ok(MatrixCoefficients::Bt2020Ncl),
10 => Ok(MatrixCoefficients::Bt2020Cl),
11 => Ok(MatrixCoefficients::Smpte2085),
12 => Ok(MatrixCoefficients::ChromaticityDerivedNCL),
13 => Ok(MatrixCoefficients::ChromaticityDerivedCL),
14 => Ok(MatrixCoefficients::ICtCp),
_ => Err(CmsError::InvalidCicp),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::WHITE_POINT_D65;
#[test]
fn test_to_xyz_using_absolute_coordinates() {
let conversion_matrix = ColorPrimaries::BT_709.transform_to_xyz_d(WHITE_POINT_D65);
assert!((conversion_matrix.v[0][0] - 0.4121524015214193).abs() < 1e-14);
assert!((conversion_matrix.v[1][1] - 0.7153537403945436).abs() < 1e-14);
assert!((conversion_matrix.v[2][2] - 0.9497138466283235).abs() < 1e-14);
}
#[test]
fn test_to_xyz_using_absolute_coordinates_xyz() {
let conversion_matrix = ColorPrimaries::XYZ.transform_to_xyz_d(WHITE_POINT_D65);
assert!((conversion_matrix.v[0][0] - 0.95015469385536477).abs() < 1e-14);
assert!((conversion_matrix.v[1][1] - 1.0).abs() < 1e-14);
assert!((conversion_matrix.v[2][2] - 1.0882590676722474).abs() < 1e-14);
}
#[test]
fn test_to_xyz_using_absolute_coordinates_f() {
let conversion_matrix = ColorPrimaries::BT_709.transform_to_xyz(WHITE_POINT_D65);
assert!((conversion_matrix.v[0][0] - 0.4121524015214193).abs() < 1e-5);
assert!((conversion_matrix.v[1][1] - 0.7153537403945436).abs() < 1e-5);
assert!((conversion_matrix.v[2][2] - 0.9497138466283235).abs() < 1e-5);
}
}

View File

@@ -1,121 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//
// use crate::conversions::interpolator::{MultidimensionalInterpolation, Tetrahedral};
// use crate::conversions::transform_lut4_to_4::{NonFiniteVector3fLerp, Vector3fCmykLerp};
// use crate::mlaf::mlaf;
// use crate::{Chromaticity, ColorProfile, DataColorSpace, Lab, Xyz};
//
// impl ColorProfile {
// #[inline]
// pub(crate) fn detect_black_point<const GRID_SIZE: usize>(&self, lut: &[f32]) -> Option<Xyz> {
// if self.color_space == DataColorSpace::Cmyk {
// // if let Some(mut bp) = self.black_point {
// // if let Some(wp) = self.media_white_point.map(|x| x.normalize()) {
// // if wp != Chromaticity::D50.to_xyz() {
// // let ad = adaption_matrix(wp, Chromaticity::D50.to_xyz());
// // let v = ad.mul_vector(bp.to_vector());
// // bp = Xyz {
// // x: v.v[0],
// // y: v.v[1],
// // z: v.v[2],
// // };
// // }
// // }
// // let mut lab = Lab::from_xyz(bp);
// // lab.a = 0.;
// // lab.b = 0.;
// // if lab.l > 50. {
// // lab.l = 50.;
// // }
// // bp = lab.to_xyz();
// // return Some(bp);
// // }
// let c = 65535;
// let m = 65535;
// let y = 65535;
// let k = 65535;
//
// let linear_k: f32 = k as f32 * (1. / 65535.);
// let w: i32 = k * (GRID_SIZE as i32 - 1) / 65535;
// let w_n: i32 = (w + 1).min(GRID_SIZE as i32 - 1);
// let t: f32 = linear_k * (GRID_SIZE as i32 - 1) as f32 - w as f32;
//
// let grid_size = GRID_SIZE as i32;
// let grid_size3 = grid_size * grid_size * grid_size;
//
// let table1 = &lut[(w * grid_size3 * 3) as usize..];
// let table2 = &lut[(w_n * grid_size3 * 3) as usize..];
//
// let tetrahedral1 = Tetrahedral::<GRID_SIZE>::new(table1);
// let tetrahedral2 = Tetrahedral::<GRID_SIZE>::new(table2);
// let r1 = tetrahedral1.inter3(c, m, y);
// let r2 = tetrahedral2.inter3(c, m, y);
// let r = NonFiniteVector3fLerp::interpolate(r1, r2, t, 1.0);
//
// let mut lab = Lab::from_xyz(Xyz {
// x: r.v[0],
// y: r.v[1],
// z: r.v[2],
// });
// lab.a = 0.;
// lab.b = 0.;
// if lab.l > 50. {
// lab.l = 50.;
// }
// let bp = lab.to_xyz();
//
// return Some(bp);
// }
// if self.color_space == DataColorSpace::Rgb {
// return Some(Xyz::new(0.0, 0.0, 0.0));
// }
// None
// }
// }
//
// pub(crate) fn compensate_bpc_in_lut(lut_xyz: &mut [f32], src_bp: Xyz, dst_bp: Xyz) {
// const WP_50: Xyz = Chromaticity::D50.to_xyz();
// let tx = src_bp.x - WP_50.x;
// let ty = src_bp.y - WP_50.y;
// let tz = src_bp.z - WP_50.z;
// let ax = (dst_bp.x - WP_50.x) / tx;
// let ay = (dst_bp.y - WP_50.y) / ty;
// let az = (dst_bp.z - WP_50.z) / tz;
//
// let bx = -WP_50.x * (dst_bp.x - src_bp.x) / tx;
// let by = -WP_50.y * (dst_bp.y - src_bp.y) / ty;
// let bz = -WP_50.z * (dst_bp.z - src_bp.z) / tz;
//
// for dst in lut_xyz.chunks_exact_mut(3) {
// dst[0] = mlaf(bx, dst[0], ax);
// dst[1] = mlaf(by, dst[1], ay);
// dst[2] = mlaf(bz, dst[2], az);
// }
// }

View File

@@ -1,388 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::transform::PointeeSizeExpressible;
use crate::{CmsError, Layout, TransformExecutor};
use num_traits::AsPrimitive;
#[derive(Clone)]
struct TransformGray2RgbFusedExecutor<T, const SRC_LAYOUT: u8, const DEST_LAYOUT: u8> {
fused_gamma: Box<[T; 65536]>,
bit_depth: usize,
}
pub(crate) fn make_gray_to_x<
T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync,
const BUCKET: usize,
>(
src_layout: Layout,
dst_layout: Layout,
gray_linear: &[f32; BUCKET],
gray_gamma: &[T; 65536],
bit_depth: usize,
gamma_lut: usize,
) -> Result<Box<dyn TransformExecutor<T> + Sync + Send>, CmsError>
where
u32: AsPrimitive<T>,
{
if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
return Err(CmsError::UnsupportedProfileConnection);
}
let mut fused_gamma = Box::new([T::default(); 65536]);
let max_lut_size = (gamma_lut - 1) as f32;
for (&src, dst) in gray_linear.iter().zip(fused_gamma.iter_mut()) {
let possible_value = ((src * max_lut_size).round() as u32).min(max_lut_size as u32) as u16;
*dst = gray_gamma[possible_value as usize];
}
match src_layout {
Layout::Gray => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgb as u8 },
> {
fused_gamma,
bit_depth,
})),
Layout::Rgba => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
fused_gamma,
bit_depth,
})),
Layout::Gray => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
fused_gamma,
bit_depth,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
fused_gamma,
bit_depth,
})),
_ => unreachable!(),
},
Layout::GrayAlpha => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
fused_gamma,
bit_depth,
})),
Layout::Rgba => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
fused_gamma,
bit_depth,
})),
Layout::Gray => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
fused_gamma,
bit_depth,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGray2RgbFusedExecutor::<
T,
{ Layout::GrayAlpha as u8 },
{ Layout::GrayAlpha as u8 },
> {
fused_gamma,
bit_depth,
})),
_ => unreachable!(),
},
_ => Err(CmsError::UnsupportedProfileConnection),
}
}
impl<
T: Copy + Default + PointeeSizeExpressible + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
> TransformExecutor<T> for TransformGray2RgbFusedExecutor<T, SRC_LAYOUT, DST_LAYOUT>
where
u32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let is_gray_alpha = src_cn == Layout::GrayAlpha;
let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let g = self.fused_gamma[src[0]._as_usize()];
let a = if is_gray_alpha { src[1] } else { max_value };
dst[0] = g;
if dst_cn == Layout::GrayAlpha {
dst[1] = a;
} else if dst_cn == Layout::Rgb {
dst[1] = g;
dst[2] = g;
} else if dst_cn == Layout::Rgba {
dst[1] = g;
dst[2] = g;
dst[3] = a;
}
}
Ok(())
}
}
#[derive(Clone)]
struct TransformGrayToRgbExecutor<T, const SRC_LAYOUT: u8, const DEST_LAYOUT: u8> {
gray_linear: Box<[f32; 65536]>,
red_gamma: Box<[T; 65536]>,
green_gamma: Box<[T; 65536]>,
blue_gamma: Box<[T; 65536]>,
bit_depth: usize,
gamma_lut: usize,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn make_gray_to_unfused<
T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync,
const BUCKET: usize,
>(
src_layout: Layout,
dst_layout: Layout,
gray_linear: Box<[f32; 65536]>,
red_gamma: Box<[T; 65536]>,
green_gamma: Box<[T; 65536]>,
blue_gamma: Box<[T; 65536]>,
bit_depth: usize,
gamma_lut: usize,
) -> Result<Box<dyn TransformExecutor<T> + Sync + Send>, CmsError>
where
u32: AsPrimitive<T>,
{
if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
return Err(CmsError::UnsupportedProfileConnection);
}
if dst_layout != Layout::Rgb && dst_layout != Layout::Rgba {
return Err(CmsError::UnsupportedProfileConnection);
}
match src_layout {
Layout::Gray => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgb as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
Layout::Rgba => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
Layout::Gray => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
_ => Err(CmsError::UnsupportedProfileConnection),
},
Layout::GrayAlpha => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
Layout::Rgba => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
Layout::Gray => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGrayToRgbExecutor::<
T,
{ Layout::GrayAlpha as u8 },
{ Layout::GrayAlpha as u8 },
> {
gray_linear,
red_gamma,
green_gamma,
blue_gamma,
bit_depth,
gamma_lut,
})),
_ => Err(CmsError::UnsupportedProfileConnection),
},
_ => Err(CmsError::UnsupportedProfileConnection),
}
}
impl<
T: Copy + Default + PointeeSizeExpressible + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
> TransformExecutor<T> for TransformGrayToRgbExecutor<T, SRC_LAYOUT, DST_LAYOUT>
where
u32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let is_gray_alpha = src_cn == Layout::GrayAlpha;
let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
let max_lut_size = (self.gamma_lut - 1) as f32;
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let g = self.gray_linear[src[0]._as_usize()];
let a = if is_gray_alpha { src[1] } else { max_value };
let possible_value = ((g * max_lut_size).round() as u16) as usize;
let red_value = self.red_gamma[possible_value];
let green_value = self.green_gamma[possible_value];
let blue_value = self.blue_gamma[possible_value];
if dst_cn == Layout::Rgb {
dst[0] = red_value;
dst[1] = green_value;
dst[2] = blue_value;
} else if dst_cn == Layout::Rgba {
dst[0] = red_value;
dst[1] = green_value;
dst[2] = blue_value;
dst[3] = a;
} else {
return Err(CmsError::UnsupportedProfileConnection);
}
}
Ok(())
}
}

View File

@@ -1,383 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 7/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::transform::PointeeSizeExpressible;
use crate::trc::ToneCurveEvaluator;
use crate::{CmsError, Layout, Rgb, TransformExecutor};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
struct TransformGrayOneToOneExecutor<T, const SRC_LAYOUT: u8, const DEST_LAYOUT: u8> {
linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
_phantom: PhantomData<T>,
bit_depth: usize,
}
pub(crate) fn make_gray_to_one_trc_extended<
T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync + AsPrimitive<f32>,
>(
src_layout: Layout,
dst_layout: Layout,
linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Sync + Send>, CmsError>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
return Err(CmsError::UnsupportedProfileConnection);
}
match src_layout {
Layout::Gray => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgb as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Rgba => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Gray => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
_ => unreachable!(),
},
Layout::GrayAlpha => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Rgba => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Gray => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGrayOneToOneExecutor::<
T,
{ Layout::GrayAlpha as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
_ => unreachable!(),
},
_ => Err(CmsError::UnsupportedProfileConnection),
}
}
impl<
T: Copy + Default + PointeeSizeExpressible + 'static + AsPrimitive<f32>,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
> TransformExecutor<T> for TransformGrayOneToOneExecutor<T, SRC_LAYOUT, DST_LAYOUT>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let is_gray_alpha = src_cn == Layout::GrayAlpha;
let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let linear_value = self.linear_eval.evaluate_value(src[0].as_());
let g = self.gamma_eval.evaluate_value(linear_value).as_();
let a = if is_gray_alpha { src[1] } else { max_value };
dst[0] = g;
if dst_cn == Layout::GrayAlpha {
dst[1] = a;
} else if dst_cn == Layout::Rgb {
dst[1] = g;
dst[2] = g;
} else if dst_cn == Layout::Rgba {
dst[1] = g;
dst[2] = g;
dst[3] = a;
}
}
Ok(())
}
}
struct TransformGrayToRgbExtendedExecutor<T, const SRC_LAYOUT: u8, const DEST_LAYOUT: u8> {
linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
_phantom: PhantomData<T>,
bit_depth: usize,
}
pub(crate) fn make_gray_to_rgb_extended<
T: Copy + Default + PointeeSizeExpressible + 'static + Send + Sync + AsPrimitive<f32>,
>(
src_layout: Layout,
dst_layout: Layout,
linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Sync + Send>, CmsError>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
if src_layout != Layout::Gray && src_layout != Layout::GrayAlpha {
return Err(CmsError::UnsupportedProfileConnection);
}
if dst_layout != Layout::Rgb && dst_layout != Layout::Rgba {
return Err(CmsError::UnsupportedProfileConnection);
}
match src_layout {
Layout::Gray => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgb as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Rgba => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Gray => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
_ => Err(CmsError::UnsupportedProfileConnection),
},
Layout::GrayAlpha => match dst_layout {
Layout::Rgb => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Rgba => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Rgba as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::Gray => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::Gray as u8 },
{ Layout::Gray as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
Layout::GrayAlpha => Ok(Box::new(TransformGrayToRgbExtendedExecutor::<
T,
{ Layout::GrayAlpha as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
_phantom: PhantomData,
bit_depth,
})),
_ => Err(CmsError::UnsupportedProfileConnection),
},
_ => Err(CmsError::UnsupportedProfileConnection),
}
}
impl<
T: Copy + Default + PointeeSizeExpressible + 'static + AsPrimitive<f32>,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
> TransformExecutor<T> for TransformGrayToRgbExtendedExecutor<T, SRC_LAYOUT, DST_LAYOUT>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let is_gray_alpha = src_cn == Layout::GrayAlpha;
let max_value: T = ((1u32 << self.bit_depth as u32) - 1u32).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let linear_value = self.linear_eval.evaluate_value(src[0].as_());
let a = if is_gray_alpha { src[1] } else { max_value };
let tristimulus = self.gamma_eval.evaluate_tristimulus(Rgb::new(
linear_value,
linear_value,
linear_value,
));
let red_value = tristimulus.r.as_();
let green_value = tristimulus.g.as_();
let blue_value = tristimulus.b.as_();
if dst_cn == Layout::Rgb {
dst[0] = red_value;
dst[1] = green_value;
dst[2] = blue_value;
} else if dst_cn == Layout::Rgba {
dst[0] = red_value;
dst[1] = green_value;
dst[2] = blue_value;
dst[3] = a;
} else {
return Err(CmsError::UnsupportedProfileConnection);
}
}
Ok(())
}
}

View File

@@ -1,599 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#![allow(dead_code)]
use crate::conversions::lut_transforms::LUT_SAMPLING;
use crate::math::{FusedMultiplyAdd, FusedMultiplyNegAdd};
use crate::{Vector3f, Vector4f};
use std::ops::{Add, Mul, Sub};
#[cfg(feature = "options")]
pub(crate) struct Tetrahedral<const GRID_SIZE: usize> {}
#[cfg(feature = "options")]
pub(crate) struct Pyramidal<const GRID_SIZE: usize> {}
#[cfg(feature = "options")]
pub(crate) struct Prismatic<const GRID_SIZE: usize> {}
pub(crate) struct Trilinear<const GRID_SIZE: usize> {}
#[derive(Debug, Copy, Clone, Default)]
pub(crate) struct BarycentricWeight<V> {
pub x: i32,
pub x_n: i32,
pub w: V,
}
impl BarycentricWeight<f32> {
pub(crate) fn create_ranged_256<const GRID_SIZE: usize>() -> Box<[BarycentricWeight<f32>; 256]>
{
let mut weights = Box::new([BarycentricWeight::default(); 256]);
for (index, weight) in weights.iter_mut().enumerate() {
const SCALE: f32 = 1.0 / LUT_SAMPLING as f32;
let x: i32 = index as i32 * (GRID_SIZE as i32 - 1) / LUT_SAMPLING as i32;
let x_n: i32 = (x + 1).min(GRID_SIZE as i32 - 1);
let scale = (GRID_SIZE as i32 - 1) as f32 * SCALE;
let dr = index as f32 * scale - x as f32;
*weight = BarycentricWeight { x, x_n, w: dr };
}
weights
}
#[cfg(feature = "options")]
pub(crate) fn create_binned<const GRID_SIZE: usize, const BINS: usize>()
-> Box<[BarycentricWeight<f32>; 65536]> {
let mut weights = Box::new([BarycentricWeight::<f32>::default(); 65536]);
let b_scale: f32 = 1.0 / (BINS - 1) as f32;
for (index, weight) in weights.iter_mut().enumerate().take(BINS) {
let x: i32 = (index as f32 * (GRID_SIZE as i32 - 1) as f32 * b_scale).floor() as i32;
let x_n: i32 = (x + 1).min(GRID_SIZE as i32 - 1);
let scale = (GRID_SIZE as i32 - 1) as f32 * b_scale;
let dr = index as f32 * scale - x as f32;
*weight = BarycentricWeight { x, x_n, w: dr };
}
weights
}
}
#[allow(dead_code)]
impl BarycentricWeight<i16> {
pub(crate) fn create_ranged_256<const GRID_SIZE: usize>() -> Box<[BarycentricWeight<i16>; 256]>
{
let mut weights = Box::new([BarycentricWeight::default(); 256]);
for (index, weight) in weights.iter_mut().enumerate() {
const SCALE: f32 = 1.0 / LUT_SAMPLING as f32;
let x: i32 = index as i32 * (GRID_SIZE as i32 - 1) / LUT_SAMPLING as i32;
let x_n: i32 = (x + 1).min(GRID_SIZE as i32 - 1);
let scale = (GRID_SIZE as i32 - 1) as f32 * SCALE;
const Q: f32 = ((1i32 << 15) - 1) as f32;
let dr = ((index as f32 * scale - x as f32) * Q)
.round()
.min(i16::MAX as f32)
.max(-i16::MAX as f32) as i16;
*weight = BarycentricWeight { x, x_n, w: dr };
}
weights
}
#[cfg(feature = "options")]
pub(crate) fn create_binned<const GRID_SIZE: usize, const BINS: usize>()
-> Box<[BarycentricWeight<i16>; 65536]> {
let mut weights = Box::new([BarycentricWeight::<i16>::default(); 65536]);
let b_scale: f32 = 1.0 / (BINS - 1) as f32;
for (index, weight) in weights.iter_mut().enumerate().take(BINS) {
let x: i32 = (index as f32 * (GRID_SIZE as i32 - 1) as f32 * b_scale).floor() as i32;
let x_n: i32 = (x + 1).min(GRID_SIZE as i32 - 1);
let scale = (GRID_SIZE as i32 - 1) as f32 * b_scale;
const Q: f32 = ((1i32 << 15) - 1) as f32;
let dr = ((index as f32 * scale - x as f32) * Q)
.round()
.min(i16::MAX as f32)
.max(-i16::MAX as f32) as i16;
*weight = BarycentricWeight { x, x_n, w: dr };
}
weights
}
}
trait Fetcher<T> {
fn fetch(&self, x: i32, y: i32, z: i32) -> T;
}
struct TetrahedralFetchVector3f<'a, const GRID_SIZE: usize> {
cube: &'a [f32],
}
pub(crate) trait MultidimensionalInterpolation {
fn inter3(
&self,
cube: &[f32],
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
) -> Vector3f;
fn inter4(
&self,
cube: &[f32],
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
) -> Vector4f;
}
impl<const GRID_SIZE: usize> Fetcher<Vector3f> for TetrahedralFetchVector3f<'_, GRID_SIZE> {
#[inline(always)]
fn fetch(&self, x: i32, y: i32, z: i32) -> Vector3f {
let offset = (x as u32 * (GRID_SIZE as u32 * GRID_SIZE as u32)
+ y as u32 * GRID_SIZE as u32
+ z as u32) as usize
* 3;
let jx = &self.cube[offset..offset + 3];
Vector3f {
v: [jx[0], jx[1], jx[2]],
}
}
}
struct TetrahedralFetchVector4f<'a, const GRID_SIZE: usize> {
cube: &'a [f32],
}
impl<const GRID_SIZE: usize> Fetcher<Vector4f> for TetrahedralFetchVector4f<'_, GRID_SIZE> {
#[inline(always)]
fn fetch(&self, x: i32, y: i32, z: i32) -> Vector4f {
let offset = (x as u32 * (GRID_SIZE as u32 * GRID_SIZE as u32)
+ y as u32 * GRID_SIZE as u32
+ z as u32) as usize
* 4;
let jx = &self.cube[offset..offset + 4];
Vector4f {
v: [jx[0], jx[1], jx[2], jx[3]],
}
}
}
#[cfg(feature = "options")]
impl<const GRID_SIZE: usize> Tetrahedral<GRID_SIZE> {
#[inline]
fn interpolate<
T: Copy
+ Sub<T, Output = T>
+ Mul<T, Output = T>
+ Mul<f32, Output = T>
+ Add<T, Output = T>
+ From<f32>
+ FusedMultiplyAdd<T>,
>(
&self,
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
r: impl Fetcher<T>,
) -> T {
let x: i32 = lut_r.x;
let y: i32 = lut_g.x;
let z: i32 = lut_b.x;
let x_n: i32 = lut_r.x_n;
let y_n: i32 = lut_g.x_n;
let z_n: i32 = lut_b.x_n;
let rx = lut_r.w;
let ry = lut_g.w;
let rz = lut_b.w;
let c0 = r.fetch(x, y, z);
let c2;
let c1;
let c3;
if rx >= ry {
if ry >= rz {
//rx >= ry && ry >= rz
c1 = r.fetch(x_n, y, z) - c0;
c2 = r.fetch(x_n, y_n, z) - r.fetch(x_n, y, z);
c3 = r.fetch(x_n, y_n, z_n) - r.fetch(x_n, y_n, z);
} else if rx >= rz {
//rx >= rz && rz >= ry
c1 = r.fetch(x_n, y, z) - c0;
c2 = r.fetch(x_n, y_n, z_n) - r.fetch(x_n, y, z_n);
c3 = r.fetch(x_n, y, z_n) - r.fetch(x_n, y, z);
} else {
//rz > rx && rx >= ry
c1 = r.fetch(x_n, y, z_n) - r.fetch(x, y, z_n);
c2 = r.fetch(x_n, y_n, z_n) - r.fetch(x_n, y, z_n);
c3 = r.fetch(x, y, z_n) - c0;
}
} else if rx >= rz {
//ry > rx && rx >= rz
c1 = r.fetch(x_n, y_n, z) - r.fetch(x, y_n, z);
c2 = r.fetch(x, y_n, z) - c0;
c3 = r.fetch(x_n, y_n, z_n) - r.fetch(x_n, y_n, z);
} else if ry >= rz {
//ry >= rz && rz > rx
c1 = r.fetch(x_n, y_n, z_n) - r.fetch(x, y_n, z_n);
c2 = r.fetch(x, y_n, z) - c0;
c3 = r.fetch(x, y_n, z_n) - r.fetch(x, y_n, z);
} else {
//rz > ry && ry > rx
c1 = r.fetch(x_n, y_n, z_n) - r.fetch(x, y_n, z_n);
c2 = r.fetch(x, y_n, z_n) - r.fetch(x, y, z_n);
c3 = r.fetch(x, y, z_n) - c0;
}
let s0 = c0.mla(c1, T::from(rx));
let s1 = s0.mla(c2, T::from(ry));
s1.mla(c3, T::from(rz))
}
}
macro_rules! define_md_inter {
($interpolator: ident) => {
impl<const GRID_SIZE: usize> MultidimensionalInterpolation for $interpolator<GRID_SIZE> {
fn inter3(
&self,
cube: &[f32],
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
) -> Vector3f {
self.interpolate::<Vector3f>(
lut_r,
lut_g,
lut_b,
TetrahedralFetchVector3f::<GRID_SIZE> { cube },
)
}
fn inter4(
&self,
cube: &[f32],
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
) -> Vector4f {
self.interpolate::<Vector4f>(
lut_r,
lut_g,
lut_b,
TetrahedralFetchVector4f::<GRID_SIZE> { cube },
)
}
}
};
}
#[cfg(feature = "options")]
define_md_inter!(Tetrahedral);
#[cfg(feature = "options")]
define_md_inter!(Pyramidal);
#[cfg(feature = "options")]
define_md_inter!(Prismatic);
define_md_inter!(Trilinear);
#[cfg(feature = "options")]
impl<const GRID_SIZE: usize> Pyramidal<GRID_SIZE> {
#[inline]
fn interpolate<
T: Copy
+ Sub<T, Output = T>
+ Mul<T, Output = T>
+ Mul<f32, Output = T>
+ Add<T, Output = T>
+ From<f32>
+ FusedMultiplyAdd<T>,
>(
&self,
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
r: impl Fetcher<T>,
) -> T {
let x: i32 = lut_r.x;
let y: i32 = lut_g.x;
let z: i32 = lut_b.x;
let x_n: i32 = lut_r.x_n;
let y_n: i32 = lut_g.x_n;
let z_n: i32 = lut_b.x_n;
let dr = lut_r.w;
let dg = lut_g.w;
let db = lut_b.w;
let c0 = r.fetch(x, y, z);
if dr > db && dg > db {
let x0 = r.fetch(x_n, y_n, z_n);
let x1 = r.fetch(x_n, y_n, z);
let x2 = r.fetch(x_n, y, z);
let x3 = r.fetch(x, y_n, z);
let c1 = x0 - x1;
let c2 = x2 - c0;
let c3 = x3 - c0;
let c4 = c0 - x3 - x2 + x1;
let s0 = c0.mla(c1, T::from(db));
let s1 = s0.mla(c2, T::from(dr));
let s2 = s1.mla(c3, T::from(dg));
s2.mla(c4, T::from(dr * dg))
} else if db > dr && dg > dr {
let x0 = r.fetch(x, y, z_n);
let x1 = r.fetch(x_n, y_n, z_n);
let x2 = r.fetch(x, y_n, z_n);
let x3 = r.fetch(x, y_n, z);
let c1 = x0 - c0;
let c2 = x1 - x2;
let c3 = x3 - c0;
let c4 = c0 - x3 - x0 + x2;
let s0 = c0.mla(c1, T::from(db));
let s1 = s0.mla(c2, T::from(dr));
let s2 = s1.mla(c3, T::from(dg));
s2.mla(c4, T::from(dg * db))
} else {
let x0 = r.fetch(x, y, z_n);
let x1 = r.fetch(x_n, y, z);
let x2 = r.fetch(x_n, y, z_n);
let x3 = r.fetch(x_n, y_n, z_n);
let c1 = x0 - c0;
let c2 = x1 - c0;
let c3 = x3 - x2;
let c4 = c0 - x1 - x0 + x2;
let s0 = c0.mla(c1, T::from(db));
let s1 = s0.mla(c2, T::from(dr));
let s2 = s1.mla(c3, T::from(dg));
s2.mla(c4, T::from(db * dr))
}
}
}
#[cfg(feature = "options")]
impl<const GRID_SIZE: usize> Prismatic<GRID_SIZE> {
#[inline(always)]
fn interpolate<
T: Copy
+ Sub<T, Output = T>
+ Mul<T, Output = T>
+ Mul<f32, Output = T>
+ Add<T, Output = T>
+ From<f32>
+ FusedMultiplyAdd<T>,
>(
&self,
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
r: impl Fetcher<T>,
) -> T {
let x: i32 = lut_r.x;
let y: i32 = lut_g.x;
let z: i32 = lut_b.x;
let x_n: i32 = lut_r.x_n;
let y_n: i32 = lut_g.x_n;
let z_n: i32 = lut_b.x_n;
let dr = lut_r.w;
let dg = lut_g.w;
let db = lut_b.w;
let c0 = r.fetch(x, y, z);
if db >= dr {
let x0 = r.fetch(x, y, z_n);
let x1 = r.fetch(x_n, y, z_n);
let x2 = r.fetch(x, y_n, z);
let x3 = r.fetch(x, y_n, z_n);
let x4 = r.fetch(x_n, y_n, z_n);
let c1 = x0 - c0;
let c2 = x1 - x0;
let c3 = x2 - c0;
let c4 = c0 - x2 - x0 + x3;
let c5 = x0 - x3 - x1 + x4;
let s0 = c0.mla(c1, T::from(db));
let s1 = s0.mla(c2, T::from(dr));
let s2 = s1.mla(c3, T::from(dg));
let s3 = s2.mla(c4, T::from(dg * db));
s3.mla(c5, T::from(dr * dg))
} else {
let x0 = r.fetch(x_n, y, z);
let x1 = r.fetch(x_n, y, z_n);
let x2 = r.fetch(x, y_n, z);
let x3 = r.fetch(x_n, y_n, z);
let x4 = r.fetch(x_n, y_n, z_n);
let c1 = x1 - x0;
let c2 = x0 - c0;
let c3 = x2 - c0;
let c4 = x0 - x3 - x1 + x4;
let c5 = c0 - x2 - x0 + x3;
let s0 = c0.mla(c1, T::from(db));
let s1 = s0.mla(c2, T::from(dr));
let s2 = s1.mla(c3, T::from(dg));
let s3 = s2.mla(c4, T::from(dg * db));
s3.mla(c5, T::from(dr * dg))
}
}
}
impl<const GRID_SIZE: usize> Trilinear<GRID_SIZE> {
#[inline(always)]
fn interpolate<
T: Copy
+ Sub<T, Output = T>
+ Mul<T, Output = T>
+ Mul<f32, Output = T>
+ Add<T, Output = T>
+ From<f32>
+ FusedMultiplyAdd<T>
+ FusedMultiplyNegAdd<T>,
>(
&self,
lut_r: &BarycentricWeight<f32>,
lut_g: &BarycentricWeight<f32>,
lut_b: &BarycentricWeight<f32>,
r: impl Fetcher<T>,
) -> T {
let x: i32 = lut_r.x;
let y: i32 = lut_g.x;
let z: i32 = lut_b.x;
let x_n: i32 = lut_r.x_n;
let y_n: i32 = lut_g.x_n;
let z_n: i32 = lut_b.x_n;
let dr = lut_r.w;
let dg = lut_g.w;
let db = lut_b.w;
let w0 = T::from(dr);
let w1 = T::from(dg);
let w2 = T::from(db);
let c000 = r.fetch(x, y, z);
let c100 = r.fetch(x_n, y, z);
let c010 = r.fetch(x, y_n, z);
let c110 = r.fetch(x_n, y_n, z);
let c001 = r.fetch(x, y, z_n);
let c101 = r.fetch(x_n, y, z_n);
let c011 = r.fetch(x, y_n, z_n);
let c111 = r.fetch(x_n, y_n, z_n);
let dx = T::from(dr);
let c00 = c000.neg_mla(c000, dx).mla(c100, w0);
let c10 = c010.neg_mla(c010, dx).mla(c110, w0);
let c01 = c001.neg_mla(c001, dx).mla(c101, w0);
let c11 = c011.neg_mla(c011, dx).mla(c111, w0);
let dy = T::from(dg);
let c0 = c00.neg_mla(c00, dy).mla(c10, w1);
let c1 = c01.neg_mla(c01, dy).mla(c11, w1);
let dz = T::from(db);
c0.neg_mla(c0, dz).mla(c1, w2)
}
}
pub(crate) trait LutBarycentricReduction<T, U> {
fn reduce<const SRC_BP: usize, const BINS: usize>(v: T) -> U;
}
impl LutBarycentricReduction<u8, u8> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: u8) -> u8 {
v
}
}
impl LutBarycentricReduction<u8, u16> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: u8) -> u16 {
if BINS == 65536 {
return u16::from_ne_bytes([v, v]);
}
if BINS == 16384 {
return u16::from_ne_bytes([v, v]) >> 2;
}
unimplemented!()
}
}
impl LutBarycentricReduction<f32, u8> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: f32) -> u8 {
(v * 255.).round().min(255.).max(0.) as u8
}
}
impl LutBarycentricReduction<f32, u16> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: f32) -> u16 {
let scale = (BINS - 1) as f32;
(v * scale).round().min(scale).max(0.) as u16
}
}
impl LutBarycentricReduction<f64, u8> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: f64) -> u8 {
(v * 255.).round().min(255.).max(0.) as u8
}
}
impl LutBarycentricReduction<f64, u16> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: f64) -> u16 {
let scale = (BINS - 1) as f64;
(v * scale).round().min(scale).max(0.) as u16
}
}
impl LutBarycentricReduction<u16, u16> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: u16) -> u16 {
let src_scale = 1. / ((1 << SRC_BP) - 1) as f32;
let scale = src_scale * (BINS - 1) as f32;
(v as f32 * scale).round().min(scale).max(0.) as u16
}
}
impl LutBarycentricReduction<u16, u8> for () {
#[inline(always)]
fn reduce<const SRC_BP: usize, const BINS: usize>(v: u16) -> u8 {
let shift = SRC_BP as u16 - 8;
if SRC_BP == 16 {
(v >> 8) as u8
} else {
(v >> shift).min(255) as u8
}
}
}

View File

@@ -1,118 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 8/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::KatanaPostFinalizationStage;
use crate::{CmsError, DataColorSpace, Layout, PointeeSizeExpressible};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) struct InjectAlphaStage<I> {
pub(crate) dst_layout: Layout,
pub(crate) target_color_space: DataColorSpace,
pub(crate) _phantom: PhantomData<I>,
pub(crate) bit_depth: usize,
}
pub(crate) struct CopyAlphaStage<I> {
pub(crate) src_layout: Layout,
pub(crate) dst_layout: Layout,
pub(crate) target_color_space: DataColorSpace,
pub(crate) _phantom: PhantomData<I>,
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaPostFinalizationStage<T> for InjectAlphaStage<T>
where
f32: AsPrimitive<T>,
{
fn finalize(&self, _: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let norm_value: T = (if T::FINITE {
((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
})
.as_();
if self.dst_layout == Layout::Rgba && self.target_color_space == DataColorSpace::Rgb {
for dst in dst.chunks_exact_mut(self.dst_layout.channels()) {
dst[3] = norm_value;
}
} else if self.dst_layout == Layout::GrayAlpha
&& self.target_color_space == DataColorSpace::Gray
{
for dst in dst.chunks_exact_mut(self.dst_layout.channels()) {
dst[1] = norm_value;
}
}
Ok(())
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaPostFinalizationStage<T> for CopyAlphaStage<T>
where
f32: AsPrimitive<T>,
{
fn finalize(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
if self.dst_layout == Layout::Rgba && self.target_color_space == DataColorSpace::Rgb {
if self.src_layout == Layout::Rgba {
for (src, dst) in src
.chunks_exact(self.src_layout.channels())
.zip(dst.chunks_exact_mut(self.dst_layout.channels()))
{
dst[3] = src[3];
}
} else if self.src_layout == Layout::GrayAlpha {
for (src, dst) in src
.chunks_exact(self.src_layout.channels())
.zip(dst.chunks_exact_mut(self.dst_layout.channels()))
{
dst[3] = src[1];
}
}
} else if self.dst_layout == Layout::GrayAlpha
&& self.target_color_space == DataColorSpace::Gray
{
if self.src_layout == Layout::Rgba {
for (src, dst) in src
.chunks_exact(self.src_layout.channels())
.zip(dst.chunks_exact_mut(self.dst_layout.channels()))
{
dst[1] = src[3];
}
} else if self.src_layout == Layout::GrayAlpha {
for (src, dst) in src
.chunks_exact(self.src_layout.channels())
.zip(dst.chunks_exact_mut(self.dst_layout.channels()))
{
dst[1] = src[1];
}
}
}
Ok(())
}
}

View File

@@ -1,483 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::{KatanaFinalStage, KatanaInitialStage};
use crate::mlaf::mlaf;
use crate::safe_math::SafeMul;
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, Cube, DataColorSpace, InterpolationMethod, LutMultidimensionalType, MalformedSize,
Matrix3d, Matrix3f, PointeeSizeExpressible, TransformOptions, Vector3d, Vector3f,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub(crate) enum MultidimensionalDirection {
DeviceToPcs,
PcsToDevice,
}
struct Multidimensional3x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
> {
a_curves: Option<Box<[Vec<f32>; 3]>>,
m_curves: Option<Box<[Vec<f32>; 3]>>,
b_curves: Option<Box<[Vec<f32>; 3]>>,
clut: Option<Vec<f32>>,
matrix: Matrix3f,
bias: Vector3f,
direction: MultidimensionalDirection,
options: TransformOptions,
pcs: DataColorSpace,
grid_size: [u8; 3],
_phantom: PhantomData<T>,
bit_depth: usize,
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
Multidimensional3x3<T>
{
fn execute_matrix_stage(&self, dst: &mut [f32]) {
let m = self.matrix;
let b = self.bias;
if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
}
}
}
fn execute_simple_curves(&self, dst: &mut [f32], curves: &[Vec<f32>; 3]) {
let curve0 = &curves[0];
let curve1 = &curves[1];
let curve2 = &curves[2];
for dst in dst.chunks_exact_mut(3) {
let a0 = dst[0];
let a1 = dst[1];
let a2 = dst[2];
let b0 = lut_interp_linear_float(a0, curve0);
let b1 = lut_interp_linear_float(a1, curve1);
let b2 = lut_interp_linear_float(a2, curve2);
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
}
}
fn to_pcs_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
input: &[T],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let norm_value = if T::FINITE {
1.0 / ((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
assert_eq!(
self.direction,
MultidimensionalDirection::DeviceToPcs,
"PCS to device cannot be used on `to pcs` stage"
);
// A -> B
// OR B - A A - curves stage
if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
if !clut.is_empty() {
let curve0 = &a_curves[0];
let curve1 = &a_curves[1];
let curve2 = &a_curves[2];
for (src, dst) in input.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
let b0 = lut_interp_linear_float(src[0].as_() * norm_value, curve0);
let b1 = lut_interp_linear_float(src[1].as_() * norm_value, curve1);
let b2 = lut_interp_linear_float(src[2].as_() * norm_value, curve2);
let interpolated = fetch(b0, b1, b2);
dst[0] = interpolated.v[0];
dst[1] = interpolated.v[1];
dst[2] = interpolated.v[2];
}
} else {
for (src, dst) in input.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
dst[0] = src[0].as_() * norm_value;
dst[1] = src[1].as_() * norm_value;
dst[2] = src[2].as_() * norm_value;
}
}
} else {
for (src, dst) in input.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
dst[0] = src[0].as_() * norm_value;
dst[1] = src[1].as_() * norm_value;
dst[2] = src[2].as_() * norm_value;
}
}
// Matrix stage
if let Some(m_curves) = self.m_curves.as_ref() {
self.execute_simple_curves(dst, m_curves);
self.execute_matrix_stage(dst);
}
// B-curves is mandatory
if let Some(b_curves) = &self.b_curves.as_ref() {
self.execute_simple_curves(dst, b_curves);
}
Ok(())
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaInitialStage<f32, T> for Multidimensional3x3<T>
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
if input.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let fixed_new_clut = Vec::new();
let new_clut = self.clut.as_ref().unwrap_or(&fixed_new_clut);
let lut = Cube::new_cube(new_clut, self.grid_size);
let mut new_dst = vec![0f32; input.len()];
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
self.to_pcs_impl(input, &mut new_dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
return Ok(new_dst);
}
match self.options.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z| lut.prism_vec3(x, y, z))?;
}
InterpolationMethod::Linear => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
}
}
Ok(new_dst)
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
Multidimensional3x3<T>
where
f32: AsPrimitive<T>,
{
fn to_output_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
src: &mut [f32],
dst: &mut [T],
fetch: Fetch,
) -> Result<(), CmsError> {
let norm_value = if T::FINITE {
((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
assert_eq!(
self.direction,
MultidimensionalDirection::PcsToDevice,
"Device to PCS cannot be used on `to output` stage"
);
if let Some(b_curves) = &self.b_curves.as_ref() {
self.execute_simple_curves(src, b_curves);
}
// Matrix stage
if let Some(m_curves) = self.m_curves.as_ref() {
self.execute_matrix_stage(src);
self.execute_simple_curves(src, m_curves);
}
if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
if !clut.is_empty() {
let curve0 = &a_curves[0];
let curve1 = &a_curves[1];
let curve2 = &a_curves[2];
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
let b0 = lut_interp_linear_float(src[0], curve0);
let b1 = lut_interp_linear_float(src[1], curve1);
let b2 = lut_interp_linear_float(src[2], curve2);
let interpolated = fetch(b0, b1, b2);
if T::FINITE {
dst[0] = (interpolated.v[0] * norm_value)
.round()
.max(0.0)
.min(norm_value)
.as_();
dst[1] = (interpolated.v[1] * norm_value)
.round()
.max(0.0)
.min(norm_value)
.as_();
dst[2] = (interpolated.v[2] * norm_value)
.round()
.max(0.0)
.min(norm_value)
.as_();
} else {
dst[0] = interpolated.v[0].as_();
dst[1] = interpolated.v[1].as_();
dst[2] = interpolated.v[2].as_();
}
}
} else {
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
if T::FINITE {
dst[0] = (src[0] * norm_value).round().max(0.0).min(norm_value).as_();
dst[1] = (src[1] * norm_value).round().max(0.0).min(norm_value).as_();
dst[2] = (src[2] * norm_value).round().max(0.0).min(norm_value).as_();
} else {
dst[0] = src[0].as_();
dst[1] = src[1].as_();
dst[2] = src[2].as_();
}
}
}
} else {
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
if T::FINITE {
dst[0] = (src[0] * norm_value).round().max(0.0).min(norm_value).as_();
dst[1] = (src[1] * norm_value).round().max(0.0).min(norm_value).as_();
dst[2] = (src[2] * norm_value).round().max(0.0).min(norm_value).as_();
} else {
dst[0] = src[0].as_();
dst[1] = src[1].as_();
dst[2] = src[2].as_();
}
}
}
Ok(())
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaFinalStage<f32, T> for Multidimensional3x3<T>
where
f32: AsPrimitive<T>,
{
fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
if src.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if src.len() != dst.len() {
return Err(CmsError::LaneSizeMismatch);
}
let fixed_new_clut = Vec::new();
let new_clut = self.clut.as_ref().unwrap_or(&fixed_new_clut);
let lut = Cube::new_cube(new_clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.to_output_impl(src, dst, |x, y, z| lut.trilinear_vec3(x, y, z));
}
match self.options.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.to_output_impl(src, dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.to_output_impl(src, dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.to_output_impl(src, dst, |x, y, z| lut.prism_vec3(x, y, z))?;
}
InterpolationMethod::Linear => {
self.to_output_impl(src, dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
}
}
Ok(())
}
}
fn make_multidimensional_3x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
direction: MultidimensionalDirection,
bit_depth: usize,
) -> Result<Multidimensional3x3<T>, CmsError> {
if mab.num_input_channels != 3 && mab.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
return Err(CmsError::InvalidAtoBLut);
}
let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
let clut: Option<Vec<f32>> = if mab.a_curves.len() == 3 && mab.clut.is_some() {
let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let lut_grid = (mab.grid_points[0] as usize)
.safe_mul(mab.grid_points[1] as usize)?
.safe_mul(mab.grid_points[2] as usize)?
.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
Some(clut)
} else {
None
};
let a_curves: Option<Box<[Vec<f32>; 3]>> = if mab.a_curves.len() == 3 && mab.clut.is_some() {
let mut arr = Box::<[Vec<f32>; 3]>::default();
for (a_curve, dst) in mab.a_curves.iter().zip(arr.iter_mut()) {
*dst = a_curve.to_clut()?;
}
Some(arr)
} else {
None
};
let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
let mut arr = Box::<[Vec<f32>; 3]>::default();
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if all_curves_linear {
None
} else {
for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
*dst = c_curve.to_clut()?;
}
Some(arr)
}
} else {
return Err(CmsError::InvalidAtoBLut);
};
let matrix = mab.matrix.to_f32();
let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let mut arr = Box::<[Vec<f32>; 3]>::default();
for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
*dst = curve.to_clut()?;
}
Some(arr)
} else {
None
}
} else {
None
};
let bias = mab.bias.cast();
let transform = Multidimensional3x3::<T> {
a_curves,
b_curves,
m_curves,
matrix,
direction,
options,
clut,
pcs,
grid_size,
bias,
_phantom: PhantomData,
bit_depth,
};
Ok(transform)
}
pub(crate) fn multi_dimensional_3x3_to_pcs<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError> {
let transform = make_multidimensional_3x3::<T>(
mab,
options,
pcs,
MultidimensionalDirection::DeviceToPcs,
bit_depth,
)?;
Ok(Box::new(transform))
}
pub(crate) fn multi_dimensional_3x3_to_device<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaFinalStage<f32, T> + Send + Sync>, CmsError>
where
f32: AsPrimitive<T>,
{
let transform = make_multidimensional_3x3::<T>(
mab,
options,
pcs,
MultidimensionalDirection::PcsToDevice,
bit_depth,
)?;
Ok(Box::new(transform))
}

View File

@@ -1,321 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::KatanaInitialStage;
use crate::conversions::katana::md3x3::MultidimensionalDirection;
use crate::mlaf::mlaf;
use crate::safe_math::SafeMul;
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, DataColorSpace, Hypercube, InterpolationMethod, LutMultidimensionalType,
MalformedSize, Matrix3d, Matrix3f, PointeeSizeExpressible, TransformOptions, Vector3d,
Vector3f,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) fn execute_simple_curves3(dst: &mut [f32], curves: &[Vec<f32>; 3]) {
let curve0 = &curves[0];
let curve1 = &curves[1];
let curve2 = &curves[2];
for dst in dst.chunks_exact_mut(3) {
let a0 = dst[0];
let a1 = dst[1];
let a2 = dst[2];
let b0 = lut_interp_linear_float(a0, curve0);
let b1 = lut_interp_linear_float(a1, curve1);
let b2 = lut_interp_linear_float(a2, curve2);
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
}
}
pub(crate) fn execute_matrix_stage3(matrix: Matrix3f, bias: Vector3f, dst: &mut [f32]) {
let m = matrix;
let b = bias;
if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
}
}
}
struct Multidimensional4x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
> {
a_curves: Option<Box<[Vec<f32>; 4]>>,
m_curves: Option<Box<[Vec<f32>; 3]>>,
b_curves: Option<Box<[Vec<f32>; 3]>>,
clut: Option<Vec<f32>>,
matrix: Matrix3f,
bias: Vector3f,
direction: MultidimensionalDirection,
options: TransformOptions,
pcs: DataColorSpace,
grid_size: [u8; 4],
_phantom: PhantomData<T>,
bit_depth: usize,
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
Multidimensional4x3<T>
{
fn to_pcs_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
&self,
input: &[T],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let norm_value = if T::FINITE {
1.0 / ((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
assert_eq!(
self.direction,
MultidimensionalDirection::DeviceToPcs,
"PCS to device cannot be used on `to pcs` stage"
);
// A -> B
// OR B - A A - curves stage
if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
if !clut.is_empty() {
let curve0 = &a_curves[0];
let curve1 = &a_curves[1];
let curve2 = &a_curves[2];
let curve3 = &a_curves[3];
for (src, dst) in input.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
let b0 = lut_interp_linear_float(src[0].as_() * norm_value, curve0);
let b1 = lut_interp_linear_float(src[1].as_() * norm_value, curve1);
let b2 = lut_interp_linear_float(src[2].as_() * norm_value, curve2);
let b3 = lut_interp_linear_float(src[3].as_() * norm_value, curve3);
let interpolated = fetch(b0, b1, b2, b3);
dst[0] = interpolated.v[0];
dst[1] = interpolated.v[1];
dst[2] = interpolated.v[2];
}
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
// Matrix stage
if let Some(m_curves) = self.m_curves.as_ref() {
execute_simple_curves3(dst, m_curves);
execute_matrix_stage3(self.matrix, self.bias, dst);
}
// B-curves is mandatory
if let Some(b_curves) = &self.b_curves.as_ref() {
execute_simple_curves3(dst, b_curves);
}
Ok(())
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaInitialStage<f32, T> for Multidimensional4x3<T>
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
if input.len() % 4 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let fixed_new_clut = Vec::new();
let new_clut = self.clut.as_ref().unwrap_or(&fixed_new_clut);
let lut = Hypercube::new_hypercube(new_clut, self.grid_size);
let mut new_dst = vec![0f32; (input.len() / 4) * 3];
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
lut.quadlinear_vec3(x, y, z, w)
})?;
return Ok(new_dst);
}
match self.options.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
lut.pyramid_vec3(x, y, z, w)
})?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
}
InterpolationMethod::Linear => {
self.to_pcs_impl(input, &mut new_dst, |x, y, z, w| {
lut.quadlinear_vec3(x, y, z, w)
})?;
}
}
Ok(new_dst)
}
}
fn make_multidimensional_4x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
direction: MultidimensionalDirection,
bit_depth: usize,
) -> Result<Multidimensional4x3<T>, CmsError> {
if mab.num_input_channels != 4 && mab.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
return Err(CmsError::InvalidAtoBLut);
}
let grid_size = [
mab.grid_points[0],
mab.grid_points[1],
mab.grid_points[2],
mab.grid_points[3],
];
let clut: Option<Vec<f32>> = if mab.a_curves.len() == 4 && mab.clut.is_some() {
let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let lut_grid = (mab.grid_points[0] as usize)
.safe_mul(mab.grid_points[1] as usize)?
.safe_mul(mab.grid_points[2] as usize)?
.safe_mul(mab.grid_points[3] as usize)?
.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
Some(clut)
} else {
return Err(CmsError::InvalidAtoBLut);
};
let a_curves: Option<Box<[Vec<f32>; 4]>> = if mab.a_curves.len() == 4 && mab.clut.is_some() {
let mut arr = Box::<[Vec<f32>; 4]>::default();
for (a_curve, dst) in mab.a_curves.iter().zip(arr.iter_mut()) {
*dst = a_curve.to_clut()?;
}
Some(arr)
} else {
None
};
let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
let mut arr = Box::<[Vec<f32>; 3]>::default();
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if all_curves_linear {
None
} else {
for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
*dst = c_curve.to_clut()?;
}
Some(arr)
}
} else {
return Err(CmsError::InvalidAtoBLut);
};
let matrix = mab.matrix.to_f32();
let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let mut arr = Box::<[Vec<f32>; 3]>::default();
for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
*dst = curve.to_clut()?;
}
Some(arr)
} else {
None
}
} else {
None
};
let bias = mab.bias.cast();
let transform = Multidimensional4x3::<T> {
a_curves,
b_curves,
m_curves,
matrix,
direction,
options,
clut,
pcs,
grid_size,
bias,
_phantom: PhantomData,
bit_depth,
};
Ok(transform)
}
pub(crate) fn multi_dimensional_4x3_to_pcs<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError> {
let transform = make_multidimensional_4x3::<T>(
mab,
options,
pcs,
MultidimensionalDirection::DeviceToPcs,
bit_depth,
)?;
Ok(Box::new(transform))
}

View File

@@ -1,284 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::KatanaFinalStage;
use crate::conversions::katana::md3x3::MultidimensionalDirection;
use crate::conversions::katana::md4x3::{execute_matrix_stage3, execute_simple_curves3};
use crate::conversions::md_lut::{MultidimensionalLut, tetra_3i_to_any_vec};
use crate::safe_math::SafeMul;
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, DataColorSpace, Layout, LutMultidimensionalType, MalformedSize, Matrix3d, Matrix3f,
PointeeSizeExpressible, TransformOptions, Vector3d, Vector3f,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
struct Multidimensional3xN<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
> {
a_curves: Option<Vec<Vec<f32>>>,
m_curves: Option<Box<[Vec<f32>; 3]>>,
b_curves: Option<Box<[Vec<f32>; 3]>>,
clut: Option<Vec<f32>>,
matrix: Matrix3f,
bias: Vector3f,
direction: MultidimensionalDirection,
grid_size: [u8; 16],
output_inks: usize,
_phantom: PhantomData<T>,
dst_layout: Layout,
bit_depth: usize,
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
Multidimensional3xN<T>
where
f32: AsPrimitive<T>,
{
fn to_output_impl(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
let norm_value = if T::FINITE {
((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
assert_eq!(
self.direction,
MultidimensionalDirection::PcsToDevice,
"PCS to device cannot be used on `to pcs` stage"
);
// B-curves is mandatory
if let Some(b_curves) = &self.b_curves.as_ref() {
execute_simple_curves3(src, b_curves);
}
// Matrix stage
if let Some(m_curves) = self.m_curves.as_ref() {
execute_matrix_stage3(self.matrix, self.bias, src);
execute_simple_curves3(src, m_curves);
}
if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
let mut inks = vec![0.; self.output_inks];
if clut.is_empty() {
return Err(CmsError::InvalidAtoBLut);
}
let md_lut = MultidimensionalLut::new(self.grid_size, 3, self.output_inks);
for (src, dst) in src
.chunks_exact(3)
.zip(dst.chunks_exact_mut(self.dst_layout.channels()))
{
tetra_3i_to_any_vec(
&md_lut,
clut,
src[0],
src[1],
src[2],
&mut inks,
self.output_inks,
);
for (ink, curve) in inks.iter_mut().zip(a_curves.iter()) {
*ink = lut_interp_linear_float(*ink, curve);
}
if T::FINITE {
for (dst, ink) in dst.iter_mut().zip(inks.iter()) {
*dst = (*ink * norm_value).round().max(0.).min(norm_value).as_();
}
} else {
for (dst, ink) in dst.iter_mut().zip(inks.iter()) {
*dst = (*ink * norm_value).as_();
}
}
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
Ok(())
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaFinalStage<f32, T> for Multidimensional3xN<T>
where
f32: AsPrimitive<T>,
{
fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
if src.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % self.output_inks != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
self.to_output_impl(src, dst)?;
Ok(())
}
}
fn make_multidimensional_nx3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
dst_layout: Layout,
mab: &LutMultidimensionalType,
_: TransformOptions,
pcs: DataColorSpace,
direction: MultidimensionalDirection,
bit_depth: usize,
) -> Result<Multidimensional3xN<T>, CmsError> {
let real_inks = if pcs == DataColorSpace::Rgb {
3
} else {
dst_layout.channels()
};
if mab.num_output_channels != real_inks as u8 {
return Err(CmsError::UnsupportedProfileConnection);
}
if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
return Err(CmsError::InvalidAtoBLut);
}
let clut: Option<Vec<f32>> =
if mab.a_curves.len() == mab.num_output_channels as usize && mab.clut.is_some() {
let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let mut lut_grid = 1usize;
for grid in mab.grid_points.iter().take(mab.num_input_channels as usize) {
lut_grid = lut_grid.safe_mul(*grid as usize)?;
}
let lut_grid = lut_grid.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
Some(clut)
} else {
return Err(CmsError::InvalidAtoBLut);
};
let a_curves: Option<Vec<Vec<f32>>> =
if mab.a_curves.len() == mab.num_output_channels as usize && mab.clut.is_some() {
let mut arr = Vec::new();
for a_curve in mab.a_curves.iter() {
arr.push(a_curve.to_clut()?);
}
Some(arr)
} else {
None
};
let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
let mut arr = Box::<[Vec<f32>; 3]>::default();
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if all_curves_linear {
None
} else {
for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
*dst = c_curve.to_clut()?;
}
Some(arr)
}
} else {
return Err(CmsError::InvalidAtoBLut);
};
let matrix = mab.matrix.to_f32();
let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let mut arr = Box::<[Vec<f32>; 3]>::default();
for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
*dst = curve.to_clut()?;
}
Some(arr)
} else {
None
}
} else {
None
};
let bias = mab.bias.cast();
let transform = Multidimensional3xN::<T> {
a_curves,
b_curves,
m_curves,
matrix,
direction,
clut,
grid_size: mab.grid_points,
bias,
dst_layout,
output_inks: real_inks,
_phantom: PhantomData,
bit_depth,
};
Ok(transform)
}
pub(crate) fn katana_multi_dimensional_3xn_to_device<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
dst_layout: Layout,
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaFinalStage<f32, T> + Send + Sync>, CmsError>
where
f32: AsPrimitive<T>,
{
if mab.num_input_channels == 0 {
return Err(CmsError::UnsupportedProfileConnection);
}
let transform = make_multidimensional_nx3::<T>(
dst_layout,
mab,
options,
pcs,
MultidimensionalDirection::PcsToDevice,
bit_depth,
)?;
Ok(Box::new(transform))
}

View File

@@ -1,294 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::KatanaInitialStage;
use crate::conversions::katana::md3x3::MultidimensionalDirection;
use crate::conversions::katana::md4x3::{execute_matrix_stage3, execute_simple_curves3};
use crate::conversions::md_lut::{
MultidimensionalLut, NVector, linear_1i_vec3f, linear_2i_vec3f_direct, linear_3i_vec3f_direct,
linear_4i_vec3f, linear_5i_vec3f, linear_6i_vec3f, linear_7i_vec3f, linear_8i_vec3f,
linear_9i_vec3f, linear_10i_vec3f, linear_11i_vec3f, linear_12i_vec3f, linear_13i_vec3f,
linear_14i_vec3f, linear_15i_vec3f,
};
use crate::safe_math::SafeMul;
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, DataColorSpace, Layout, LutMultidimensionalType, MalformedSize, Matrix3d, Matrix3f,
PointeeSizeExpressible, TransformOptions, Vector3d, Vector3f,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
struct MultidimensionalNx3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
> {
a_curves: Option<Vec<Vec<f32>>>,
m_curves: Option<Box<[Vec<f32>; 3]>>,
b_curves: Option<Box<[Vec<f32>; 3]>>,
clut: Option<Vec<f32>>,
matrix: Matrix3f,
bias: Vector3f,
direction: MultidimensionalDirection,
grid_size: [u8; 16],
input_inks: usize,
_phantom: PhantomData<T>,
bit_depth: usize,
}
#[inline(never)]
pub(crate) fn interpolate_out_function(
layout: Layout,
) -> fn(lut: &MultidimensionalLut, arr: &[f32], inputs: &[f32]) -> NVector<f32, 3> {
const OUT: usize = 3;
match layout {
Layout::Rgb => linear_3i_vec3f_direct::<OUT>,
Layout::Rgba => linear_4i_vec3f::<OUT>,
Layout::Gray => linear_1i_vec3f::<OUT>,
Layout::GrayAlpha => linear_2i_vec3f_direct::<OUT>,
Layout::Inks5 => linear_5i_vec3f::<OUT>,
Layout::Inks6 => linear_6i_vec3f::<OUT>,
Layout::Inks7 => linear_7i_vec3f::<OUT>,
Layout::Inks8 => linear_8i_vec3f::<OUT>,
Layout::Inks9 => linear_9i_vec3f::<OUT>,
Layout::Inks10 => linear_10i_vec3f::<OUT>,
Layout::Inks11 => linear_11i_vec3f::<OUT>,
Layout::Inks12 => linear_12i_vec3f::<OUT>,
Layout::Inks13 => linear_13i_vec3f::<OUT>,
Layout::Inks14 => linear_14i_vec3f::<OUT>,
Layout::Inks15 => linear_15i_vec3f::<OUT>,
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
MultidimensionalNx3<T>
{
fn to_pcs_impl(&self, input: &[T], dst: &mut [f32]) -> Result<(), CmsError> {
let norm_value = if T::FINITE {
1.0 / ((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
assert_eq!(
self.direction,
MultidimensionalDirection::DeviceToPcs,
"PCS to device cannot be used on `to pcs` stage"
);
// A -> B
// OR B - A A - curves stage
if let (Some(a_curves), Some(clut)) = (self.a_curves.as_ref(), self.clut.as_ref()) {
let layout = Layout::from_inks(self.input_inks);
let mut inks = vec![0.; self.input_inks];
if clut.is_empty() {
return Err(CmsError::InvalidAtoBLut);
}
let fetcher = interpolate_out_function(layout);
let md_lut = MultidimensionalLut::new(self.grid_size, self.input_inks, 3);
for (src, dst) in input
.chunks_exact(layout.channels())
.zip(dst.chunks_exact_mut(3))
{
for ((ink, src_ink), curve) in inks.iter_mut().zip(src).zip(a_curves.iter()) {
*ink = lut_interp_linear_float(src_ink.as_() * norm_value, curve);
}
let interpolated = fetcher(&md_lut, clut, &inks);
dst[0] = interpolated.v[0];
dst[1] = interpolated.v[1];
dst[2] = interpolated.v[2];
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
// Matrix stage
if let Some(m_curves) = self.m_curves.as_ref() {
execute_simple_curves3(dst, m_curves);
execute_matrix_stage3(self.matrix, self.bias, dst);
}
// B-curves is mandatory
if let Some(b_curves) = &self.b_curves.as_ref() {
execute_simple_curves3(dst, b_curves);
}
Ok(())
}
}
impl<T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync>
KatanaInitialStage<f32, T> for MultidimensionalNx3<T>
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
if input.len() % self.input_inks != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let mut new_dst = vec![0f32; (input.len() / self.input_inks) * 3];
self.to_pcs_impl(input, &mut new_dst)?;
Ok(new_dst)
}
}
fn make_multidimensional_nx3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
mab: &LutMultidimensionalType,
_: TransformOptions,
_: DataColorSpace,
direction: MultidimensionalDirection,
bit_depth: usize,
) -> Result<MultidimensionalNx3<T>, CmsError> {
if mab.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
if mab.b_curves.is_empty() || mab.b_curves.len() != 3 {
return Err(CmsError::InvalidAtoBLut);
}
let clut: Option<Vec<f32>> =
if mab.a_curves.len() == mab.num_input_channels as usize && mab.clut.is_some() {
let clut = mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let mut lut_grid = 1usize;
for grid in mab.grid_points.iter().take(mab.num_input_channels as usize) {
lut_grid = lut_grid.safe_mul(*grid as usize)?;
}
let lut_grid = lut_grid.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
Some(clut)
} else {
return Err(CmsError::InvalidAtoBLut);
};
let a_curves: Option<Vec<Vec<f32>>> =
if mab.a_curves.len() == mab.num_input_channels as usize && mab.clut.is_some() {
let mut arr = Vec::new();
for a_curve in mab.a_curves.iter() {
arr.push(a_curve.to_clut()?);
}
Some(arr)
} else {
None
};
let b_curves: Option<Box<[Vec<f32>; 3]>> = if mab.b_curves.len() == 3 {
let mut arr = Box::<[Vec<f32>; 3]>::default();
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if all_curves_linear {
None
} else {
for (c_curve, dst) in mab.b_curves.iter().zip(arr.iter_mut()) {
*dst = c_curve.to_clut()?;
}
Some(arr)
}
} else {
return Err(CmsError::InvalidAtoBLut);
};
let matrix = mab.matrix.to_f32();
let m_curves: Option<Box<[Vec<f32>; 3]>> = if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let mut arr = Box::<[Vec<f32>; 3]>::default();
for (curve, dst) in mab.m_curves.iter().zip(arr.iter_mut()) {
*dst = curve.to_clut()?;
}
Some(arr)
} else {
None
}
} else {
None
};
let bias = mab.bias.cast();
let transform = MultidimensionalNx3::<T> {
a_curves,
b_curves,
m_curves,
matrix,
direction,
clut,
grid_size: mab.grid_points,
bias,
input_inks: mab.num_input_channels as usize,
_phantom: PhantomData,
bit_depth,
};
Ok(transform)
}
pub(crate) fn katana_multi_dimensional_nx3_to_pcs<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
src_layout: Layout,
mab: &LutMultidimensionalType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError> {
if pcs == DataColorSpace::Rgb {
if mab.num_input_channels != 3 {
return Err(CmsError::InvalidAtoBLut);
}
if src_layout != Layout::Rgba && src_layout != Layout::Rgb {
return Err(CmsError::InvalidInksCountForProfile);
}
} else if mab.num_input_channels != src_layout.channels() as u8 {
return Err(CmsError::InvalidInksCountForProfile);
}
let transform = make_multidimensional_nx3::<T>(
mab,
options,
pcs,
MultidimensionalDirection::DeviceToPcs,
bit_depth,
)?;
Ok(Box::new(transform))
}

View File

@@ -1,393 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::md_nx3::interpolate_out_function;
use crate::conversions::katana::{KatanaFinalStage, KatanaInitialStage};
use crate::conversions::md_lut::{MultidimensionalLut, tetra_3i_to_any_vec};
use crate::profile::LutDataType;
use crate::safe_math::{SafeMul, SafePowi};
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, DataColorSpace, Layout, MalformedSize, PointeeSizeExpressible, TransformOptions,
};
use num_traits::AsPrimitive;
use std::array::from_fn;
use std::marker::PhantomData;
#[derive(Default)]
struct KatanaLutNx3<T> {
linearization: Vec<Vec<f32>>,
clut: Vec<f32>,
grid_size: u8,
input_inks: usize,
output: [Vec<f32>; 3],
_phantom: PhantomData<T>,
bit_depth: usize,
}
struct KatanaLut3xN<T> {
linearization: [Vec<f32>; 3],
clut: Vec<f32>,
grid_size: u8,
output_inks: usize,
output: Vec<Vec<f32>>,
dst_layout: Layout,
target_color_space: DataColorSpace,
_phantom: PhantomData<T>,
bit_depth: usize,
}
impl<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>> KatanaLutNx3<T> {
fn to_pcs_impl(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
if input.len() % self.input_inks != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let norm_value = if T::FINITE {
1.0 / ((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
let grid_sizes: [u8; 16] = from_fn(|i| {
if i < self.input_inks {
self.grid_size
} else {
0
}
});
let md_lut = MultidimensionalLut::new(grid_sizes, self.input_inks, 3);
let layout = Layout::from_inks(self.input_inks);
let mut inks = vec![0.; self.input_inks];
let mut dst = vec![0.; (input.len() / layout.channels()) * 3];
let fetcher = interpolate_out_function(layout);
for (dest, src) in dst
.chunks_exact_mut(3)
.zip(input.chunks_exact(layout.channels()))
{
for ((ink, src_ink), curve) in inks.iter_mut().zip(src).zip(self.linearization.iter()) {
*ink = lut_interp_linear_float(src_ink.as_() * norm_value, curve);
}
let clut = fetcher(&md_lut, &self.clut, &inks);
let pcs_x = lut_interp_linear_float(clut.v[0], &self.output[0]);
let pcs_y = lut_interp_linear_float(clut.v[1], &self.output[1]);
let pcs_z = lut_interp_linear_float(clut.v[2], &self.output[2]);
dest[0] = pcs_x;
dest[1] = pcs_y;
dest[2] = pcs_z;
}
Ok(dst)
}
}
impl<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>> KatanaInitialStage<f32, T>
for KatanaLutNx3<T>
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
if input.len() % self.input_inks != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
self.to_pcs_impl(input)
}
}
impl<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>> KatanaFinalStage<f32, T>
for KatanaLut3xN<T>
where
f32: AsPrimitive<T>,
{
fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
if src.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let grid_sizes: [u8; 16] = from_fn(|i| {
if i < self.output_inks {
self.grid_size
} else {
0
}
});
let md_lut = MultidimensionalLut::new(grid_sizes, 3, self.output_inks);
let scale_value = if T::FINITE {
((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
let mut working = vec![0.; self.output_inks];
for (dest, src) in dst
.chunks_exact_mut(self.dst_layout.channels())
.zip(src.chunks_exact(3))
{
let x = lut_interp_linear_float(src[0], &self.linearization[0]);
let y = lut_interp_linear_float(src[1], &self.linearization[1]);
let z = lut_interp_linear_float(src[2], &self.linearization[2]);
tetra_3i_to_any_vec(&md_lut, &self.clut, x, y, z, &mut working, self.output_inks);
for (ink, curve) in working.iter_mut().zip(self.output.iter()) {
*ink = lut_interp_linear_float(*ink, curve);
}
if T::FINITE {
for (dst, ink) in dest.iter_mut().zip(working.iter()) {
*dst = (*ink * scale_value).round().max(0.).min(scale_value).as_();
}
} else {
for (dst, ink) in dest.iter_mut().zip(working.iter()) {
*dst = (*ink * scale_value).as_();
}
}
}
if self.dst_layout == Layout::Rgba && self.target_color_space == DataColorSpace::Rgb {
for dst in dst.chunks_exact_mut(self.dst_layout.channels()) {
dst[3] = scale_value.as_();
}
}
Ok(())
}
}
fn katana_make_lut_nx3<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>>(
inks: usize,
lut: &LutDataType,
_: TransformOptions,
_: DataColorSpace,
bit_depth: usize,
) -> Result<KatanaLutNx3<T>, CmsError> {
if inks != lut.num_input_channels as usize {
return Err(CmsError::UnsupportedProfileConnection);
}
if lut.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
let clut_length: usize = (lut.num_clut_grid_points as usize)
.safe_powi(lut.num_input_channels as u32)?
.safe_mul(lut.num_output_channels as usize)?;
let clut_table = lut.clut_table.to_clut_f32();
if clut_table.len() != clut_length {
return Err(CmsError::MalformedClut(MalformedSize {
size: clut_table.len(),
expected: clut_length,
}));
}
let linearization_table = lut.input_table.to_clut_f32();
if linearization_table.len() < lut.num_input_table_entries as usize * inks {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: linearization_table.len(),
expected: lut.num_input_table_entries as usize * inks,
}));
}
let linearization = (0..inks)
.map(|x| {
linearization_table[x * lut.num_input_table_entries as usize
..(x + 1) * lut.num_input_table_entries as usize]
.to_vec()
})
.collect::<_>();
let gamma_table = lut.output_table.to_clut_f32();
if gamma_table.len() < lut.num_output_table_entries as usize * 3 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: gamma_table.len(),
expected: lut.num_output_table_entries as usize * 3,
}));
}
let gamma_curve0 = gamma_table[..lut.num_output_table_entries as usize].to_vec();
let gamma_curve1 = gamma_table
[lut.num_output_table_entries as usize..lut.num_output_table_entries as usize * 2]
.to_vec();
let gamma_curve2 = gamma_table
[lut.num_output_table_entries as usize * 2..lut.num_output_table_entries as usize * 3]
.to_vec();
let transform = KatanaLutNx3::<T> {
linearization,
clut: clut_table,
grid_size: lut.num_clut_grid_points,
output: [gamma_curve0, gamma_curve1, gamma_curve2],
input_inks: inks,
_phantom: PhantomData,
bit_depth,
};
Ok(transform)
}
fn katana_make_lut_3xn<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>>(
inks: usize,
dst_layout: Layout,
lut: &LutDataType,
_: TransformOptions,
target_color_space: DataColorSpace,
bit_depth: usize,
) -> Result<KatanaLut3xN<T>, CmsError> {
if lut.num_input_channels as usize != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
if target_color_space == DataColorSpace::Rgb {
if lut.num_output_channels != 3 || lut.num_output_channels != 4 {
return Err(CmsError::InvalidInksCountForProfile);
}
if dst_layout != Layout::Rgb || dst_layout != Layout::Rgba {
return Err(CmsError::InvalidInksCountForProfile);
}
} else if lut.num_output_channels as usize != dst_layout.channels() {
return Err(CmsError::InvalidInksCountForProfile);
}
let clut_length: usize = (lut.num_clut_grid_points as usize)
.safe_powi(lut.num_input_channels as u32)?
.safe_mul(lut.num_output_channels as usize)?;
let clut_table = lut.clut_table.to_clut_f32();
if clut_table.len() != clut_length {
return Err(CmsError::MalformedClut(MalformedSize {
size: clut_table.len(),
expected: clut_length,
}));
}
let linearization_table = lut.input_table.to_clut_f32();
if linearization_table.len() < lut.num_input_table_entries as usize * 3 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: linearization_table.len(),
expected: lut.num_input_table_entries as usize * 3,
}));
}
let linear_curve0 = linearization_table[..lut.num_input_table_entries as usize].to_vec();
let linear_curve1 = linearization_table
[lut.num_input_table_entries as usize..lut.num_input_table_entries as usize * 2]
.to_vec();
let linear_curve2 = linearization_table
[lut.num_input_table_entries as usize * 2..lut.num_input_table_entries as usize * 3]
.to_vec();
let gamma_table = lut.output_table.to_clut_f32();
if gamma_table.len() < lut.num_output_table_entries as usize * inks {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: gamma_table.len(),
expected: lut.num_output_table_entries as usize * inks,
}));
}
let gamma = (0..inks)
.map(|x| {
gamma_table[x * lut.num_output_table_entries as usize
..(x + 1) * lut.num_output_table_entries as usize]
.to_vec()
})
.collect::<_>();
let transform = KatanaLut3xN::<T> {
linearization: [linear_curve0, linear_curve1, linear_curve2],
clut: clut_table,
grid_size: lut.num_clut_grid_points,
output: gamma,
output_inks: inks,
_phantom: PhantomData,
target_color_space,
dst_layout,
bit_depth,
};
Ok(transform)
}
pub(crate) fn katana_input_make_lut_nx3<
T: Copy + PointeeSizeExpressible + AsPrimitive<f32> + Send + Sync,
>(
src_layout: Layout,
inks: usize,
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError> {
if pcs == DataColorSpace::Rgb {
if lut.num_input_channels != 3 {
return Err(CmsError::InvalidAtoBLut);
}
if src_layout != Layout::Rgba && src_layout != Layout::Rgb {
return Err(CmsError::InvalidInksCountForProfile);
}
} else if lut.num_input_channels != src_layout.channels() as u8 {
return Err(CmsError::InvalidInksCountForProfile);
}
let z0 = katana_make_lut_nx3::<T>(inks, lut, options, pcs, bit_depth)?;
Ok(Box::new(z0))
}
pub(crate) fn katana_output_make_lut_3xn<
T: Copy + PointeeSizeExpressible + AsPrimitive<f32> + Send + Sync,
>(
dst_layout: Layout,
lut: &LutDataType,
options: TransformOptions,
target_color_space: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaFinalStage<f32, T> + Send + Sync>, CmsError>
where
f32: AsPrimitive<T>,
{
let real_inks = if target_color_space == DataColorSpace::Rgb {
3
} else {
dst_layout.channels()
};
let z0 = katana_make_lut_3xn::<T>(
real_inks,
dst_layout,
lut,
options,
target_color_space,
bit_depth,
)?;
Ok(Box::new(z0))
}

View File

@@ -1,56 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
mod finalizers;
mod md3x3;
mod md4x3;
mod md_3xn;
mod md_nx3;
mod md_pipeline;
mod pcs_stages;
mod rgb_xyz;
mod stages;
mod xyz_lab;
mod xyz_rgb;
pub(crate) use finalizers::{CopyAlphaStage, InjectAlphaStage};
pub(crate) use md_3xn::katana_multi_dimensional_3xn_to_device;
pub(crate) use md_nx3::katana_multi_dimensional_nx3_to_pcs;
pub(crate) use md_pipeline::{katana_input_make_lut_nx3, katana_output_make_lut_3xn};
pub(crate) use md3x3::{multi_dimensional_3x3_to_device, multi_dimensional_3x3_to_pcs};
pub(crate) use md4x3::multi_dimensional_4x3_to_pcs;
pub(crate) use pcs_stages::{
KatanaDefaultIntermediate, katana_pcs_lab_v2_to_v4, katana_pcs_lab_v4_to_v2,
};
pub(crate) use rgb_xyz::katana_create_rgb_lin_lut;
pub(crate) use stages::{
Katana, KatanaFinalStage, KatanaInitialStage, KatanaIntermediateStage,
KatanaPostFinalizationStage,
};
pub(crate) use xyz_lab::{KatanaStageLabToXyz, KatanaStageXyzToLab};
pub(crate) use xyz_rgb::katana_prepare_inverse_lut_rgb_xyz;

View File

@@ -1,100 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::KatanaIntermediateStage;
use crate::conversions::katana::stages::BlackholeIntermediateStage;
use crate::mlaf::mlaf;
use crate::{CmsError, ColorProfile, DataColorSpace, Matrix3f, ProfileVersion};
use std::marker::PhantomData;
pub(crate) struct KatanaMatrixStage {
pub(crate) matrices: Vec<Matrix3f>,
}
impl KatanaMatrixStage {
pub(crate) fn new(matrix: Matrix3f) -> Self {
Self {
matrices: vec![matrix],
}
}
}
pub(crate) type KatanaDefaultIntermediate = dyn KatanaIntermediateStage<f32> + Send + Sync;
impl KatanaIntermediateStage<f32> for KatanaMatrixStage {
fn stage(&self, input: &mut Vec<f32>) -> Result<Vec<f32>, CmsError> {
if input.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
for m in self.matrices.iter() {
for dst in input.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
}
}
Ok(std::mem::take(input))
}
}
pub(crate) fn katana_pcs_lab_v4_to_v2(profile: &ColorProfile) -> Box<KatanaDefaultIntermediate> {
if profile.pcs == DataColorSpace::Lab && profile.version_internal <= ProfileVersion::V4_0 {
let v_mat = vec![Matrix3f {
v: [
[65280.0 / 65535.0, 0., 0.],
[0., 65280.0 / 65535.0, 0.],
[0., 0., 65280.0 / 65535.0],
],
}];
return Box::new(KatanaMatrixStage { matrices: v_mat });
}
Box::new(BlackholeIntermediateStage {
_phantom: PhantomData,
})
}
pub(crate) fn katana_pcs_lab_v2_to_v4(profile: &ColorProfile) -> Box<KatanaDefaultIntermediate> {
if profile.pcs == DataColorSpace::Lab && profile.version_internal <= ProfileVersion::V4_0 {
let v_mat = vec![Matrix3f {
v: [
[65535.0 / 65280.0, 0., 0.],
[0., 65535.0 / 65280.0, 0.],
[0., 0., 65535.0 / 65280.0],
],
}];
return Box::new(KatanaMatrixStage { matrices: v_mat });
}
Box::new(BlackholeIntermediateStage {
_phantom: PhantomData,
})
}

View File

@@ -1,162 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::pcs_stages::KatanaMatrixStage;
use crate::conversions::katana::{KatanaInitialStage, KatanaIntermediateStage};
use crate::err::try_vec;
use crate::{CmsError, ColorProfile, Layout, Matrix3f, PointeeSizeExpressible, TransformOptions};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
struct KatanaRgbLinearizationStage<T: Clone, const LAYOUT: u8, const LINEAR_CAP: usize> {
r_lin: Box<[f32; LINEAR_CAP]>,
g_lin: Box<[f32; LINEAR_CAP]>,
b_lin: Box<[f32; LINEAR_CAP]>,
linear_cap: usize,
bit_depth: usize,
_phantom: PhantomData<T>,
}
impl<
T: Clone + AsPrimitive<f32> + PointeeSizeExpressible,
const LAYOUT: u8,
const LINEAR_CAP: usize,
> KatanaInitialStage<f32, T> for KatanaRgbLinearizationStage<T, LAYOUT, LINEAR_CAP>
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
let src_layout = Layout::from(LAYOUT);
if input.len() % src_layout.channels() != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let mut dst = try_vec![0.; input.len() / src_layout.channels() * 3];
let scale = if T::FINITE {
(self.linear_cap as f32 - 1.) / ((1 << self.bit_depth) - 1) as f32
} else {
(T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32
};
let cap_value = if T::FINITE {
((1 << self.bit_depth) - 1) as f32
} else {
(T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32
};
for (src, dst) in input
.chunks_exact(src_layout.channels())
.zip(dst.chunks_exact_mut(3))
{
let j_r = src[0].as_() * scale;
let j_g = src[1].as_() * scale;
let j_b = src[2].as_() * scale;
dst[0] = self.r_lin[(j_r.round().min(cap_value).max(0.) as u16) as usize];
dst[1] = self.g_lin[(j_g.round().min(cap_value).max(0.) as u16) as usize];
dst[2] = self.b_lin[(j_b.round().min(cap_value).max(0.) as u16) as usize];
}
Ok(dst)
}
}
pub(crate) struct KatanaRgbLinearizationState<T> {
pub(crate) stages: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>>,
pub(crate) initial_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync>,
}
pub(crate) fn katana_create_rgb_lin_lut<
T: Copy + Default + AsPrimitive<f32> + Send + Sync + AsPrimitive<usize> + PointeeSizeExpressible,
const BIT_DEPTH: usize,
const LINEAR_CAP: usize,
>(
layout: Layout,
source: &ColorProfile,
opts: TransformOptions,
) -> Result<KatanaRgbLinearizationState<T>, CmsError>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
let lin_r =
source.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
let lin_g =
source.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
let lin_b =
source.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
let lin_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync> = match layout {
Layout::Rgb => {
Box::new(
KatanaRgbLinearizationStage::<T, { Layout::Rgb as u8 }, LINEAR_CAP> {
r_lin: lin_r,
g_lin: lin_g,
b_lin: lin_b,
bit_depth: BIT_DEPTH,
linear_cap: LINEAR_CAP,
_phantom: PhantomData,
},
)
}
Layout::Rgba => {
Box::new(
KatanaRgbLinearizationStage::<T, { Layout::Rgba as u8 }, LINEAR_CAP> {
r_lin: lin_r,
g_lin: lin_g,
b_lin: lin_b,
bit_depth: BIT_DEPTH,
linear_cap: LINEAR_CAP,
_phantom: PhantomData,
},
)
}
Layout::Gray => unimplemented!("Gray should not be called on Rgb/Rgba execution path"),
Layout::GrayAlpha => {
unimplemented!("GrayAlpha should not be called on Rgb/Rgba execution path")
}
_ => unreachable!(),
};
let xyz_to_rgb = source.rgb_to_xyz_matrix();
let matrices: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>> =
vec![Box::new(KatanaMatrixStage {
matrices: vec![
xyz_to_rgb.to_f32(),
Matrix3f {
v: [
[32768.0 / 65535.0, 0.0, 0.0],
[0.0, 32768.0 / 65535.0, 0.0],
[0.0, 0.0, 32768.0 / 65535.0],
],
},
],
})];
Ok(KatanaRgbLinearizationState {
stages: matrices,
initial_stage: lin_stage,
})
}

View File

@@ -1,85 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::{CmsError, TransformExecutor};
use std::marker::PhantomData;
/// W storage working data type
/// I input/output data type
pub(crate) trait KatanaInitialStage<W, I> {
fn to_pcs(&self, input: &[I]) -> Result<Vec<W>, CmsError>;
}
/// W storage working data type
/// I input/output data type
pub(crate) trait KatanaFinalStage<W, I> {
fn to_output(&self, src: &mut [W], dst: &mut [I]) -> Result<(), CmsError>;
}
/// W storage working data type
pub(crate) trait KatanaIntermediateStage<W> {
fn stage(&self, input: &mut Vec<W>) -> Result<Vec<W>, CmsError>;
}
pub(crate) struct BlackholeIntermediateStage<W> {
pub(crate) _phantom: PhantomData<W>,
}
impl<W> KatanaIntermediateStage<W> for BlackholeIntermediateStage<W> {
fn stage(&self, input: &mut Vec<W>) -> Result<Vec<W>, CmsError> {
Ok(std::mem::take(input))
}
}
/// I input/output data type
pub(crate) trait KatanaPostFinalizationStage<I> {
fn finalize(&self, src: &[I], dst: &mut [I]) -> Result<(), CmsError>;
}
/// W storage working data type
/// I input/output data type
pub(crate) struct Katana<W, I> {
pub(crate) initial_stage: Box<dyn KatanaInitialStage<W, I> + Send + Sync>,
pub(crate) final_stage: Box<dyn KatanaFinalStage<W, I> + Sync + Send>,
pub(crate) stages: Vec<Box<dyn KatanaIntermediateStage<W> + Send + Sync>>,
pub(crate) post_finalization: Vec<Box<dyn KatanaPostFinalizationStage<I> + Send + Sync>>,
}
impl<W, I: Copy + Default> TransformExecutor<I> for Katana<W, I> {
fn transform(&self, src: &[I], dst: &mut [I]) -> Result<(), CmsError> {
let mut working_vec = self.initial_stage.to_pcs(src)?;
for stage in self.stages.iter() {
working_vec = stage.stage(&mut working_vec)?;
}
self.final_stage.to_output(&mut working_vec, dst)?;
for finalization in self.post_finalization.iter() {
finalization.finalize(src, dst)?;
}
Ok(())
}
}

View File

@@ -1,62 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::KatanaIntermediateStage;
use crate::{CmsError, Lab, Xyz};
#[derive(Default)]
pub(crate) struct KatanaStageLabToXyz {}
impl KatanaIntermediateStage<f32> for KatanaStageLabToXyz {
fn stage(&self, input: &mut Vec<f32>) -> Result<Vec<f32>, CmsError> {
for dst in input.chunks_exact_mut(3) {
let lab = Lab::new(dst[0], dst[1], dst[2]);
let xyz = lab.to_pcs_xyz();
dst[0] = xyz.x;
dst[1] = xyz.y;
dst[2] = xyz.z;
}
Ok(std::mem::take(input))
}
}
#[derive(Default)]
pub(crate) struct KatanaStageXyzToLab {}
impl KatanaIntermediateStage<f32> for KatanaStageXyzToLab {
fn stage(&self, input: &mut Vec<f32>) -> Result<Vec<f32>, CmsError> {
for dst in input.chunks_exact_mut(3) {
let xyz = Xyz::new(dst[0], dst[1], dst[2]);
let lab = Lab::from_pcs_xyz(xyz);
dst[0] = lab.l;
dst[1] = lab.a;
dst[2] = lab.b;
}
Ok(std::mem::take(input))
}
}

View File

@@ -1,223 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::pcs_stages::KatanaMatrixStage;
use crate::conversions::katana::{
KatanaDefaultIntermediate, KatanaFinalStage, KatanaIntermediateStage,
};
use crate::mlaf::mlaf;
use crate::{
CmsError, ColorProfile, GammaLutInterpolate, Layout, Matrix3f, PointeeSizeExpressible,
RenderingIntent, Rgb, TransformOptions, filmlike_clip,
};
use num_traits::AsPrimitive;
pub(crate) struct KatanaXyzToRgbStage<T: Clone, const LAYOUT: u8> {
pub(crate) r_gamma: Box<[T; 65536]>,
pub(crate) g_gamma: Box<[T; 65536]>,
pub(crate) b_gamma: Box<[T; 65536]>,
pub(crate) intent: RenderingIntent,
pub(crate) bit_depth: usize,
pub(crate) gamma_lut: usize,
}
impl<T: Clone + AsPrimitive<f32> + PointeeSizeExpressible, const LAYOUT: u8>
KatanaFinalStage<f32, T> for KatanaXyzToRgbStage<T, LAYOUT>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
let dst_cn = Layout::from(LAYOUT);
let dst_channels = dst_cn.channels();
if src.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let src_chunks = src.len() / 3;
let dst_chunks = dst.len() / dst_channels;
if src_chunks != dst_chunks {
return Err(CmsError::LaneSizeMismatch);
}
let max_colors: T = (if T::FINITE {
((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
})
.as_();
let lut_cap = (self.gamma_lut - 1) as f32;
if self.intent != RenderingIntent::AbsoluteColorimetric {
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(dst_channels)) {
let mut rgb = Rgb::new(src[0], src[1], src[2]);
if rgb.is_out_of_gamut() {
rgb = filmlike_clip(rgb);
}
let r = mlaf(0.5, rgb.r, lut_cap).min(lut_cap).max(0.) as u16;
let g = mlaf(0.5, rgb.g, lut_cap).min(lut_cap).max(0.) as u16;
let b = mlaf(0.5, rgb.b, lut_cap).min(lut_cap).max(0.) as u16;
dst[0] = self.r_gamma[r as usize];
dst[1] = self.g_gamma[g as usize];
dst[2] = self.b_gamma[b as usize];
if dst_cn == Layout::Rgba {
dst[3] = max_colors;
}
}
} else {
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(dst_channels)) {
let rgb = Rgb::new(src[0], src[1], src[2]);
let r = mlaf(0.5, rgb.r, lut_cap).min(lut_cap).max(0.) as u16;
let g = mlaf(0.5, rgb.g, lut_cap).min(lut_cap).max(0.) as u16;
let b = mlaf(0.5, rgb.b, lut_cap).min(lut_cap).max(0.) as u16;
dst[0] = self.r_gamma[r as usize];
dst[1] = self.g_gamma[g as usize];
dst[2] = self.b_gamma[b as usize];
if dst_cn == Layout::Rgba {
dst[3] = max_colors;
}
}
}
Ok(())
}
}
pub(crate) struct KatanaXyzRgbState<T> {
pub(crate) stages: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>>,
pub(crate) final_stage: Box<dyn KatanaFinalStage<f32, T> + Send + Sync>,
}
pub(crate) fn katana_prepare_inverse_lut_rgb_xyz<
T: Copy
+ Default
+ AsPrimitive<f32>
+ Send
+ Sync
+ AsPrimitive<usize>
+ PointeeSizeExpressible
+ GammaLutInterpolate,
const BIT_DEPTH: usize,
const GAMMA_LUT: usize,
>(
dest: &ColorProfile,
dest_layout: Layout,
options: TransformOptions,
) -> Result<KatanaXyzRgbState<T>, CmsError>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
{
// if !T::FINITE {
// if let Some(extended_gamma) = dest.try_extended_gamma_evaluator() {
// let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
//
// let mut matrices = vec![Matrix3f {
// v: [
// [65535.0 / 32768.0, 0.0, 0.0],
// [0.0, 65535.0 / 32768.0, 0.0],
// [0.0, 0.0, 65535.0 / 32768.0],
// ],
// }];
//
// matrices.push(xyz_to_rgb.to_f32());
// let xyz_to_rgb_stage = XyzToRgbStageExtended::<T> {
// gamma_evaluator: extended_gamma,
// matrices,
// phantom_data: PhantomData,
// };
// xyz_to_rgb_stage.transform(lut)?;
// return Ok(());
// }
// }
let gamma_map_r = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
&dest.red_trc,
options.allow_use_cicp_transfer,
)?;
let gamma_map_g = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
&dest.green_trc,
options.allow_use_cicp_transfer,
)?;
let gamma_map_b = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
&dest.blue_trc,
options.allow_use_cicp_transfer,
)?;
let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
let mut matrices: Vec<Box<KatanaDefaultIntermediate>> =
vec![Box::new(KatanaMatrixStage::new(Matrix3f {
v: [
[65535.0 / 32768.0, 0.0, 0.0],
[0.0, 65535.0 / 32768.0, 0.0],
[0.0, 0.0, 65535.0 / 32768.0],
],
}))];
matrices.push(Box::new(KatanaMatrixStage::new(xyz_to_rgb.to_f32())));
match dest_layout {
Layout::Rgb => {
let xyz_to_rgb_stage = KatanaXyzToRgbStage::<T, { Layout::Rgb as u8 }> {
r_gamma: gamma_map_r,
g_gamma: gamma_map_g,
b_gamma: gamma_map_b,
intent: options.rendering_intent,
bit_depth: BIT_DEPTH,
gamma_lut: GAMMA_LUT,
};
Ok(KatanaXyzRgbState {
stages: matrices,
final_stage: Box::new(xyz_to_rgb_stage),
})
}
Layout::Rgba => {
let xyz_to_rgb_stage = KatanaXyzToRgbStage::<T, { Layout::Rgba as u8 }> {
r_gamma: gamma_map_r,
g_gamma: gamma_map_g,
b_gamma: gamma_map_b,
intent: options.rendering_intent,
bit_depth: BIT_DEPTH,
gamma_lut: GAMMA_LUT,
};
Ok(KatanaXyzRgbState {
stages: matrices,
final_stage: Box::new(xyz_to_rgb_stage),
})
}
Layout::Gray => unreachable!("Gray layout must not be called on Rgb/Rgba path"),
Layout::GrayAlpha => unreachable!("Gray layout must not be called on Rgb/Rgba path"),
_ => unreachable!(
"layout {:?} should not be called on xyz->rgb path",
dest_layout
),
}
}

View File

@@ -1,428 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::{KatanaFinalStage, KatanaInitialStage};
use crate::err::{MalformedSize, try_vec};
use crate::profile::LutDataType;
use crate::safe_math::{SafeMul, SafePowi};
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, Cube, DataColorSpace, InterpolationMethod, PointeeSizeExpressible, Stage,
TransformOptions, Vector3f,
};
use num_traits::AsPrimitive;
#[derive(Default)]
struct Lut3x3 {
input: [Vec<f32>; 3],
clut: Vec<f32>,
grid_size: u8,
gamma: [Vec<f32>; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
}
#[derive(Default)]
struct KatanaLut3x3<T: Copy + Default> {
input: [Vec<f32>; 3],
clut: Vec<f32>,
grid_size: u8,
gamma: [Vec<f32>; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
_phantom: std::marker::PhantomData<T>,
bit_depth: usize,
}
fn make_lut_3x3(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Lut3x3, CmsError> {
let clut_length: usize = (lut.num_clut_grid_points as usize)
.safe_powi(lut.num_input_channels as u32)?
.safe_mul(lut.num_output_channels as usize)?;
let lin_table = lut.input_table.to_clut_f32();
if lin_table.len() < lut.num_input_table_entries as usize * 3 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: lin_table.len(),
expected: lut.num_input_table_entries as usize * 3,
}));
}
let lin_curve0 = lin_table[..lut.num_input_table_entries as usize].to_vec();
let lin_curve1 = lin_table
[lut.num_input_table_entries as usize..lut.num_input_table_entries as usize * 2]
.to_vec();
let lin_curve2 = lin_table
[lut.num_input_table_entries as usize * 2..lut.num_input_table_entries as usize * 3]
.to_vec();
let clut_table = lut.clut_table.to_clut_f32();
if clut_table.len() != clut_length {
return Err(CmsError::MalformedClut(MalformedSize {
size: clut_table.len(),
expected: clut_length,
}));
}
let gamma_curves = lut.output_table.to_clut_f32();
if gamma_curves.len() < lut.num_output_table_entries as usize * 3 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: gamma_curves.len(),
expected: lut.num_output_table_entries as usize * 3,
}));
}
let gamma_curve0 = gamma_curves[..lut.num_output_table_entries as usize].to_vec();
let gamma_curve1 = gamma_curves
[lut.num_output_table_entries as usize..lut.num_output_table_entries as usize * 2]
.to_vec();
let gamma_curve2 = gamma_curves
[lut.num_output_table_entries as usize * 2..lut.num_output_table_entries as usize * 3]
.to_vec();
let transform = Lut3x3 {
input: [lin_curve0, lin_curve1, lin_curve2],
gamma: [gamma_curve0, gamma_curve1, gamma_curve2],
interpolation_method: options.interpolation_method,
clut: clut_table,
grid_size: lut.num_clut_grid_points,
pcs,
};
Ok(transform)
}
fn stage_lut_3x3(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Box<dyn Stage>, CmsError> {
let lut = make_lut_3x3(lut, options, pcs)?;
let transform = Lut3x3 {
input: lut.input,
gamma: lut.gamma,
interpolation_method: lut.interpolation_method,
clut: lut.clut,
grid_size: lut.grid_size,
pcs: lut.pcs,
};
Ok(Box::new(transform))
}
pub(crate) fn katana_input_stage_lut_3x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError>
where
f32: AsPrimitive<T>,
{
let lut = make_lut_3x3(lut, options, pcs)?;
let transform = KatanaLut3x3::<T> {
input: lut.input,
gamma: lut.gamma,
interpolation_method: lut.interpolation_method,
clut: lut.clut,
grid_size: lut.grid_size,
pcs: lut.pcs,
_phantom: std::marker::PhantomData,
bit_depth,
};
Ok(Box::new(transform))
}
pub(crate) fn katana_output_stage_lut_3x3<
T: Copy + Default + AsPrimitive<f32> + PointeeSizeExpressible + Send + Sync,
>(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaFinalStage<f32, T> + Send + Sync>, CmsError>
where
f32: AsPrimitive<T>,
{
let lut = make_lut_3x3(lut, options, pcs)?;
let transform = KatanaLut3x3::<T> {
input: lut.input,
gamma: lut.gamma,
interpolation_method: lut.interpolation_method,
clut: lut.clut,
grid_size: lut.grid_size,
pcs: lut.pcs,
_phantom: std::marker::PhantomData,
bit_depth,
};
Ok(Box::new(transform))
}
impl Lut3x3 {
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
src: &[f32],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let linearization_0 = &self.input[0];
let linearization_1 = &self.input[1];
let linearization_2 = &self.input[2];
for (dest, src) in dst.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
debug_assert!(self.grid_size as i32 >= 1);
let linear_x = lut_interp_linear_float(src[0], linearization_0);
let linear_y = lut_interp_linear_float(src[1], linearization_1);
let linear_z = lut_interp_linear_float(src[2], linearization_2);
let clut = fetch(linear_x, linear_y, linear_z);
let pcs_x = lut_interp_linear_float(clut.v[0], &self.gamma[0]);
let pcs_y = lut_interp_linear_float(clut.v[1], &self.gamma[1]);
let pcs_z = lut_interp_linear_float(clut.v[2], &self.gamma[2]);
dest[0] = pcs_x;
dest[1] = pcs_y;
dest[2] = pcs_z;
}
Ok(())
}
}
impl Stage for Lut3x3 {
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
let l_tbl = Cube::new(&self.clut, self.grid_size as usize);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(src, dst, |x, y, z| l_tbl.trilinear_vec3(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(src, dst, |x, y, z| l_tbl.tetra_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(src, dst, |x, y, z| l_tbl.pyramid_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(src, dst, |x, y, z| l_tbl.prism_vec3(x, y, z))?;
}
InterpolationMethod::Linear => {
self.transform_impl(src, dst, |x, y, z| l_tbl.trilinear_vec3(x, y, z))?;
}
}
Ok(())
}
}
impl<T: Copy + Default + PointeeSizeExpressible + AsPrimitive<f32>> KatanaLut3x3<T>
where
f32: AsPrimitive<T>,
{
fn to_pcs_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
input: &[T],
fetch: Fetch,
) -> Result<Vec<f32>, CmsError> {
if input.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let normalizing_value = if T::FINITE {
1.0 / ((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
let mut dst = try_vec![0.; input.len()];
let linearization_0 = &self.input[0];
let linearization_1 = &self.input[1];
let linearization_2 = &self.input[2];
for (dest, src) in dst.chunks_exact_mut(3).zip(input.chunks_exact(3)) {
let linear_x =
lut_interp_linear_float(src[0].as_() * normalizing_value, linearization_0);
let linear_y =
lut_interp_linear_float(src[1].as_() * normalizing_value, linearization_1);
let linear_z =
lut_interp_linear_float(src[2].as_() * normalizing_value, linearization_2);
let clut = fetch(linear_x, linear_y, linear_z);
let pcs_x = lut_interp_linear_float(clut.v[0], &self.gamma[0]);
let pcs_y = lut_interp_linear_float(clut.v[1], &self.gamma[1]);
let pcs_z = lut_interp_linear_float(clut.v[2], &self.gamma[2]);
dest[0] = pcs_x;
dest[1] = pcs_y;
dest[2] = pcs_z;
}
Ok(dst)
}
fn to_output<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
src: &[f32],
dst: &mut [T],
fetch: Fetch,
) -> Result<(), CmsError> {
if src.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() != src.len() {
return Err(CmsError::LaneSizeMismatch);
}
let norm_value = if T::FINITE {
((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
let linearization_0 = &self.input[0];
let linearization_1 = &self.input[1];
let linearization_2 = &self.input[2];
for (dest, src) in dst.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
let linear_x = lut_interp_linear_float(src[0], linearization_0);
let linear_y = lut_interp_linear_float(src[1], linearization_1);
let linear_z = lut_interp_linear_float(src[2], linearization_2);
let clut = fetch(linear_x, linear_y, linear_z);
let pcs_x = lut_interp_linear_float(clut.v[0], &self.gamma[0]);
let pcs_y = lut_interp_linear_float(clut.v[1], &self.gamma[1]);
let pcs_z = lut_interp_linear_float(clut.v[2], &self.gamma[2]);
if T::FINITE {
dest[0] = (pcs_x * norm_value).round().max(0.0).min(norm_value).as_();
dest[1] = (pcs_y * norm_value).round().max(0.0).min(norm_value).as_();
dest[2] = (pcs_z * norm_value).round().max(0.0).min(norm_value).as_();
} else {
dest[0] = pcs_x.as_();
dest[1] = pcs_y.as_();
dest[2] = pcs_z.as_();
}
}
Ok(())
}
}
impl<T: Copy + Default + PointeeSizeExpressible + AsPrimitive<f32>> KatanaInitialStage<f32, T>
for KatanaLut3x3<T>
where
f32: AsPrimitive<T>,
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
let l_tbl = Cube::new(&self.clut, self.grid_size as usize);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.to_pcs_impl(input, |x, y, z| l_tbl.trilinear_vec3(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.to_pcs_impl(input, |x, y, z| l_tbl.tetra_vec3(x, y, z))
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.to_pcs_impl(input, |x, y, z| l_tbl.pyramid_vec3(x, y, z))
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.to_pcs_impl(input, |x, y, z| l_tbl.prism_vec3(x, y, z))
}
InterpolationMethod::Linear => {
self.to_pcs_impl(input, |x, y, z| l_tbl.trilinear_vec3(x, y, z))
}
}
}
}
impl<T: Copy + Default + PointeeSizeExpressible + AsPrimitive<f32>> KatanaFinalStage<f32, T>
for KatanaLut3x3<T>
where
f32: AsPrimitive<T>,
{
fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
let l_tbl = Cube::new(&self.clut, self.grid_size as usize);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.to_output(src, dst, |x, y, z| l_tbl.trilinear_vec3(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.to_output(src, dst, |x, y, z| l_tbl.tetra_vec3(x, y, z))
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.to_output(src, dst, |x, y, z| l_tbl.pyramid_vec3(x, y, z))
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.to_output(src, dst, |x, y, z| l_tbl.prism_vec3(x, y, z))
}
InterpolationMethod::Linear => {
self.to_output(src, dst, |x, y, z| l_tbl.trilinear_vec3(x, y, z))
}
}
}
}
pub(crate) fn create_lut3x3(
lut: &LutDataType,
src: &[f32],
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Vec<f32>, CmsError> {
if lut.num_input_channels != 3 || lut.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
let mut dest = try_vec![0.; src.len()];
let lut_stage = stage_lut_3x3(lut, options, pcs)?;
lut_stage.transform(src, &mut dest)?;
Ok(dest)
}

View File

@@ -1,249 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::err::try_vec;
use crate::profile::LutDataType;
use crate::safe_math::{SafeMul, SafePowi};
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, Cube, DataColorSpace, InterpolationMethod, MalformedSize, Stage, TransformOptions,
Vector4f,
};
use num_traits::AsPrimitive;
#[derive(Default)]
struct Lut3x4 {
input: [Vec<f32>; 3],
clut: Vec<f32>,
grid_size: u8,
gamma: [Vec<f32>; 4],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
}
fn make_lut_3x4(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Lut3x4, CmsError> {
let clut_length: usize = (lut.num_clut_grid_points as usize)
.safe_powi(lut.num_input_channels as u32)?
.safe_mul(lut.num_output_channels as usize)?;
let clut_table = lut.clut_table.to_clut_f32();
if clut_table.len() != clut_length {
return Err(CmsError::MalformedClut(MalformedSize {
size: clut_table.len(),
expected: clut_length,
}));
}
let linearization_table = lut.input_table.to_clut_f32();
if linearization_table.len() < lut.num_input_table_entries as usize * 3 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: linearization_table.len(),
expected: lut.num_input_table_entries as usize * 3,
}));
}
let linear_curve0 = linearization_table[..lut.num_input_table_entries as usize].to_vec();
let linear_curve1 = linearization_table
[lut.num_input_table_entries as usize..lut.num_input_table_entries as usize * 2]
.to_vec();
let linear_curve2 = linearization_table
[lut.num_input_table_entries as usize * 2..lut.num_input_table_entries as usize * 3]
.to_vec();
let gamma_table = lut.output_table.to_clut_f32();
if gamma_table.len() < lut.num_output_table_entries as usize * 4 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: gamma_table.len(),
expected: lut.num_output_table_entries as usize * 4,
}));
}
let gamma_curve0 = gamma_table[..lut.num_output_table_entries as usize].to_vec();
let gamma_curve1 = gamma_table
[lut.num_output_table_entries as usize..lut.num_output_table_entries as usize * 2]
.to_vec();
let gamma_curve2 = gamma_table
[lut.num_output_table_entries as usize * 2..lut.num_output_table_entries as usize * 3]
.to_vec();
let gamma_curve3 = gamma_table
[lut.num_output_table_entries as usize * 3..lut.num_output_table_entries as usize * 4]
.to_vec();
let transform = Lut3x4 {
input: [linear_curve0, linear_curve1, linear_curve2],
interpolation_method: options.interpolation_method,
clut: clut_table,
grid_size: lut.num_clut_grid_points,
pcs,
gamma: [gamma_curve0, gamma_curve1, gamma_curve2, gamma_curve3],
};
Ok(transform)
}
fn stage_lut_3x4(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Box<dyn Stage>, CmsError> {
let lut = make_lut_3x4(lut, options, pcs)?;
let transform = Lut3x4 {
input: lut.input,
interpolation_method: lut.interpolation_method,
clut: lut.clut,
grid_size: lut.grid_size,
pcs: lut.pcs,
gamma: lut.gamma,
};
Ok(Box::new(transform))
}
impl Lut3x4 {
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector4f>(
&self,
src: &[f32],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let linearization_0 = &self.input[0];
let linearization_1 = &self.input[1];
let linearization_2 = &self.input[2];
for (dest, src) in dst.chunks_exact_mut(4).zip(src.chunks_exact(3)) {
debug_assert!(self.grid_size as i32 >= 1);
let linear_x = lut_interp_linear_float(src[0], linearization_0);
let linear_y = lut_interp_linear_float(src[1], linearization_1);
let linear_z = lut_interp_linear_float(src[2], linearization_2);
let clut = fetch(linear_x, linear_y, linear_z);
let pcs_x = lut_interp_linear_float(clut.v[0], &self.gamma[0]);
let pcs_y = lut_interp_linear_float(clut.v[1], &self.gamma[1]);
let pcs_z = lut_interp_linear_float(clut.v[2], &self.gamma[2]);
let pcs_w = lut_interp_linear_float(clut.v[3], &self.gamma[3]);
dest[0] = pcs_x;
dest[1] = pcs_y;
dest[2] = pcs_z;
dest[3] = pcs_w;
}
Ok(())
}
}
impl Stage for Lut3x4 {
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
let l_tbl = Cube::new(&self.clut, self.grid_size as usize);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(src, dst, |x, y, z| l_tbl.trilinear_vec4(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(src, dst, |x, y, z| l_tbl.tetra_vec4(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(src, dst, |x, y, z| l_tbl.pyramid_vec4(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(src, dst, |x, y, z| l_tbl.prism_vec4(x, y, z))?;
}
InterpolationMethod::Linear => {
self.transform_impl(src, dst, |x, y, z| l_tbl.trilinear_vec4(x, y, z))?;
}
}
Ok(())
}
}
pub(crate) fn create_lut3_samples<T: Copy + 'static, const SAMPLES: usize>() -> Vec<T>
where
u32: AsPrimitive<T>,
{
let lut_size: u32 = (3 * SAMPLES * SAMPLES * SAMPLES) as u32;
assert!(SAMPLES >= 1);
let mut src = Vec::with_capacity(lut_size as usize);
for x in 0..SAMPLES as u32 {
for y in 0..SAMPLES as u32 {
for z in 0..SAMPLES as u32 {
src.push(x.as_());
src.push(y.as_());
src.push(z.as_());
}
}
}
src
}
pub(crate) fn create_lut3_samples_norm<const SAMPLES: usize>() -> Vec<f32> {
let lut_size: u32 = (3 * SAMPLES * SAMPLES * SAMPLES) as u32;
assert!(SAMPLES >= 1);
let scale = 1. / (SAMPLES as f32 - 1.0);
let mut src = Vec::with_capacity(lut_size as usize);
for x in 0..SAMPLES as u32 {
for y in 0..SAMPLES as u32 {
for z in 0..SAMPLES as u32 {
src.push(x as f32 * scale);
src.push(y as f32 * scale);
src.push(z as f32 * scale);
}
}
}
src
}
pub(crate) fn create_lut3x4(
lut: &LutDataType,
src: &[f32],
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Vec<f32>, CmsError> {
if lut.num_input_channels != 3 || lut.num_output_channels != 4 {
return Err(CmsError::UnsupportedProfileConnection);
}
let mut dest = try_vec![0.; (src.len() / 3) * 4];
let lut_stage = stage_lut_3x4(lut, options, pcs)?;
lut_stage.transform(src, &mut dest)?;
Ok(dest)
}

View File

@@ -1,360 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::katana::KatanaInitialStage;
use crate::err::try_vec;
use crate::profile::LutDataType;
use crate::safe_math::{SafeMul, SafePowi};
use crate::trc::lut_interp_linear_float;
use crate::{
CmsError, DataColorSpace, Hypercube, InterpolationMethod, MalformedSize,
PointeeSizeExpressible, Stage, TransformOptions, Vector3f,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
#[allow(unused)]
#[derive(Default)]
struct Lut4x3 {
linearization: [Vec<f32>; 4],
clut: Vec<f32>,
grid_size: u8,
output: [Vec<f32>; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
}
#[allow(unused)]
#[derive(Default)]
struct KatanaLut4x3<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>> {
linearization: [Vec<f32>; 4],
clut: Vec<f32>,
grid_size: u8,
output: [Vec<f32>; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
_phantom: PhantomData<T>,
bit_depth: usize,
}
#[allow(unused)]
impl Lut4x3 {
fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
&self,
src: &[f32],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let linearization_0 = &self.linearization[0];
let linearization_1 = &self.linearization[1];
let linearization_2 = &self.linearization[2];
let linearization_3 = &self.linearization[3];
for (dest, src) in dst.chunks_exact_mut(3).zip(src.chunks_exact(4)) {
debug_assert!(self.grid_size as i32 >= 1);
let linear_x = lut_interp_linear_float(src[0], linearization_0);
let linear_y = lut_interp_linear_float(src[1], linearization_1);
let linear_z = lut_interp_linear_float(src[2], linearization_2);
let linear_w = lut_interp_linear_float(src[3], linearization_3);
let clut = fetch(linear_x, linear_y, linear_z, linear_w);
let pcs_x = lut_interp_linear_float(clut.v[0], &self.output[0]);
let pcs_y = lut_interp_linear_float(clut.v[1], &self.output[1]);
let pcs_z = lut_interp_linear_float(clut.v[2], &self.output[2]);
dest[0] = pcs_x;
dest[1] = pcs_y;
dest[2] = pcs_z;
}
Ok(())
}
}
macro_rules! define_lut4_dispatch {
($dispatcher: ident) => {
impl Stage for $dispatcher {
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
let l_tbl = Hypercube::new(&self.clut, self.grid_size as usize);
// If Source PCS is LAB trilinear should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self
.transform_impl(src, dst, |x, y, z, w| l_tbl.quadlinear_vec3(x, y, z, w));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(src, dst, |x, y, z, w| l_tbl.tetra_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(src, dst, |x, y, z, w| l_tbl.pyramid_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(src, dst, |x, y, z, w| l_tbl.prism_vec3(x, y, z, w))?
}
InterpolationMethod::Linear => {
self.transform_impl(src, dst, |x, y, z, w| {
l_tbl.quadlinear_vec3(x, y, z, w)
})?
}
}
Ok(())
}
}
};
}
impl<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>> KatanaLut4x3<T> {
fn to_pcs_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
&self,
input: &[T],
fetch: Fetch,
) -> Result<Vec<f32>, CmsError> {
if input.len() % 4 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let norm_value = if T::FINITE {
1.0 / ((1u32 << self.bit_depth) - 1) as f32
} else {
1.0
};
let mut dst = try_vec![0.; (input.len() / 4) * 3];
let linearization_0 = &self.linearization[0];
let linearization_1 = &self.linearization[1];
let linearization_2 = &self.linearization[2];
let linearization_3 = &self.linearization[3];
for (dest, src) in dst.chunks_exact_mut(3).zip(input.chunks_exact(4)) {
let linear_x = lut_interp_linear_float(src[0].as_() * norm_value, linearization_0);
let linear_y = lut_interp_linear_float(src[1].as_() * norm_value, linearization_1);
let linear_z = lut_interp_linear_float(src[2].as_() * norm_value, linearization_2);
let linear_w = lut_interp_linear_float(src[3].as_() * norm_value, linearization_3);
let clut = fetch(linear_x, linear_y, linear_z, linear_w);
let pcs_x = lut_interp_linear_float(clut.v[0], &self.output[0]);
let pcs_y = lut_interp_linear_float(clut.v[1], &self.output[1]);
let pcs_z = lut_interp_linear_float(clut.v[2], &self.output[2]);
dest[0] = pcs_x;
dest[1] = pcs_y;
dest[2] = pcs_z;
}
Ok(dst)
}
}
impl<T: Copy + PointeeSizeExpressible + AsPrimitive<f32>> KatanaInitialStage<f32, T>
for KatanaLut4x3<T>
{
fn to_pcs(&self, input: &[T]) -> Result<Vec<f32>, CmsError> {
if input.len() % 4 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let l_tbl = Hypercube::new(&self.clut, self.grid_size as usize);
// If Source PCS is LAB trilinear should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.to_pcs_impl(input, |x, y, z, w| l_tbl.quadlinear_vec3(x, y, z, w));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.to_pcs_impl(input, |x, y, z, w| l_tbl.tetra_vec3(x, y, z, w))
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.to_pcs_impl(input, |x, y, z, w| l_tbl.pyramid_vec3(x, y, z, w))
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.to_pcs_impl(input, |x, y, z, w| l_tbl.prism_vec3(x, y, z, w))
}
InterpolationMethod::Linear => {
self.to_pcs_impl(input, |x, y, z, w| l_tbl.quadlinear_vec3(x, y, z, w))
}
}
}
}
define_lut4_dispatch!(Lut4x3);
fn make_lut_4x3(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Lut4x3, CmsError> {
// There is 4 possible cases:
// - All curves are non-linear
// - Linearization curves are non-linear, but gamma is linear
// - Gamma curves are non-linear, but linearization is linear
// - All curves linear
let clut_length: usize = (lut.num_clut_grid_points as usize)
.safe_powi(lut.num_input_channels as u32)?
.safe_mul(lut.num_output_channels as usize)?;
let clut_table = lut.clut_table.to_clut_f32();
if clut_table.len() != clut_length {
return Err(CmsError::MalformedClut(MalformedSize {
size: clut_table.len(),
expected: clut_length,
}));
}
let linearization_table = lut.input_table.to_clut_f32();
if linearization_table.len() < lut.num_input_table_entries as usize * 4 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: linearization_table.len(),
expected: lut.num_input_table_entries as usize * 4,
}));
}
let lin_curve0 = linearization_table[0..lut.num_input_table_entries as usize].to_vec();
let lin_curve1 = linearization_table
[lut.num_input_table_entries as usize..lut.num_input_table_entries as usize * 2]
.to_vec();
let lin_curve2 = linearization_table
[lut.num_input_table_entries as usize * 2..lut.num_input_table_entries as usize * 3]
.to_vec();
let lin_curve3 = linearization_table
[lut.num_input_table_entries as usize * 3..lut.num_input_table_entries as usize * 4]
.to_vec();
let gamma_table = lut.output_table.to_clut_f32();
if gamma_table.len() < lut.num_output_table_entries as usize * 3 {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: gamma_table.len(),
expected: lut.num_output_table_entries as usize * 3,
}));
}
let gamma_curve0 = gamma_table[..lut.num_output_table_entries as usize].to_vec();
let gamma_curve1 = gamma_table
[lut.num_output_table_entries as usize..lut.num_output_table_entries as usize * 2]
.to_vec();
let gamma_curve2 = gamma_table
[lut.num_output_table_entries as usize * 2..lut.num_output_table_entries as usize * 3]
.to_vec();
let transform = Lut4x3 {
linearization: [lin_curve0, lin_curve1, lin_curve2, lin_curve3],
interpolation_method: options.interpolation_method,
pcs,
clut: clut_table,
grid_size: lut.num_clut_grid_points,
output: [gamma_curve0, gamma_curve1, gamma_curve2],
};
Ok(transform)
}
fn stage_lut_4x3(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Box<dyn Stage>, CmsError> {
let lut = make_lut_4x3(lut, options, pcs)?;
let transform = Lut4x3 {
linearization: lut.linearization,
interpolation_method: lut.interpolation_method,
pcs: lut.pcs,
clut: lut.clut,
grid_size: lut.grid_size,
output: lut.output,
};
Ok(Box::new(transform))
}
pub(crate) fn katana_input_stage_lut_4x3<
T: Copy + PointeeSizeExpressible + AsPrimitive<f32> + Send + Sync,
>(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
bit_depth: usize,
) -> Result<Box<dyn KatanaInitialStage<f32, T> + Send + Sync>, CmsError> {
// There is 4 possible cases:
// - All curves are non-linear
// - Linearization curves are non-linear, but gamma is linear
// - Gamma curves are non-linear, but linearization is linear
// - All curves linear
let lut = make_lut_4x3(lut, options, pcs)?;
let transform = KatanaLut4x3::<T> {
linearization: lut.linearization,
interpolation_method: lut.interpolation_method,
pcs: lut.pcs,
clut: lut.clut,
grid_size: lut.grid_size,
output: lut.output,
_phantom: PhantomData,
bit_depth,
};
Ok(Box::new(transform))
}
pub(crate) fn create_lut4_norm_samples<const SAMPLES: usize>() -> Vec<f32> {
let lut_size: u32 = (4 * SAMPLES * SAMPLES * SAMPLES * SAMPLES) as u32;
let mut src = Vec::with_capacity(lut_size as usize);
let recpeq = 1f32 / (SAMPLES - 1) as f32;
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 * recpeq);
src.push(m as f32 * recpeq);
src.push(y as f32 * recpeq);
src.push(k as f32 * recpeq);
}
}
}
}
src
}
pub(crate) fn create_lut4<const SAMPLES: usize>(
lut: &LutDataType,
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Vec<f32>, CmsError> {
if lut.num_input_channels != 4 {
return Err(CmsError::UnsupportedProfileConnection);
}
let lut_size: u32 = (4 * SAMPLES * SAMPLES * SAMPLES * SAMPLES) as u32;
let src = create_lut4_norm_samples::<SAMPLES>();
let mut dest = try_vec![0.; (lut_size as usize) / 4 * 3];
let lut_stage = stage_lut_4x3(lut, options, pcs)?;
lut_stage.transform(&src, &mut dest)?;
Ok(dest)
}

View File

@@ -1,802 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::lut3x3::{
create_lut3x3, katana_input_stage_lut_3x3, katana_output_stage_lut_3x3,
};
use crate::conversions::lut3x4::{create_lut3_samples_norm, create_lut3x4};
use crate::conversions::lut4::{create_lut4, create_lut4_norm_samples, katana_input_stage_lut_4x3};
use crate::conversions::mab::{prepare_mab_3x3, prepare_mba_3x3};
use crate::conversions::transform_lut3_to_4::make_transform_3x4;
use crate::mlaf::mlaf;
use crate::{
CmsError, ColorProfile, DataColorSpace, InPlaceStage, Layout, LutWarehouse, Matrix3f,
ProfileVersion, TransformExecutor, TransformOptions,
};
use num_traits::AsPrimitive;
pub(crate) struct MatrixStage {
pub(crate) matrices: Vec<Matrix3f>,
}
impl InPlaceStage for MatrixStage {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
if !self.matrices.is_empty() {
let m = self.matrices[0];
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
}
}
for m in self.matrices.iter().skip(1) {
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
}
}
Ok(())
}
}
pub(crate) const LUT_SAMPLING: u16 = 255;
pub(crate) trait Lut3x3Factory {
fn make_transform_3x3<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
>(
lut: Vec<f32>,
options: TransformOptions,
color_space: DataColorSpace,
is_linear: bool,
) -> Box<dyn TransformExecutor<T> + Send + Sync>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>;
}
pub(crate) trait Lut4x3Factory {
fn make_transform_4x3<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
>(
lut: Vec<f32>,
options: TransformOptions,
color_space: DataColorSpace,
is_linear: bool,
) -> Box<dyn TransformExecutor<T> + Sync + Send>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>;
}
fn pcs_lab_v4_to_v2(profile: &ColorProfile, lut: &mut [f32]) {
if profile.pcs == DataColorSpace::Lab
&& profile.version_internal <= ProfileVersion::V4_0
&& lut.len() % 3 == 0
{
assert_eq!(
lut.len() % 3,
0,
"Lut {:?} is not a multiple of 3, this should not happen for lab",
lut.len()
);
let v_mat = vec![Matrix3f {
v: [
[65280.0 / 65535.0, 0f32, 0f32],
[0f32, 65280.0 / 65535.0, 0f32],
[0f32, 0f32, 65280.0 / 65535.0f32],
],
}];
let stage = MatrixStage { matrices: v_mat };
stage.transform(lut).unwrap();
}
}
fn pcs_lab_v2_to_v4(profile: &ColorProfile, lut: &mut [f32]) {
if profile.pcs == DataColorSpace::Lab
&& profile.version_internal <= ProfileVersion::V4_0
&& lut.len() % 3 == 0
{
assert_eq!(
lut.len() % 3,
0,
"Lut {:?} is not a multiple of 3, this should not happen for lab",
lut.len()
);
let v_mat = vec![Matrix3f {
v: [
[65535.0 / 65280.0f32, 0f32, 0f32],
[0f32, 65535.0f32 / 65280.0f32, 0f32],
[0f32, 0f32, 65535.0f32 / 65280.0f32],
],
}];
let stage = MatrixStage { matrices: v_mat };
stage.transform(lut).unwrap();
}
}
macro_rules! make_transform_3x3_fn {
($method_name: ident, $exec_impl: ident) => {
fn $method_name<
T: Copy
+ Default
+ AsPrimitive<f32>
+ Send
+ Sync
+ AsPrimitive<usize>
+ PointeeSizeExpressible,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
>(
src_layout: Layout,
dst_layout: Layout,
lut: Vec<f32>,
options: TransformOptions,
color_space: DataColorSpace,
is_linear: bool,
) -> Box<dyn TransformExecutor<T> + Send + Sync>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>,
{
match src_layout {
Layout::Rgb => match dst_layout {
Layout::Rgb => $exec_impl::make_transform_3x3::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
GRID_SIZE,
BIT_DEPTH,
>(lut, options, color_space, is_linear),
Layout::Rgba => $exec_impl::make_transform_3x3::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
GRID_SIZE,
BIT_DEPTH,
>(lut, options, color_space, is_linear),
_ => unimplemented!(),
},
Layout::Rgba => match dst_layout {
Layout::Rgb => $exec_impl::make_transform_3x3::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
GRID_SIZE,
BIT_DEPTH,
>(lut, options, color_space, is_linear),
Layout::Rgba => $exec_impl::make_transform_3x3::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
GRID_SIZE,
BIT_DEPTH,
>(lut, options, color_space, is_linear),
_ => unimplemented!(),
},
_ => unimplemented!(),
}
}
};
}
macro_rules! make_transform_4x3_fn {
($method_name: ident, $exec_name: ident) => {
fn $method_name<
T: Copy
+ Default
+ AsPrimitive<f32>
+ Send
+ Sync
+ AsPrimitive<usize>
+ PointeeSizeExpressible,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
>(
dst_layout: Layout,
lut: Vec<f32>,
options: TransformOptions,
data_color_space: DataColorSpace,
is_linear: bool,
) -> Box<dyn TransformExecutor<T> + Send + Sync>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>,
{
match dst_layout {
Layout::Rgb => $exec_name::make_transform_4x3::<
T,
{ Layout::Rgb as u8 },
GRID_SIZE,
BIT_DEPTH,
>(lut, options, data_color_space, is_linear),
Layout::Rgba => $exec_name::make_transform_4x3::<
T,
{ Layout::Rgba as u8 },
GRID_SIZE,
BIT_DEPTH,
>(lut, options, data_color_space, is_linear),
_ => unimplemented!(),
}
}
};
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
use crate::conversions::neon::NeonLut3x3Factory;
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
make_transform_3x3_fn!(make_transformer_3x3, NeonLut3x3Factory);
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
use crate::conversions::transform_lut3_to_3::DefaultLut3x3Factory;
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
make_transform_3x3_fn!(make_transformer_3x3, DefaultLut3x3Factory);
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
use crate::conversions::avx::AvxLut3x3Factory;
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
make_transform_3x3_fn!(make_transformer_3x3_avx_fma, AvxLut3x3Factory);
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
use crate::conversions::sse::SseLut3x3Factory;
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
make_transform_3x3_fn!(make_transformer_3x3_sse41, SseLut3x3Factory);
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
use crate::conversions::avx::AvxLut4x3Factory;
use crate::conversions::interpolator::LutBarycentricReduction;
use crate::conversions::katana::{
Katana, KatanaDefaultIntermediate, KatanaInitialStage, KatanaPostFinalizationStage,
KatanaStageLabToXyz, KatanaStageXyzToLab, katana_create_rgb_lin_lut, katana_pcs_lab_v2_to_v4,
katana_pcs_lab_v4_to_v2, katana_prepare_inverse_lut_rgb_xyz, multi_dimensional_3x3_to_device,
multi_dimensional_3x3_to_pcs, multi_dimensional_4x3_to_pcs,
};
use crate::conversions::mab4x3::prepare_mab_4x3;
use crate::conversions::mba3x4::prepare_mba_3x4;
use crate::conversions::md_luts_factory::{do_any_to_any, prepare_alpha_finalizer};
// use crate::conversions::bpc::compensate_bpc_in_lut;
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
make_transform_4x3_fn!(make_transformer_4x3_avx_fma, AvxLut4x3Factory);
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
use crate::conversions::sse::SseLut4x3Factory;
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
make_transform_4x3_fn!(make_transformer_4x3_sse41, SseLut4x3Factory);
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
use crate::conversions::transform_lut4_to_3::DefaultLut4x3Factory;
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
make_transform_4x3_fn!(make_transformer_4x3, DefaultLut4x3Factory);
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
use crate::conversions::neon::NeonLut4x3Factory;
use crate::conversions::prelude_lut_xyz_rgb::{create_rgb_lin_lut, prepare_inverse_lut_rgb_xyz};
use crate::conversions::xyz_lab::{StageLabToXyz, StageXyzToLab};
use crate::transform::PointeeSizeExpressible;
use crate::trc::GammaLutInterpolate;
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
make_transform_4x3_fn!(make_transformer_4x3, NeonLut4x3Factory);
#[inline(never)]
#[cold]
pub(crate) fn make_lut_transform<
T: Copy
+ Default
+ AsPrimitive<f32>
+ Send
+ Sync
+ AsPrimitive<usize>
+ PointeeSizeExpressible
+ GammaLutInterpolate,
const BIT_DEPTH: usize,
const LINEAR_CAP: usize,
const GAMMA_LUT: usize,
>(
src_layout: Layout,
source: &ColorProfile,
dst_layout: Layout,
dest: &ColorProfile,
options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>,
{
if (source.color_space == DataColorSpace::Cmyk || source.color_space == DataColorSpace::Color4)
&& (dest.color_space == DataColorSpace::Rgb || dest.color_space == DataColorSpace::Lab)
{
source.color_space.check_layout(src_layout)?;
dest.color_space.check_layout(dst_layout)?;
if source.pcs != DataColorSpace::Xyz && source.pcs != DataColorSpace::Lab {
return Err(CmsError::UnsupportedProfileConnection);
}
if dest.pcs != DataColorSpace::Lab && dest.pcs != DataColorSpace::Xyz {
return Err(CmsError::UnsupportedProfileConnection);
}
const GRID_SIZE: usize = 17;
let is_katana_required_for_source = source
.get_device_to_pcs(options.rendering_intent)
.ok_or(CmsError::UnsupportedLutRenderingIntent(
source.rendering_intent,
))
.map(|x| x.is_katana_required())?;
let is_katana_required_for_destination =
if dest.is_matrix_shaper() || dest.pcs == DataColorSpace::Xyz {
false
} else if dest.pcs == DataColorSpace::Lab {
dest.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)
.map(|x| x.is_katana_required())?
} else {
return Err(CmsError::UnsupportedProfileConnection);
};
if is_katana_required_for_source || is_katana_required_for_destination {
let initial_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync> =
match source.get_device_to_pcs(options.rendering_intent).ok_or(
CmsError::UnsupportedLutRenderingIntent(source.rendering_intent),
)? {
LutWarehouse::Lut(lut) => {
katana_input_stage_lut_4x3::<T>(lut, options, source.pcs, BIT_DEPTH)?
}
LutWarehouse::Multidimensional(mab) => {
multi_dimensional_4x3_to_pcs::<T>(mab, options, source.pcs, BIT_DEPTH)?
}
};
let mut stages = Vec::new();
stages.push(katana_pcs_lab_v2_to_v4(source));
if source.pcs == DataColorSpace::Lab {
stages.push(Box::new(KatanaStageLabToXyz::default()));
}
if dest.pcs == DataColorSpace::Lab {
stages.push(Box::new(KatanaStageXyzToLab::default()));
}
stages.push(katana_pcs_lab_v4_to_v2(dest));
let final_stage = if dest.has_pcs_to_device_lut() {
let pcs_to_device = dest
.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?;
match pcs_to_device {
LutWarehouse::Lut(lut) => {
katana_output_stage_lut_3x3::<T>(lut, options, dest.pcs, BIT_DEPTH)?
}
LutWarehouse::Multidimensional(mab) => {
multi_dimensional_3x3_to_device::<T>(mab, options, dest.pcs, BIT_DEPTH)?
}
}
} else if dest.is_matrix_shaper() {
let state = katana_prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(
dest, dst_layout, options,
)?;
stages.extend(state.stages);
state.final_stage
} else {
return Err(CmsError::UnsupportedProfileConnection);
};
let mut post_finalization: Vec<Box<dyn KatanaPostFinalizationStage<T> + Send + Sync>> =
Vec::new();
if let Some(stage) =
prepare_alpha_finalizer::<T>(src_layout, source, dst_layout, dest, BIT_DEPTH)
{
post_finalization.push(stage);
}
return Ok(Box::new(Katana::<f32, T> {
initial_stage,
final_stage,
stages,
post_finalization,
}));
}
let mut lut = match source.get_device_to_pcs(options.rendering_intent).ok_or(
CmsError::UnsupportedLutRenderingIntent(source.rendering_intent),
)? {
LutWarehouse::Lut(lut) => create_lut4::<GRID_SIZE>(lut, options, source.pcs)?,
LutWarehouse::Multidimensional(m_curves) => {
let mut samples = create_lut4_norm_samples::<GRID_SIZE>();
prepare_mab_4x3(m_curves, &mut samples, options, source.pcs)?
}
};
pcs_lab_v2_to_v4(source, &mut lut);
if source.pcs == DataColorSpace::Lab {
let lab_to_xyz_stage = StageLabToXyz::default();
lab_to_xyz_stage.transform(&mut lut)?;
}
// if source.color_space == DataColorSpace::Cmyk
// && (options.rendering_intent == RenderingIntent::Perceptual
// || options.rendering_intent == RenderingIntent::RelativeColorimetric)
// && options.black_point_compensation
// {
// if let (Some(src_bp), Some(dst_bp)) = (
// source.detect_black_point::<GRID_SIZE>(&lut),
// dest.detect_black_point::<GRID_SIZE>(&lut),
// ) {
// compensate_bpc_in_lut(&mut lut, src_bp, dst_bp);
// }
// }
if dest.pcs == DataColorSpace::Lab {
let lab_to_xyz_stage = StageXyzToLab::default();
lab_to_xyz_stage.transform(&mut lut)?;
}
pcs_lab_v4_to_v2(dest, &mut lut);
if dest.pcs == DataColorSpace::Xyz {
if dest.is_matrix_shaper() {
prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(dest, &mut lut, options)?;
} else {
return Err(CmsError::UnsupportedProfileConnection);
}
} else if dest.pcs == DataColorSpace::Lab {
let pcs_to_device = dest
.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?;
match pcs_to_device {
LutWarehouse::Lut(lut_data_type) => {
lut = create_lut3x3(lut_data_type, &lut, options, dest.pcs)?
}
LutWarehouse::Multidimensional(mab) => {
prepare_mba_3x3(mab, &mut lut, options, dest.pcs)?
}
}
}
let is_dest_linear_profile = dest.color_space == DataColorSpace::Rgb
&& dest.is_matrix_shaper()
&& dest.is_linear_matrix_shaper();
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
if std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma")
{
return Ok(make_transformer_4x3_avx_fma::<T, GRID_SIZE, BIT_DEPTH>(
dst_layout,
lut,
options,
dest.color_space,
is_dest_linear_profile,
));
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
if std::arch::is_x86_feature_detected!("sse4.1") {
return Ok(make_transformer_4x3_sse41::<T, GRID_SIZE, BIT_DEPTH>(
dst_layout,
lut,
options,
dest.color_space,
is_dest_linear_profile,
));
}
Ok(make_transformer_4x3::<T, GRID_SIZE, BIT_DEPTH>(
dst_layout,
lut,
options,
dest.color_space,
is_dest_linear_profile,
))
} else if (source.color_space == DataColorSpace::Rgb
|| source.color_space == DataColorSpace::Lab)
&& (dest.color_space == DataColorSpace::Cmyk || dest.color_space == DataColorSpace::Color4)
{
source.color_space.check_layout(src_layout)?;
dest.color_space.check_layout(dst_layout)?;
if source.pcs != DataColorSpace::Xyz && source.pcs != DataColorSpace::Lab {
return Err(CmsError::UnsupportedProfileConnection);
}
const GRID_SIZE: usize = 33;
let mut lut: Vec<f32>;
if source.has_device_to_pcs_lut() {
let device_to_pcs = source
.get_device_to_pcs(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?;
lut = create_lut3_samples_norm::<GRID_SIZE>();
match device_to_pcs {
LutWarehouse::Lut(lut_data_type) => {
lut = create_lut3x3(lut_data_type, &lut, options, source.pcs)?;
}
LutWarehouse::Multidimensional(mab) => {
prepare_mab_3x3(mab, &mut lut, options, source.pcs)?
}
}
} else if source.is_matrix_shaper() {
lut = create_rgb_lin_lut::<T, BIT_DEPTH, LINEAR_CAP, GRID_SIZE>(source, options)?;
} else {
return Err(CmsError::UnsupportedProfileConnection);
}
pcs_lab_v2_to_v4(source, &mut lut);
if source.pcs == DataColorSpace::Xyz && dest.pcs == DataColorSpace::Lab {
let xyz_to_lab = StageXyzToLab::default();
xyz_to_lab.transform(&mut lut)?;
} else if source.pcs == DataColorSpace::Lab && dest.pcs == DataColorSpace::Xyz {
let lab_to_xyz_stage = StageLabToXyz::default();
lab_to_xyz_stage.transform(&mut lut)?;
}
pcs_lab_v4_to_v2(dest, &mut lut);
let lut = match dest
.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?
{
LutWarehouse::Lut(lut_type) => create_lut3x4(lut_type, &lut, options, dest.pcs)?,
LutWarehouse::Multidimensional(m_curves) => {
prepare_mba_3x4(m_curves, &mut lut, options, dest.pcs)?
}
};
let is_dest_linear_profile = dest.color_space == DataColorSpace::Rgb
&& dest.is_matrix_shaper()
&& dest.is_linear_matrix_shaper();
Ok(make_transform_3x4::<T, GRID_SIZE, BIT_DEPTH>(
src_layout,
lut,
options,
dest.color_space,
is_dest_linear_profile,
))
} else if (source.color_space.is_three_channels()) && (dest.color_space.is_three_channels()) {
source.color_space.check_layout(src_layout)?;
dest.color_space.check_layout(dst_layout)?;
const GRID_SIZE: usize = 33;
let is_katana_required_for_source = if source.is_matrix_shaper() {
false
} else {
source
.get_device_to_pcs(options.rendering_intent)
.ok_or(CmsError::UnsupportedLutRenderingIntent(
source.rendering_intent,
))
.map(|x| x.is_katana_required())?
};
let is_katana_required_for_destination =
if source.is_matrix_shaper() || dest.pcs == DataColorSpace::Xyz {
false
} else if dest.pcs == DataColorSpace::Lab {
dest.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)
.map(|x| x.is_katana_required())?
} else {
return Err(CmsError::UnsupportedProfileConnection);
};
let mut stages: Vec<Box<KatanaDefaultIntermediate>> = Vec::new();
// Slow and accurate fallback if anything not acceptable is detected by curve analysis
if is_katana_required_for_source || is_katana_required_for_destination {
let source_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync> =
if source.is_matrix_shaper() {
let state = katana_create_rgb_lin_lut::<T, BIT_DEPTH, LINEAR_CAP>(
src_layout, source, options,
)?;
stages.extend(state.stages);
state.initial_stage
} else {
match source.get_device_to_pcs(options.rendering_intent).ok_or(
CmsError::UnsupportedLutRenderingIntent(source.rendering_intent),
)? {
LutWarehouse::Lut(lut) => {
katana_input_stage_lut_3x3::<T>(lut, options, source.pcs, BIT_DEPTH)?
}
LutWarehouse::Multidimensional(mab) => {
multi_dimensional_3x3_to_pcs::<T>(mab, options, source.pcs, BIT_DEPTH)?
}
}
};
stages.push(katana_pcs_lab_v2_to_v4(source));
if source.pcs == DataColorSpace::Lab {
stages.push(Box::new(KatanaStageLabToXyz::default()));
}
if dest.pcs == DataColorSpace::Lab {
stages.push(Box::new(KatanaStageXyzToLab::default()));
}
stages.push(katana_pcs_lab_v4_to_v2(dest));
let final_stage = if dest.has_pcs_to_device_lut() {
let pcs_to_device = dest
.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?;
match pcs_to_device {
LutWarehouse::Lut(lut) => {
katana_output_stage_lut_3x3::<T>(lut, options, dest.pcs, BIT_DEPTH)?
}
LutWarehouse::Multidimensional(mab) => {
multi_dimensional_3x3_to_device::<T>(mab, options, dest.pcs, BIT_DEPTH)?
}
}
} else if dest.is_matrix_shaper() {
let state = katana_prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(
dest, dst_layout, options,
)?;
stages.extend(state.stages);
state.final_stage
} else {
return Err(CmsError::UnsupportedProfileConnection);
};
let mut post_finalization: Vec<Box<dyn KatanaPostFinalizationStage<T> + Send + Sync>> =
Vec::new();
if let Some(stage) =
prepare_alpha_finalizer::<T>(src_layout, source, dst_layout, dest, BIT_DEPTH)
{
post_finalization.push(stage);
}
return Ok(Box::new(Katana::<f32, T> {
initial_stage: source_stage,
final_stage,
stages,
post_finalization,
}));
}
let mut lut: Vec<f32>;
if source.has_device_to_pcs_lut() {
let device_to_pcs = source
.get_device_to_pcs(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?;
lut = create_lut3_samples_norm::<GRID_SIZE>();
match device_to_pcs {
LutWarehouse::Lut(lut_data_type) => {
lut = create_lut3x3(lut_data_type, &lut, options, source.pcs)?;
}
LutWarehouse::Multidimensional(mab) => {
prepare_mab_3x3(mab, &mut lut, options, source.pcs)?
}
}
} else if source.is_matrix_shaper() {
lut = create_rgb_lin_lut::<T, BIT_DEPTH, LINEAR_CAP, GRID_SIZE>(source, options)?;
} else {
return Err(CmsError::UnsupportedProfileConnection);
}
pcs_lab_v2_to_v4(source, &mut lut);
if source.pcs == DataColorSpace::Xyz && dest.pcs == DataColorSpace::Lab {
let xyz_to_lab = StageXyzToLab::default();
xyz_to_lab.transform(&mut lut)?;
} else if source.pcs == DataColorSpace::Lab && dest.pcs == DataColorSpace::Xyz {
let lab_to_xyz_stage = StageLabToXyz::default();
lab_to_xyz_stage.transform(&mut lut)?;
}
pcs_lab_v4_to_v2(dest, &mut lut);
if dest.has_pcs_to_device_lut() {
let pcs_to_device = dest
.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?;
match pcs_to_device {
LutWarehouse::Lut(lut_data_type) => {
lut = create_lut3x3(lut_data_type, &lut, options, dest.pcs)?;
}
LutWarehouse::Multidimensional(mab) => {
prepare_mba_3x3(mab, &mut lut, options, dest.pcs)?
}
}
} else if dest.is_matrix_shaper() {
prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(dest, &mut lut, options)?;
} else {
return Err(CmsError::UnsupportedProfileConnection);
}
let is_dest_linear_profile = dest.color_space == DataColorSpace::Rgb
&& dest.is_matrix_shaper()
&& dest.is_linear_matrix_shaper();
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
if std::arch::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("fma") {
return Ok(make_transformer_3x3_avx_fma::<T, GRID_SIZE, BIT_DEPTH>(
src_layout,
dst_layout,
lut,
options,
dest.color_space,
is_dest_linear_profile,
));
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
if std::arch::is_x86_feature_detected!("sse4.1") {
return Ok(make_transformer_3x3_sse41::<T, GRID_SIZE, BIT_DEPTH>(
src_layout,
dst_layout,
lut,
options,
dest.color_space,
is_dest_linear_profile,
));
}
Ok(make_transformer_3x3::<T, GRID_SIZE, BIT_DEPTH>(
src_layout,
dst_layout,
lut,
options,
dest.color_space,
is_dest_linear_profile,
))
} else {
do_any_to_any::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_LUT>(
src_layout, source, dst_layout, dest, options,
)
}
}

View File

@@ -1,559 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::mlaf::mlaf;
use crate::safe_math::SafeMul;
use crate::{
CmsError, Cube, DataColorSpace, InPlaceStage, InterpolationMethod, LutMultidimensionalType,
MalformedSize, Matrix3d, Matrix3f, TransformOptions, Vector3d, Vector3f,
};
#[allow(unused)]
struct ACurves3<'a> {
curve0: Box<[f32; 65536]>,
curve1: Box<[f32; 65536]>,
curve2: Box<[f32; 65536]>,
clut: &'a [f32],
grid_size: [u8; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
depth: usize,
}
#[allow(unused)]
struct ACurves3Optimized<'a> {
clut: &'a [f32],
grid_size: [u8; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
}
#[allow(unused)]
impl ACurves3<'_> {
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let scale_value = (self.depth - 1) as f32;
for dst in dst.chunks_exact_mut(3) {
let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
let b0 = self.curve0[a0 as usize];
let b1 = self.curve1[a1 as usize];
let b2 = self.curve2[a2 as usize];
let interpolated = fetch(b0, b1, b2);
dst[0] = interpolated.v[0];
dst[1] = interpolated.v[1];
dst[2] = interpolated.v[2];
}
Ok(())
}
}
#[allow(unused)]
impl ACurves3Optimized<'_> {
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
for dst in dst.chunks_exact_mut(3) {
let a0 = dst[0];
let a1 = dst[1];
let a2 = dst[2];
let interpolated = fetch(a0, a1, a2);
dst[0] = interpolated.v[0];
dst[1] = interpolated.v[1];
dst[2] = interpolated.v[2];
}
Ok(())
}
}
impl InPlaceStage for ACurves3<'_> {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
let lut = Cube::new_cube(self.clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
}
InterpolationMethod::Linear => {
self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
}
}
Ok(())
}
}
impl InPlaceStage for ACurves3Optimized<'_> {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
let lut = Cube::new_cube(self.clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab {
return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
}
InterpolationMethod::Linear => {
self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
}
}
Ok(())
}
}
#[allow(unused)]
struct ACurves3Inverse<'a> {
curve0: Box<[f32; 65536]>,
curve1: Box<[f32; 65536]>,
curve2: Box<[f32; 65536]>,
clut: &'a [f32],
grid_size: [u8; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
depth: usize,
}
#[allow(unused)]
impl ACurves3Inverse<'_> {
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector3f>(
&self,
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let scale_value = (self.depth as u32 - 1u32) as f32;
for dst in dst.chunks_exact_mut(3) {
let interpolated = fetch(dst[0], dst[1], dst[2]);
let a0 = (interpolated.v[0] * scale_value).round().min(scale_value) as u16;
let a1 = (interpolated.v[1] * scale_value).round().min(scale_value) as u16;
let a2 = (interpolated.v[2] * scale_value).round().min(scale_value) as u16;
let b0 = self.curve0[a0 as usize];
let b1 = self.curve1[a1 as usize];
let b2 = self.curve2[a2 as usize];
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
}
Ok(())
}
}
impl InPlaceStage for ACurves3Inverse<'_> {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
let lut = Cube::new_cube(self.clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(dst, |x, y, z| lut.tetra_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(dst, |x, y, z| lut.pyramid_vec3(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(dst, |x, y, z| lut.prism_vec3(x, y, z))?;
}
InterpolationMethod::Linear => {
self.transform_impl(dst, |x, y, z| lut.trilinear_vec3(x, y, z))?;
}
}
Ok(())
}
}
pub(crate) struct MCurves3 {
pub(crate) curve0: Box<[f32; 65536]>,
pub(crate) curve1: Box<[f32; 65536]>,
pub(crate) curve2: Box<[f32; 65536]>,
pub(crate) matrix: Matrix3f,
pub(crate) bias: Vector3f,
pub(crate) inverse: bool,
pub(crate) depth: usize,
}
impl MCurves3 {
fn execute_matrix_stage(&self, dst: &mut [f32]) {
let m = self.matrix;
let b = self.bias;
if !m.test_equality(Matrix3f::IDENTITY) || !b.eq(&Vector3f::default()) {
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(mlaf(b.v[0], x, m.v[0][0]), y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(mlaf(b.v[1], x, m.v[1][0]), y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(mlaf(b.v[2], x, m.v[2][0]), y, m.v[2][1]), z, m.v[2][2]);
}
}
}
}
impl InPlaceStage for MCurves3 {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
let scale_value = (self.depth - 1) as f32;
if self.inverse {
self.execute_matrix_stage(dst);
}
for dst in dst.chunks_exact_mut(3) {
let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
let b0 = self.curve0[a0 as usize];
let b1 = self.curve1[a1 as usize];
let b2 = self.curve2[a2 as usize];
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
}
if !self.inverse {
self.execute_matrix_stage(dst);
}
Ok(())
}
}
pub(crate) struct BCurves3<const DEPTH: usize> {
pub(crate) curve0: Box<[f32; 65536]>,
pub(crate) curve1: Box<[f32; 65536]>,
pub(crate) curve2: Box<[f32; 65536]>,
}
impl<const DEPTH: usize> InPlaceStage for BCurves3<DEPTH> {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
let scale_value = (DEPTH - 1) as f32;
for dst in dst.chunks_exact_mut(3) {
let a0 = (dst[0] * scale_value).round().min(scale_value) as u16;
let a1 = (dst[1] * scale_value).round().min(scale_value) as u16;
let a2 = (dst[2] * scale_value).round().min(scale_value) as u16;
let b0 = self.curve0[a0 as usize];
let b1 = self.curve1[a1 as usize];
let b2 = self.curve2[a2 as usize];
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
}
Ok(())
}
}
pub(crate) fn prepare_mab_3x3(
mab: &LutMultidimensionalType,
lut: &mut [f32],
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<(), CmsError> {
const LERP_DEPTH: usize = 65536;
const BP: usize = 13;
const DEPTH: usize = 8192;
if mab.num_input_channels != 3 && mab.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
if mab.a_curves.len() == 3 && mab.clut.is_some() {
let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let lut_grid = (mab.grid_points[0] as usize)
.safe_mul(mab.grid_points[1] as usize)?
.safe_mul(mab.grid_points[2] as usize)?
.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
if all_curves_linear {
let l = ACurves3Optimized {
clut,
grid_size,
interpolation_method: options.interpolation_method,
pcs,
};
l.transform(lut)?;
} else {
let curves: Result<Vec<_>, _> = mab
.a_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let l = ACurves3 {
curve0,
curve1,
curve2,
clut,
grid_size,
interpolation_method: options.interpolation_method,
pcs,
depth: DEPTH,
};
l.transform(lut)?;
}
}
if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let curves: Result<Vec<_>, _> = mab
.m_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let matrix = mab.matrix.to_f32();
let bias: Vector3f = mab.bias.cast();
let m_curves = MCurves3 {
curve0,
curve1,
curve2,
matrix,
bias,
inverse: false,
depth: DEPTH,
};
m_curves.transform(lut)?;
}
}
if mab.b_curves.len() == 3 {
const LERP_DEPTH: usize = 65536;
const BP: usize = 13;
const DEPTH: usize = 8192;
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear {
let curves: Result<Vec<_>, _> = mab
.b_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let b_curves = BCurves3::<DEPTH> {
curve0,
curve1,
curve2,
};
b_curves.transform(lut)?;
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
Ok(())
}
pub(crate) fn prepare_mba_3x3(
mab: &LutMultidimensionalType,
lut: &mut [f32],
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<(), CmsError> {
if mab.num_input_channels != 3 && mab.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
const LERP_DEPTH: usize = 65536;
const BP: usize = 13;
const DEPTH: usize = 8192;
if mab.b_curves.len() == 3 {
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear {
let curves: Result<Vec<_>, _> = mab
.b_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let b_curves = BCurves3::<DEPTH> {
curve0,
curve1,
curve2,
};
b_curves.transform(lut)?;
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let curves: Result<Vec<_>, _> = mab
.m_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let matrix = mab.matrix.to_f32();
let bias: Vector3f = mab.bias.cast();
let m_curves = MCurves3 {
curve0,
curve1,
curve2,
matrix,
bias,
inverse: true,
depth: DEPTH,
};
m_curves.transform(lut)?;
}
}
if mab.a_curves.len() == 3 && mab.clut.is_some() {
let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let lut_grid = (mab.grid_points[0] as usize)
.safe_mul(mab.grid_points[1] as usize)?
.safe_mul(mab.grid_points[2] as usize)?
.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedCurveLutTable(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
if all_curves_linear {
let l = ACurves3Optimized {
clut,
grid_size,
interpolation_method: options.interpolation_method,
pcs,
};
l.transform(lut)?;
} else {
let curves: Result<Vec<_>, _> = mab
.a_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let l = ACurves3Inverse {
curve0,
curve1,
curve2,
clut,
grid_size,
interpolation_method: options.interpolation_method,
pcs,
depth: DEPTH,
};
l.transform(lut)?;
}
}
Ok(())
}

View File

@@ -1,307 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::mab::{BCurves3, MCurves3};
use crate::err::try_vec;
use crate::safe_math::SafeMul;
use crate::{
CmsError, DataColorSpace, Hypercube, InPlaceStage, InterpolationMethod,
LutMultidimensionalType, MalformedSize, Matrix3d, Stage, TransformOptions, Vector3d, Vector3f,
};
#[allow(dead_code)]
struct ACurves4x3<'a> {
curve0: Box<[f32; 65536]>,
curve1: Box<[f32; 65536]>,
curve2: Box<[f32; 65536]>,
curve3: Box<[f32; 65536]>,
clut: &'a [f32],
grid_size: [u8; 4],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
depth: usize,
}
#[allow(dead_code)]
struct ACurves4x3Optimized<'a> {
clut: &'a [f32],
grid_size: [u8; 4],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
}
#[allow(dead_code)]
impl ACurves4x3<'_> {
fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
&self,
src: &[f32],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let scale_value = (self.depth - 1) as f32;
assert_eq!(src.len() / 4, dst.len() / 3);
for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
let a0 = (src[0] * scale_value).round().min(scale_value) as u16;
let a1 = (src[1] * scale_value).round().min(scale_value) as u16;
let a2 = (src[2] * scale_value).round().min(scale_value) as u16;
let a3 = (src[3] * scale_value).round().min(scale_value) as u16;
let c = self.curve0[a0 as usize];
let m = self.curve1[a1 as usize];
let y = self.curve2[a2 as usize];
let k = self.curve3[a3 as usize];
let r = fetch(c, m, y, k);
dst[0] = r.v[0];
dst[1] = r.v[1];
dst[2] = r.v[2];
}
Ok(())
}
}
#[allow(dead_code)]
impl ACurves4x3Optimized<'_> {
fn transform_impl<Fetch: Fn(f32, f32, f32, f32) -> Vector3f>(
&self,
src: &[f32],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
assert_eq!(src.len() / 4, dst.len() / 3);
for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(3)) {
let c = src[0];
let m = src[1];
let y = src[2];
let k = src[3];
let r = fetch(c, m, y, k);
dst[0] = r.v[0];
dst[1] = r.v[1];
dst[2] = r.v[2];
}
Ok(())
}
}
impl Stage for ACurves4x3<'_> {
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
let lut = Hypercube::new_hypercube(self.clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
}
InterpolationMethod::Linear => {
self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
}
}
Ok(())
}
}
impl Stage for ACurves4x3Optimized<'_> {
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
let lut = Hypercube::new_hypercube(self.clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(src, dst, |x, y, z, w| lut.tetra_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(src, dst, |x, y, z, w| lut.pyramid_vec3(x, y, z, w))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(src, dst, |x, y, z, w| lut.prism_vec3(x, y, z, w))?;
}
InterpolationMethod::Linear => {
self.transform_impl(src, dst, |x, y, z, w| lut.quadlinear_vec3(x, y, z, w))?;
}
}
Ok(())
}
}
pub(crate) fn prepare_mab_4x3(
mab: &LutMultidimensionalType,
lut: &mut [f32],
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Vec<f32>, CmsError> {
const LERP_DEPTH: usize = 65536;
const BP: usize = 13;
const DEPTH: usize = 8192;
if mab.num_input_channels != 4 && mab.num_output_channels != 3 {
return Err(CmsError::UnsupportedProfileConnection);
}
let mut new_lut = try_vec![0f32; (lut.len() / 4) * 3];
if mab.a_curves.len() == 4 && mab.clut.is_some() {
let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let lut_grid = (mab.grid_points[0] as usize)
.safe_mul(mab.grid_points[1] as usize)?
.safe_mul(mab.grid_points[2] as usize)?
.safe_mul(mab.grid_points[3] as usize)?
.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedClut(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
let grid_size = [
mab.grid_points[0],
mab.grid_points[1],
mab.grid_points[2],
mab.grid_points[3],
];
if all_curves_linear {
let l = ACurves4x3Optimized {
clut,
grid_size,
interpolation_method: options.interpolation_method,
pcs,
};
l.transform(lut, &mut new_lut)?;
} else {
let curves: Result<Vec<_>, _> = mab
.a_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2, curve3] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let l = ACurves4x3 {
curve0,
curve1,
curve2,
curve3,
clut,
grid_size,
interpolation_method: options.interpolation_method,
pcs,
depth: DEPTH,
};
l.transform(lut, &mut new_lut)?;
}
} else {
// Not supported
return Err(CmsError::UnsupportedProfileConnection);
}
if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let curves: Result<Vec<_>, _> = mab
.m_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let matrix = mab.matrix.to_f32();
let bias: Vector3f = mab.bias.cast();
let m_curves = MCurves3 {
curve0,
curve1,
curve2,
matrix,
bias,
inverse: false,
depth: DEPTH,
};
m_curves.transform(&mut new_lut)?;
}
}
if mab.b_curves.len() == 3 {
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear {
let curves: Result<Vec<_>, _> = mab
.b_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let b_curves = BCurves3::<DEPTH> {
curve0,
curve1,
curve2,
};
b_curves.transform(&mut new_lut)?;
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
Ok(new_lut)
}

View File

@@ -1,302 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::mab::{BCurves3, MCurves3};
use crate::err::try_vec;
use crate::safe_math::SafeMul;
use crate::{
CmsError, Cube, DataColorSpace, InPlaceStage, InterpolationMethod, LutMultidimensionalType,
MalformedSize, Matrix3d, Stage, TransformOptions, Vector3d, Vector4f,
};
struct ACurves3x4Inverse<'a> {
curve0: Box<[f32; 65536]>,
curve1: Box<[f32; 65536]>,
curve2: Box<[f32; 65536]>,
curve3: Box<[f32; 65536]>,
clut: &'a [f32],
grid_size: [u8; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
depth: usize,
}
struct ACurves3x4InverseOptimized<'a> {
clut: &'a [f32],
grid_size: [u8; 3],
interpolation_method: InterpolationMethod,
pcs: DataColorSpace,
}
impl ACurves3x4Inverse<'_> {
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector4f>(
&self,
src: &[f32],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
let scale_value = (self.depth as u32 - 1u32) as f32;
assert_eq!(src.len() / 3, dst.len() / 4);
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(4)) {
let interpolated = fetch(src[0], src[1], src[2]);
let a0 = (interpolated.v[0] * scale_value).round().min(scale_value) as u16;
let a1 = (interpolated.v[1] * scale_value).round().min(scale_value) as u16;
let a2 = (interpolated.v[2] * scale_value).round().min(scale_value) as u16;
let a3 = (interpolated.v[3] * scale_value).round().min(scale_value) as u16;
let b0 = self.curve0[a0 as usize];
let b1 = self.curve1[a1 as usize];
let b2 = self.curve2[a2 as usize];
let b3 = self.curve3[a3 as usize];
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
dst[3] = b3;
}
Ok(())
}
}
impl ACurves3x4InverseOptimized<'_> {
fn transform_impl<Fetch: Fn(f32, f32, f32) -> Vector4f>(
&self,
src: &[f32],
dst: &mut [f32],
fetch: Fetch,
) -> Result<(), CmsError> {
assert_eq!(src.len() / 3, dst.len() / 4);
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(4)) {
let interpolated = fetch(src[0], src[1], src[2]);
let b0 = interpolated.v[0];
let b1 = interpolated.v[1];
let b2 = interpolated.v[2];
let b3 = interpolated.v[3];
dst[0] = b0;
dst[1] = b1;
dst[2] = b2;
dst[3] = b3;
}
Ok(())
}
}
impl Stage for ACurves3x4Inverse<'_> {
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
let lut = Cube::new_cube(self.clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(src, dst, |x, y, z| lut.tetra_vec4(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(src, dst, |x, y, z| lut.pyramid_vec4(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(src, dst, |x, y, z| lut.prism_vec4(x, y, z))?;
}
InterpolationMethod::Linear => {
self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z))?;
}
}
Ok(())
}
}
impl Stage for ACurves3x4InverseOptimized<'_> {
fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError> {
let lut = Cube::new_cube(self.clut, self.grid_size);
// If PCS is LAB then linear interpolation should be used
if self.pcs == DataColorSpace::Lab || self.pcs == DataColorSpace::Xyz {
return self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z));
}
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
self.transform_impl(src, dst, |x, y, z| lut.tetra_vec4(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
self.transform_impl(src, dst, |x, y, z| lut.pyramid_vec4(x, y, z))?;
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
self.transform_impl(src, dst, |x, y, z| lut.prism_vec4(x, y, z))?;
}
InterpolationMethod::Linear => {
self.transform_impl(src, dst, |x, y, z| lut.trilinear_vec4(x, y, z))?;
}
}
Ok(())
}
}
pub(crate) fn prepare_mba_3x4(
mab: &LutMultidimensionalType,
lut: &mut [f32],
options: TransformOptions,
pcs: DataColorSpace,
) -> Result<Vec<f32>, CmsError> {
if mab.num_input_channels != 3 && mab.num_output_channels != 4 {
return Err(CmsError::UnsupportedProfileConnection);
}
const LERP_DEPTH: usize = 65536;
const BP: usize = 13;
const DEPTH: usize = 8192;
if mab.b_curves.len() == 3 {
let all_curves_linear = mab.b_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear {
let curves: Result<Vec<_>, _> = mab
.b_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let b_curves = BCurves3::<DEPTH> {
curve0,
curve1,
curve2,
};
b_curves.transform(lut)?;
}
} else {
return Err(CmsError::InvalidAtoBLut);
}
if mab.m_curves.len() == 3 {
let all_curves_linear = mab.m_curves.iter().all(|curve| curve.is_linear());
if !all_curves_linear
|| !mab.matrix.test_equality(Matrix3d::IDENTITY)
|| mab.bias.ne(&Vector3d::default())
{
let curves: Result<Vec<_>, _> = mab
.m_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let matrix = mab.matrix.to_f32();
let bias = mab.bias.cast();
let m_curves = MCurves3 {
curve0,
curve1,
curve2,
matrix,
bias,
inverse: true,
depth: DEPTH,
};
m_curves.transform(lut)?;
}
}
let mut new_lut = try_vec![0f32; (lut.len() / 3) * 4];
if mab.a_curves.len() == 4 && mab.clut.is_some() {
let clut = &mab.clut.as_ref().map(|x| x.to_clut_f32()).unwrap();
let lut_grid = (mab.grid_points[0] as usize)
.safe_mul(mab.grid_points[1] as usize)?
.safe_mul(mab.grid_points[2] as usize)?
.safe_mul(mab.num_output_channels as usize)?;
if clut.len() != lut_grid {
return Err(CmsError::MalformedClut(MalformedSize {
size: clut.len(),
expected: lut_grid,
}));
}
let grid_size = [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]];
let all_curves_linear = mab.a_curves.iter().all(|curve| curve.is_linear());
if all_curves_linear {
let a_curves = ACurves3x4InverseOptimized {
clut,
grid_size: [mab.grid_points[0], mab.grid_points[1], mab.grid_points[2]],
interpolation_method: options.interpolation_method,
pcs,
};
a_curves.transform(lut, &mut new_lut)?;
} else {
let curves: Result<Vec<_>, _> = mab
.a_curves
.iter()
.map(|c| {
c.build_linearize_table::<u16, LERP_DEPTH, BP>()
.ok_or(CmsError::InvalidTrcCurve)
})
.collect();
let [curve0, curve1, curve2, curve3] =
curves?.try_into().map_err(|_| CmsError::InvalidTrcCurve)?;
let a_curves = ACurves3x4Inverse {
curve0,
curve1,
curve2,
curve3,
clut,
grid_size,
interpolation_method: options.interpolation_method,
depth: DEPTH,
pcs,
};
a_curves.transform(lut, &mut new_lut)?;
}
} else {
return Err(CmsError::UnsupportedProfileConnection);
}
Ok(new_lut)
}

View File

@@ -1,728 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::math::{FusedMultiplyAdd, FusedMultiplyNegAdd};
use crate::mlaf::{mlaf, neg_mlaf};
use crate::nd_array::{ArrayFetch, lerp};
use crate::{Vector3f, Vector3i};
use num_traits::MulAdd;
use std::array::from_fn;
use std::marker::PhantomData;
use std::ops::{Add, Mul, Neg, Sub};
pub(crate) struct MultidimensionalLut {
pub(crate) grid_strides: [u32; 16],
pub(crate) grid_filling_size: [u32; 16],
pub(crate) grid_scale: [f32; 16],
pub(crate) output_inks: usize,
}
struct FastCube<T, F: ArrayFetch<T>> {
fetch: F,
_phantom: PhantomData<T>,
}
struct ArrayFetchVectorN<'a> {
array: &'a [f32],
x_stride: u32,
y_stride: u32,
z_stride: u32,
output_inks: usize,
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug)]
pub(crate) struct NVector<T, const N: usize> {
pub(crate) v: [T; N],
}
impl<T: Copy, const N: usize> NVector<T, N> {
pub(crate) fn from_slice(v: &[T; N]) -> Self {
Self { v: *v }
}
}
impl<T: Copy, const N: usize> From<T> for NVector<T, N> {
#[inline]
fn from(value: T) -> Self {
Self { v: [value; N] }
}
}
impl<T: Copy + Add<T, Output = T> + Mul<T, Output = T> + MulAdd<T, Output = T>, const N: usize>
FusedMultiplyAdd<NVector<T, N>> for NVector<T, N>
{
#[inline]
fn mla(&self, b: NVector<T, N>, c: NVector<T, N>) -> NVector<T, N> {
Self {
v: from_fn(|i| mlaf(self.v[i], b.v[i], c.v[i])),
}
}
}
impl<
T: Copy + Add<T, Output = T> + Mul<T, Output = T> + MulAdd<T, Output = T> + Neg<Output = T>,
const N: usize,
> FusedMultiplyNegAdd<NVector<T, N>> for NVector<T, N>
{
#[inline]
fn neg_mla(&self, b: NVector<T, N>, c: NVector<T, N>) -> NVector<T, N> {
Self {
v: from_fn(|i| neg_mlaf(self.v[i], b.v[i], c.v[i])),
}
}
}
impl<T: Sub<Output = T> + Default + Copy, const N: usize> Sub<NVector<T, N>> for NVector<T, N> {
type Output = Self;
#[inline]
fn sub(self, rhs: NVector<T, N>) -> Self::Output {
Self {
v: from_fn(|i| self.v[i] - rhs.v[i]),
}
}
}
impl<T: Add<Output = T> + Default + Copy, const N: usize> Add<NVector<T, N>> for NVector<T, N> {
type Output = Self;
#[inline]
fn add(self, rhs: NVector<T, N>) -> Self::Output {
Self {
v: from_fn(|i| self.v[i] + rhs.v[i]),
}
}
}
impl<T: Mul<Output = T> + Default + Copy, const N: usize> Mul<NVector<T, N>> for NVector<T, N> {
type Output = Self;
#[inline]
fn mul(self, rhs: NVector<T, N>) -> Self::Output {
Self {
v: from_fn(|i| self.v[i] * rhs.v[i]),
}
}
}
impl<const N: usize> ArrayFetch<NVector<f32, N>> for ArrayFetchVectorN<'_> {
#[inline(always)]
fn fetch(&self, x: i32, y: i32, z: i32) -> NVector<f32, N> {
let start = (x as u32 * self.x_stride + y as u32 * self.y_stride + z as u32 * self.z_stride)
as usize
* self.output_inks;
let k = &self.array[start..start + N];
NVector::<f32, N>::from_slice(k.try_into().unwrap())
}
}
impl<T, F: ArrayFetch<T>> FastCube<T, F>
where
T: Copy
+ From<f32>
+ Sub<T, Output = T>
+ Mul<T, Output = T>
+ Add<T, Output = T>
+ FusedMultiplyNegAdd<T>
+ FusedMultiplyAdd<T>,
{
#[inline(never)]
fn tetra(&self, src: Vector3i, src_next: Vector3i, w: Vector3f) -> T {
let x = src.v[0];
let y = src.v[1];
let z = src.v[2];
let x_n = src_next.v[0];
let y_n = src_next.v[1];
let z_n = src_next.v[2];
let rx = w.v[0];
let ry = w.v[1];
let rz = w.v[2];
let c0 = self.fetch.fetch(x, y, z);
let c2;
let c1;
let c3;
if rx >= ry {
if ry >= rz {
//rx >= ry && ry >= rz
c1 = self.fetch.fetch(x_n, y, z) - c0;
c2 = self.fetch.fetch(x_n, y_n, z) - self.fetch.fetch(x_n, y, z);
c3 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y_n, z);
} else if rx >= rz {
//rx >= rz && rz >= ry
c1 = self.fetch.fetch(x_n, y, z) - c0;
c2 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y, z_n);
c3 = self.fetch.fetch(x_n, y, z_n) - self.fetch.fetch(x_n, y, z);
} else {
//rz > rx && rx >= ry
c1 = self.fetch.fetch(x_n, y, z_n) - self.fetch.fetch(x, y, z_n);
c2 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y, z_n);
c3 = self.fetch.fetch(x, y, z_n) - c0;
}
} else if rx >= rz {
//ry > rx && rx >= rz
c1 = self.fetch.fetch(x_n, y_n, z) - self.fetch.fetch(x, y_n, z);
c2 = self.fetch.fetch(x, y_n, z) - c0;
c3 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y_n, z);
} else if ry >= rz {
//ry >= rz && rz > rx
c1 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x, y_n, z_n);
c2 = self.fetch.fetch(x, y_n, z) - c0;
c3 = self.fetch.fetch(x, y_n, z_n) - self.fetch.fetch(x, y_n, z);
} else {
//rz > ry && ry > rx
c1 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x, y_n, z_n);
c2 = self.fetch.fetch(x, y_n, z_n) - self.fetch.fetch(x, y, z_n);
c3 = self.fetch.fetch(x, y, z_n) - c0;
}
let s0 = c0.mla(c1, T::from(rx));
let s1 = s0.mla(c2, T::from(ry));
s1.mla(c3, T::from(rz))
}
}
impl MultidimensionalLut {
pub(crate) fn new(grid_size: [u8; 16], input_inks: usize, output_inks: usize) -> Self {
assert!(input_inks <= 16);
let mut grid_strides = [1u32; 16];
let mut grid_filling_size = [1u32; 16];
for (ink, dst_stride) in grid_strides.iter_mut().take(input_inks - 1).enumerate() {
let mut stride = 1u32;
let how_many = input_inks.saturating_sub(ink).saturating_sub(1);
for &grid_stride in grid_size.iter().take(how_many) {
stride *= grid_stride as u32;
}
*dst_stride = stride;
}
for (ink, dst_stride) in grid_filling_size.iter_mut().take(input_inks).enumerate() {
let mut stride = output_inks as u32;
let how_many = input_inks.saturating_sub(ink).saturating_sub(1);
for &grid_stride in grid_size.iter().take(how_many) {
stride *= grid_stride as u32;
}
*dst_stride = stride;
}
let mut grid_strides_f = [0f32; 16];
for (dst, src) in grid_strides_f
.iter_mut()
.zip(grid_size.iter())
.take(input_inks)
{
*dst = (*src - 1) as f32;
}
Self {
grid_strides,
grid_scale: grid_strides_f,
grid_filling_size,
output_inks,
}
}
}
pub(crate) fn linear_4i_vec3f_direct<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
lx: f32,
ly: f32,
lz: f32,
lw: f32,
) -> NVector<f32, N> {
let lin_x = lx.max(0.0).min(1.0);
let lin_y = ly.max(0.0).min(1.0);
let lin_z = lz.max(0.0).min(1.0);
let lin_w = lw.max(0.0).min(1.0);
let scale_x = lut.grid_scale[0];
let scale_y = lut.grid_scale[1];
let scale_z = lut.grid_scale[2];
let scale_w = lut.grid_scale[3];
let lx = lin_x * scale_x;
let ly = lin_y * scale_y;
let lz = lin_z * scale_z;
let lw = lin_w * scale_w;
let x = lx.floor() as i32;
let y = ly.floor() as i32;
let z = lz.floor() as i32;
let w = lw.floor() as i32;
let src_x = Vector3i { v: [x, y, z] };
let x_n = lx.ceil() as i32;
let y_n = ly.ceil() as i32;
let z_n = lz.ceil() as i32;
let w_n = lw.ceil() as i32;
let src_next = Vector3i { v: [x_n, y_n, z_n] };
let x_w = lx - x as f32;
let y_w = ly - y as f32;
let z_w = lz - z as f32;
let w_w = lw - w as f32;
let weights = Vector3f { v: [x_w, y_w, z_w] };
let cube0 = &arr[(w as usize * lut.grid_filling_size[3] as usize)..];
let cube1 = &arr[(w_n as usize * lut.grid_filling_size[3] as usize)..];
let fast_cube0 = FastCube {
fetch: ArrayFetchVectorN {
array: cube0,
x_stride: lut.grid_strides[0],
y_stride: lut.grid_strides[1],
z_stride: lut.grid_strides[2],
output_inks: lut.output_inks,
},
_phantom: PhantomData,
};
let fast_cube1 = FastCube {
fetch: ArrayFetchVectorN {
array: cube1,
x_stride: lut.grid_strides[0],
y_stride: lut.grid_strides[1],
z_stride: lut.grid_strides[2],
output_inks: lut.output_inks,
},
_phantom: PhantomData,
};
let w0 = fast_cube0.tetra(src_x, src_next, weights);
let w1 = fast_cube1.tetra(src_x, src_next, weights);
lerp(w0, w1, NVector::<f32, N>::from(w_w))
}
pub(crate) fn linear_3i_vec3f_direct<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
linear_3i_vec3f(lut, arr, inputs[0], inputs[1], inputs[2])
}
fn linear_3i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
x: f32,
y: f32,
z: f32,
) -> NVector<f32, N> {
let lin_x = x.max(0.0).min(1.0);
let lin_y = y.max(0.0).min(1.0);
let lin_z = z.max(0.0).min(1.0);
let scale_x = lut.grid_scale[0];
let scale_y = lut.grid_scale[1];
let scale_z = lut.grid_scale[2];
let lx = lin_x * scale_x;
let ly = lin_y * scale_y;
let lz = lin_z * scale_z;
let x = lx.floor() as i32;
let y = ly.floor() as i32;
let z = lz.floor() as i32;
let src_x = Vector3i { v: [x, y, z] };
let x_n = lx.ceil() as i32;
let y_n = ly.ceil() as i32;
let z_n = lz.ceil() as i32;
let src_next = Vector3i { v: [x_n, y_n, z_n] };
let x_w = lx - x as f32;
let y_w = ly - y as f32;
let z_w = lz - z as f32;
let weights = Vector3f { v: [x_w, y_w, z_w] };
let fast_cube = FastCube {
fetch: ArrayFetchVectorN {
array: arr,
x_stride: lut.grid_strides[0],
y_stride: lut.grid_strides[1],
z_stride: lut.grid_strides[2],
output_inks: lut.output_inks,
},
_phantom: PhantomData,
};
fast_cube.tetra(src_x, src_next, weights)
}
pub(crate) fn linear_1i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let lin_x = inputs[0].max(0.0).min(1.0);
let scale_x = lut.grid_scale[0];
let lx = lin_x * scale_x;
let x = lx.floor() as i32;
let x_n = lx.ceil() as i32;
let x_w = lx - x as f32;
let x_stride = lut.grid_strides[0];
let offset = |xi: i32| -> usize { (xi as u32 * x_stride) as usize * lut.output_inks };
// Sample 2 corners
let a = NVector::<f32, N>::from_slice(&arr[offset(x)..][..N].try_into().unwrap());
let b = NVector::<f32, N>::from_slice(&arr[offset(x_n)..][..N].try_into().unwrap());
a * NVector::<f32, N>::from(1.0 - x_w) + b * NVector::<f32, N>::from(x_w)
}
pub(crate) fn linear_2i_vec3f_direct<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
linear_2i_vec3f(lut, arr, inputs[0], inputs[1])
}
fn linear_2i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
x: f32,
y: f32,
) -> NVector<f32, N> {
let lin_x = x.max(0.0).min(1.0);
let lin_y = y.max(0.0).min(1.0);
let scale_x = lut.grid_scale[0];
let scale_y = lut.grid_scale[1];
let lx = lin_x * scale_x;
let ly = lin_y * scale_y;
let x = lx.floor() as i32;
let y = ly.floor() as i32;
let x_n = lx.ceil() as i32;
let y_n = ly.ceil() as i32;
let x_w = lx - x as f32;
let y_w = ly - y as f32;
let x_stride = lut.grid_strides[0];
let y_stride = lut.grid_strides[1];
let offset = |xi: i32, yi: i32| -> usize {
(xi as u32 * x_stride + yi as u32 * y_stride) as usize * lut.output_inks
};
// Sample 4 corners
let a = NVector::<f32, N>::from_slice(&arr[offset(x, y)..][..N].try_into().unwrap());
let b = NVector::<f32, N>::from_slice(&arr[offset(x_n, y)..][..N].try_into().unwrap());
let c = NVector::<f32, N>::from_slice(&arr[offset(x, y_n)..][..N].try_into().unwrap());
let d = NVector::<f32, N>::from_slice(&arr[offset(x_n, y_n)..][..N].try_into().unwrap());
let ab = a * NVector::<f32, N>::from(1.0 - x_w) + b * NVector::<f32, N>::from(x_w);
let cd = c * NVector::<f32, N>::from(1.0 - x_w) + d * NVector::<f32, N>::from(x_w);
ab * NVector::<f32, N>::from(1.0 - y_w) + cd * NVector::<f32, N>::from(y_w)
}
pub(crate) fn linear_4i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
linear_4i_vec3f_direct(lut, arr, inputs[0], inputs[1], inputs[2], inputs[3])
}
type FHandle<const N: usize> = fn(&MultidimensionalLut, &[f32], &[f32]) -> NVector<f32, N>;
#[inline(never)]
pub(crate) fn linear_n_i_vec3f<
const N: usize,
const I: usize,
Handle: Fn(&MultidimensionalLut, &[f32], &[f32]) -> NVector<f32, N>,
>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
handle: Handle,
) -> NVector<f32, N> {
let lin_w = inputs[I];
let w_c = lin_w.max(0.).min(1.);
let scale_p = lut.grid_scale[I];
let wf = w_c * scale_p;
let w0 = wf.min(scale_p) as usize;
let w1 = (wf + 1.).min(scale_p) as usize;
let w = wf - w0 as f32;
let cube0 = &arr[(w0 * lut.grid_filling_size[I] as usize)..];
let cube1 = &arr[(w1 * lut.grid_filling_size[I] as usize)..];
let inputs_sliced = &inputs[0..I];
let w0 = handle(lut, cube0, inputs_sliced);
let w1 = handle(lut, cube1, inputs_sliced);
lerp(w0, w1, NVector::<f32, N>::from(w))
}
#[inline(never)]
pub(crate) fn linear_5i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let lin_w = inputs[4];
let w_c = lin_w.max(0.).min(1.);
let scale_p = lut.grid_scale[4];
let wf = w_c * scale_p;
let w0 = wf.min(scale_p) as usize;
let w1 = (wf + 1.).min(scale_p) as usize;
let w = wf - w0 as f32;
let cube0 = &arr[(w0 * lut.grid_filling_size[4] as usize)..];
let cube1 = &arr[(w1 * lut.grid_filling_size[4] as usize)..];
let w0 = linear_4i_vec3f_direct(lut, cube0, inputs[0], inputs[1], inputs[2], inputs[3]);
let w1 = linear_4i_vec3f_direct(lut, cube1, inputs[0], inputs[1], inputs[2], inputs[3]);
lerp(w0, w1, NVector::<f32, N>::from(w))
}
#[inline(never)]
pub(crate) fn linear_6i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_5i_vec3f::<N>;
linear_n_i_vec3f::<N, 5, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_7i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_6i_vec3f::<N>;
linear_n_i_vec3f::<N, 6, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_8i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_7i_vec3f::<N>;
linear_n_i_vec3f::<N, 7, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_9i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_8i_vec3f::<N>;
linear_n_i_vec3f::<N, 8, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_10i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_9i_vec3f::<N>;
linear_n_i_vec3f::<N, 9, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_11i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_10i_vec3f::<N>;
linear_n_i_vec3f::<N, 10, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_12i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_11i_vec3f::<N>;
linear_n_i_vec3f::<N, 11, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_13i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_12i_vec3f::<N>;
linear_n_i_vec3f::<N, 12, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_14i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_13i_vec3f::<N>;
linear_n_i_vec3f::<N, 13, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn linear_15i_vec3f<const N: usize>(
lut: &MultidimensionalLut,
arr: &[f32],
inputs: &[f32],
) -> NVector<f32, N> {
let f = linear_14i_vec3f::<N>;
linear_n_i_vec3f::<N, 14, FHandle<N>>(lut, arr, inputs, f)
}
#[inline(never)]
pub(crate) fn tetra_3i_to_any_vec(
lut: &MultidimensionalLut,
arr: &[f32],
x: f32,
y: f32,
z: f32,
dst: &mut [f32],
inks: usize,
) {
match inks {
1 => {
let vec3 = linear_3i_vec3f::<1>(lut, arr, x, y, z);
dst[0] = vec3.v[0];
}
2 => {
let vec3 = linear_3i_vec3f::<2>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
3 => {
let vec3 = linear_3i_vec3f::<3>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
4 => {
let vec3 = linear_3i_vec3f::<4>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
5 => {
let vec3 = linear_3i_vec3f::<5>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
6 => {
let vec3 = linear_3i_vec3f::<6>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
7 => {
let vec3 = linear_3i_vec3f::<7>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
8 => {
let vec3 = linear_3i_vec3f::<8>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
9 => {
let vec3 = linear_3i_vec3f::<9>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
10 => {
let vec3 = linear_3i_vec3f::<10>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
11 => {
let vec3 = linear_3i_vec3f::<11>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
12 => {
let vec3 = linear_3i_vec3f::<12>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
13 => {
let vec3 = linear_3i_vec3f::<13>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
14 => {
let vec3 = linear_3i_vec3f::<14>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
15 => {
let vec3 = linear_3i_vec3f::<15>(lut, arr, x, y, z);
for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
*dst = *src;
}
}
_ => unreachable!(),
}
}

View File

@@ -1,188 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::LutBarycentricReduction;
use crate::conversions::katana::{
CopyAlphaStage, InjectAlphaStage, Katana, KatanaInitialStage, KatanaIntermediateStage,
KatanaPostFinalizationStage, KatanaStageLabToXyz, KatanaStageXyzToLab,
katana_create_rgb_lin_lut, katana_input_make_lut_nx3, katana_multi_dimensional_3xn_to_device,
katana_multi_dimensional_nx3_to_pcs, katana_output_make_lut_3xn, katana_pcs_lab_v2_to_v4,
katana_pcs_lab_v4_to_v2, katana_prepare_inverse_lut_rgb_xyz,
};
use crate::{
CmsError, ColorProfile, DataColorSpace, GammaLutInterpolate, Layout, LutWarehouse,
PointeeSizeExpressible, TransformExecutor, TransformOptions,
};
use num_traits::AsPrimitive;
pub(crate) fn do_any_to_any<
T: Copy
+ Default
+ AsPrimitive<f32>
+ Send
+ Sync
+ AsPrimitive<usize>
+ PointeeSizeExpressible
+ GammaLutInterpolate,
const BIT_DEPTH: usize,
const LINEAR_CAP: usize,
const GAMMA_LUT: usize,
>(
src_layout: Layout,
source: &ColorProfile,
dst_layout: Layout,
dest: &ColorProfile,
options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>,
{
let mut stages: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>> = Vec::new();
let initial_stage: Box<dyn KatanaInitialStage<f32, T> + Send + Sync> = match source
.is_matrix_shaper()
{
true => {
let state =
katana_create_rgb_lin_lut::<T, BIT_DEPTH, LINEAR_CAP>(src_layout, source, options)?;
stages.extend(state.stages);
state.initial_stage
}
false => match source.get_device_to_pcs(options.rendering_intent).ok_or(
CmsError::UnsupportedLutRenderingIntent(source.rendering_intent),
)? {
LutWarehouse::Lut(lut) => katana_input_make_lut_nx3::<T>(
src_layout,
src_layout.channels(),
lut,
options,
source.pcs,
BIT_DEPTH,
)?,
LutWarehouse::Multidimensional(mab) => katana_multi_dimensional_nx3_to_pcs::<T>(
src_layout, mab, options, source.pcs, BIT_DEPTH,
)?,
},
};
stages.push(katana_pcs_lab_v2_to_v4(source));
if source.pcs == DataColorSpace::Lab {
stages.push(Box::new(KatanaStageLabToXyz::default()));
}
if dest.pcs == DataColorSpace::Lab {
stages.push(Box::new(KatanaStageXyzToLab::default()));
}
stages.push(katana_pcs_lab_v4_to_v2(dest));
let final_stage = if dest.has_pcs_to_device_lut() {
let pcs_to_device = dest
.get_pcs_to_device(options.rendering_intent)
.ok_or(CmsError::UnsupportedProfileConnection)?;
match pcs_to_device {
LutWarehouse::Lut(lut) => katana_output_make_lut_3xn::<T>(
dst_layout,
lut,
options,
dest.color_space,
BIT_DEPTH,
)?,
LutWarehouse::Multidimensional(mab) => katana_multi_dimensional_3xn_to_device::<T>(
dst_layout, mab, options, dest.pcs, BIT_DEPTH,
)?,
}
} else if dest.is_matrix_shaper() {
let state = katana_prepare_inverse_lut_rgb_xyz::<T, BIT_DEPTH, GAMMA_LUT>(
dest, dst_layout, options,
)?;
stages.extend(state.stages);
state.final_stage
} else {
return Err(CmsError::UnsupportedProfileConnection);
};
let mut post_finalization: Vec<Box<dyn KatanaPostFinalizationStage<T> + Send + Sync>> =
Vec::new();
if let Some(stage) =
prepare_alpha_finalizer::<T>(src_layout, source, dst_layout, dest, BIT_DEPTH)
{
post_finalization.push(stage);
}
Ok(Box::new(Katana::<f32, T> {
initial_stage,
final_stage,
stages,
post_finalization,
}))
}
pub(crate) fn prepare_alpha_finalizer<
T: Copy
+ Default
+ AsPrimitive<f32>
+ Send
+ Sync
+ AsPrimitive<usize>
+ PointeeSizeExpressible
+ GammaLutInterpolate,
>(
src_layout: Layout,
source: &ColorProfile,
dst_layout: Layout,
dest: &ColorProfile,
bit_depth: usize,
) -> Option<Box<dyn KatanaPostFinalizationStage<T> + Send + Sync>>
where
f32: AsPrimitive<T>,
{
if (dst_layout == Layout::GrayAlpha && dest.color_space == DataColorSpace::Gray)
|| (dst_layout == Layout::Rgba || dest.color_space == DataColorSpace::Rgb)
{
return if (src_layout == Layout::GrayAlpha && source.color_space == DataColorSpace::Gray)
|| (src_layout == Layout::Rgba || source.color_space == DataColorSpace::Rgb)
{
Some(Box::new(CopyAlphaStage {
src_layout,
dst_layout,
target_color_space: dest.color_space,
_phantom: Default::default(),
}))
} else {
Some(Box::new(InjectAlphaStage {
dst_layout,
target_color_space: dest.color_space,
_phantom: Default::default(),
bit_depth,
}))
};
}
None
}

View File

@@ -1,66 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
mod bpc;
mod gray2rgb;
mod gray2rgb_extended;
mod interpolator;
mod katana;
mod lut3x3;
mod lut3x4;
mod lut4;
mod lut_transforms;
mod mab;
mod mab4x3;
mod mba3x4;
mod md_lut;
mod md_luts_factory;
mod prelude_lut_xyz_rgb;
mod rgb2gray;
mod rgb2gray_extended;
mod rgb_xyz_factory;
mod rgbxyz;
mod rgbxyz_fixed;
mod rgbxyz_float;
mod transform_lut3_to_3;
mod transform_lut3_to_4;
mod transform_lut4_to_3;
mod xyz_lab;
pub(crate) use gray2rgb::{make_gray_to_unfused, make_gray_to_x};
pub(crate) use gray2rgb_extended::{make_gray_to_one_trc_extended, make_gray_to_rgb_extended};
pub(crate) use interpolator::LutBarycentricReduction;
pub(crate) use lut_transforms::make_lut_transform;
pub(crate) use rgb_xyz_factory::{RgbXyzFactory, RgbXyzFactoryOpt};
pub(crate) use rgb2gray::{ToneReproductionRgbToGray, make_rgb_to_gray};
pub(crate) use rgb2gray_extended::make_rgb_to_gray_extended;
pub(crate) use rgbxyz::{TransformMatrixShaper, TransformMatrixShaperOptimized};
pub(crate) use rgbxyz_float::{
TransformShaperFloatInOut, TransformShaperRgbFloat, make_rgb_xyz_rgb_transform_float,
make_rgb_xyz_rgb_transform_float_in_out,
};

View File

@@ -1,328 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 4/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::lut3x4::create_lut3_samples;
use crate::err::try_vec;
use crate::mlaf::mlaf;
use crate::trc::ToneCurveEvaluator;
use crate::{
CmsError, ColorProfile, GammaLutInterpolate, InPlaceStage, Matrix3f, PointeeSizeExpressible,
RenderingIntent, Rgb, TransformOptions, filmlike_clip,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) struct XyzToRgbStage<T: Clone> {
pub(crate) r_gamma: Box<[T; 65536]>,
pub(crate) g_gamma: Box<[T; 65536]>,
pub(crate) b_gamma: Box<[T; 65536]>,
pub(crate) matrices: Vec<Matrix3f>,
pub(crate) intent: RenderingIntent,
pub(crate) bit_depth: usize,
pub(crate) gamma_lut: usize,
}
impl<T: Clone + AsPrimitive<f32>> InPlaceStage for XyzToRgbStage<T> {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
assert!(self.bit_depth > 0);
if !self.matrices.is_empty() {
let m = self.matrices[0];
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
}
}
for m in self.matrices.iter().skip(1) {
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
}
}
let max_colors = (1 << self.bit_depth) - 1;
let color_scale = 1f32 / max_colors as f32;
let lut_cap = (self.gamma_lut - 1) as f32;
if self.intent != RenderingIntent::AbsoluteColorimetric {
for dst in dst.chunks_exact_mut(3) {
let mut rgb = Rgb::new(dst[0], dst[1], dst[2]);
if rgb.is_out_of_gamut() {
rgb = filmlike_clip(rgb);
}
let r = mlaf(0.5f32, rgb.r, lut_cap).min(lut_cap).max(0f32) as u16;
let g = mlaf(0.5f32, rgb.g, lut_cap).min(lut_cap).max(0f32) as u16;
let b = mlaf(0.5f32, rgb.b, lut_cap).min(lut_cap).max(0f32) as u16;
dst[0] = self.r_gamma[r as usize].as_() * color_scale;
dst[1] = self.g_gamma[g as usize].as_() * color_scale;
dst[2] = self.b_gamma[b as usize].as_() * color_scale;
}
} else {
for dst in dst.chunks_exact_mut(3) {
let rgb = Rgb::new(dst[0], dst[1], dst[2]);
let r = mlaf(0.5f32, rgb.r, lut_cap).min(lut_cap).max(0f32) as u16;
let g = mlaf(0.5f32, rgb.g, lut_cap).min(lut_cap).max(0f32) as u16;
let b = mlaf(0.5f32, rgb.b, lut_cap).min(lut_cap).max(0f32) as u16;
dst[0] = self.r_gamma[r as usize].as_() * color_scale;
dst[1] = self.g_gamma[g as usize].as_() * color_scale;
dst[2] = self.b_gamma[b as usize].as_() * color_scale;
}
}
Ok(())
}
}
pub(crate) struct XyzToRgbStageExtended<T: Clone> {
pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator>,
pub(crate) matrices: Vec<Matrix3f>,
pub(crate) phantom_data: PhantomData<T>,
}
impl<T: Clone + AsPrimitive<f32>> InPlaceStage for XyzToRgbStageExtended<T> {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
if !self.matrices.is_empty() {
let m = self.matrices[0];
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
}
}
for m in self.matrices.iter().skip(1) {
for dst in dst.chunks_exact_mut(3) {
let x = dst[0];
let y = dst[1];
let z = dst[2];
dst[0] = mlaf(mlaf(x * m.v[0][0], y, m.v[0][1]), z, m.v[0][2]);
dst[1] = mlaf(mlaf(x * m.v[1][0], y, m.v[1][1]), z, m.v[1][2]);
dst[2] = mlaf(mlaf(x * m.v[2][0], y, m.v[2][1]), z, m.v[2][2]);
}
}
for dst in dst.chunks_exact_mut(3) {
let mut rgb = Rgb::new(dst[0], dst[1], dst[2]);
rgb = self.gamma_evaluator.evaluate_tristimulus(rgb);
dst[0] = rgb.r.as_();
dst[1] = rgb.g.as_();
dst[2] = rgb.b.as_();
}
Ok(())
}
}
struct RgbLinearizationStage<T: Clone, const LINEAR_CAP: usize, const SAMPLES: usize> {
r_lin: Box<[f32; LINEAR_CAP]>,
g_lin: Box<[f32; LINEAR_CAP]>,
b_lin: Box<[f32; LINEAR_CAP]>,
_phantom: PhantomData<T>,
bit_depth: usize,
}
impl<
T: Clone + AsPrimitive<usize> + PointeeSizeExpressible,
const LINEAR_CAP: usize,
const SAMPLES: usize,
> RgbLinearizationStage<T, LINEAR_CAP, SAMPLES>
{
fn transform(&self, src: &[T], dst: &mut [f32]) -> Result<(), CmsError> {
if src.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % 3 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let scale = if T::FINITE {
((1 << self.bit_depth) - 1) as f32 / (SAMPLES as f32 - 1f32)
} else {
(T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 / (SAMPLES as f32 - 1f32)
};
let capped_value = if T::FINITE {
(1 << self.bit_depth) - 1
} else {
T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
};
for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
let j_r = src[0].as_() as f32 * scale;
let j_g = src[1].as_() as f32 * scale;
let j_b = src[2].as_() as f32 * scale;
dst[0] = self.r_lin[(j_r.round().max(0.0).min(capped_value as f32) as u16) as usize];
dst[1] = self.g_lin[(j_g.round().max(0.0).min(capped_value as f32) as u16) as usize];
dst[2] = self.b_lin[(j_b.round().max(0.0).min(capped_value as f32) as u16) as usize];
}
Ok(())
}
}
pub(crate) fn create_rgb_lin_lut<
T: Copy + Default + AsPrimitive<f32> + Send + Sync + AsPrimitive<usize> + PointeeSizeExpressible,
const BIT_DEPTH: usize,
const LINEAR_CAP: usize,
const GRID_SIZE: usize,
>(
source: &ColorProfile,
opts: TransformOptions,
) -> Result<Vec<f32>, CmsError>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
let lut_origins = create_lut3_samples::<T, GRID_SIZE>();
let lin_r =
source.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
let lin_g =
source.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
let lin_b =
source.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>(opts.allow_use_cicp_transfer)?;
let lin_stage = RgbLinearizationStage::<T, LINEAR_CAP, GRID_SIZE> {
r_lin: lin_r,
g_lin: lin_g,
b_lin: lin_b,
_phantom: PhantomData,
bit_depth: BIT_DEPTH,
};
let mut lut = try_vec![0f32; lut_origins.len()];
lin_stage.transform(&lut_origins, &mut lut)?;
let xyz_to_rgb = source.rgb_to_xyz_matrix();
let matrices = vec![
xyz_to_rgb.to_f32(),
Matrix3f {
v: [
[32768.0 / 65535.0, 0.0, 0.0],
[0.0, 32768.0 / 65535.0, 0.0],
[0.0, 0.0, 32768.0 / 65535.0],
],
},
];
let matrix_stage = crate::conversions::lut_transforms::MatrixStage { matrices };
matrix_stage.transform(&mut lut)?;
Ok(lut)
}
pub(crate) fn prepare_inverse_lut_rgb_xyz<
T: Copy
+ Default
+ AsPrimitive<f32>
+ Send
+ Sync
+ AsPrimitive<usize>
+ PointeeSizeExpressible
+ GammaLutInterpolate,
const BIT_DEPTH: usize,
const GAMMA_LUT: usize,
>(
dest: &ColorProfile,
lut: &mut [f32],
options: TransformOptions,
) -> Result<(), CmsError>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
{
if !T::FINITE {
if let Some(extended_gamma) = dest.try_extended_gamma_evaluator() {
let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
let mut matrices = vec![Matrix3f {
v: [
[65535.0 / 32768.0, 0.0, 0.0],
[0.0, 65535.0 / 32768.0, 0.0],
[0.0, 0.0, 65535.0 / 32768.0],
],
}];
matrices.push(xyz_to_rgb.to_f32());
let xyz_to_rgb_stage = XyzToRgbStageExtended::<T> {
gamma_evaluator: extended_gamma,
matrices,
phantom_data: PhantomData,
};
xyz_to_rgb_stage.transform(lut)?;
return Ok(());
}
}
let gamma_map_r = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
&dest.red_trc,
options.allow_use_cicp_transfer,
)?;
let gamma_map_g = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
&dest.green_trc,
options.allow_use_cicp_transfer,
)?;
let gamma_map_b = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
&dest.blue_trc,
options.allow_use_cicp_transfer,
)?;
let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
let mut matrices = vec![Matrix3f {
v: [
[65535.0 / 32768.0, 0.0, 0.0],
[0.0, 65535.0 / 32768.0, 0.0],
[0.0, 0.0, 65535.0 / 32768.0],
],
}];
matrices.push(xyz_to_rgb.to_f32());
let xyz_to_rgb_stage = XyzToRgbStage::<T> {
r_gamma: gamma_map_r,
g_gamma: gamma_map_g,
b_gamma: gamma_map_b,
matrices,
intent: options.rendering_intent,
gamma_lut: GAMMA_LUT,
bit_depth: BIT_DEPTH,
};
xyz_to_rgb_stage.transform(lut)?;
Ok(())
}

View File

@@ -1,189 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::mlaf::mlaf;
use crate::transform::PointeeSizeExpressible;
use crate::{CmsError, Layout, TransformExecutor, Vector3f};
use num_traits::AsPrimitive;
#[derive(Clone)]
pub(crate) struct ToneReproductionRgbToGray<T, const BUCKET: usize> {
pub(crate) r_linear: Box<[f32; BUCKET]>,
pub(crate) g_linear: Box<[f32; BUCKET]>,
pub(crate) b_linear: Box<[f32; BUCKET]>,
pub(crate) gray_gamma: Box<[T; 65536]>,
}
#[derive(Clone)]
struct TransformRgbToGrayExecutor<
T,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const BUCKET: usize,
> {
trc_box: ToneReproductionRgbToGray<T, BUCKET>,
weights: Vector3f,
bit_depth: usize,
gamma_lut: usize,
}
pub(crate) fn make_rgb_to_gray<
T: Copy + Default + PointeeSizeExpressible + Send + Sync + 'static,
const BUCKET: usize,
>(
src_layout: Layout,
dst_layout: Layout,
trc: ToneReproductionRgbToGray<T, BUCKET>,
weights: Vector3f,
gamma_lut: usize,
bit_depth: usize,
) -> Box<dyn TransformExecutor<T> + Send + Sync>
where
u32: AsPrimitive<T>,
{
match src_layout {
Layout::Rgb => match dst_layout {
Layout::Rgb => unreachable!(),
Layout::Rgba => unreachable!(),
Layout::Gray => Box::new(TransformRgbToGrayExecutor::<
T,
{ Layout::Rgb as u8 },
{ Layout::Gray as u8 },
BUCKET,
> {
trc_box: trc,
weights,
bit_depth,
gamma_lut,
}),
Layout::GrayAlpha => Box::new(TransformRgbToGrayExecutor::<
T,
{ Layout::Rgb as u8 },
{ Layout::GrayAlpha as u8 },
BUCKET,
> {
trc_box: trc,
weights,
bit_depth,
gamma_lut,
}),
_ => unreachable!(),
},
Layout::Rgba => match dst_layout {
Layout::Rgb => unreachable!(),
Layout::Rgba => unreachable!(),
Layout::Gray => Box::new(TransformRgbToGrayExecutor::<
T,
{ Layout::Rgba as u8 },
{ Layout::Gray as u8 },
BUCKET,
> {
trc_box: trc,
weights,
bit_depth,
gamma_lut,
}),
Layout::GrayAlpha => Box::new(TransformRgbToGrayExecutor::<
T,
{ Layout::Rgba as u8 },
{ Layout::GrayAlpha as u8 },
BUCKET,
> {
trc_box: trc,
weights,
bit_depth,
gamma_lut,
}),
_ => unreachable!(),
},
Layout::Gray => unreachable!(),
Layout::GrayAlpha => unreachable!(),
_ => unreachable!(),
}
}
impl<
T: Copy + Default + PointeeSizeExpressible + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const BUCKET: usize,
> TransformExecutor<T> for TransformRgbToGrayExecutor<T, SRC_LAYOUT, DST_LAYOUT, BUCKET>
where
u32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let scale_value = (self.gamma_lut - 1) as f32;
let max_value = ((1u32 << self.bit_depth) - 1).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let r = self.trc_box.r_linear[src[src_cn.r_i()]._as_usize()];
let g = self.trc_box.g_linear[src[src_cn.g_i()]._as_usize()];
let b = self.trc_box.b_linear[src[src_cn.b_i()]._as_usize()];
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_value
};
let grey = mlaf(
0.5,
mlaf(
mlaf(self.weights.v[0] * r, self.weights.v[1], g),
self.weights.v[2],
b,
)
.min(1.)
.max(0.),
scale_value,
);
dst[0] = self.trc_box.gray_gamma[(grey as u16) as usize];
if dst_channels == 2 {
dst[1] = a;
}
}
Ok(())
}
}

View File

@@ -1,181 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::mlaf::mlaf;
use crate::transform::PointeeSizeExpressible;
use crate::trc::ToneCurveEvaluator;
use crate::{CmsError, Layout, Rgb, TransformExecutor, Vector3f};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
struct TransformRgbToGrayExtendedExecutor<T, const SRC_LAYOUT: u8, const DST_LAYOUT: u8> {
linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
weights: Vector3f,
_phantom: PhantomData<T>,
bit_depth: usize,
}
pub(crate) fn make_rgb_to_gray_extended<
T: Copy + Default + PointeeSizeExpressible + Send + Sync + 'static + AsPrimitive<f32>,
>(
src_layout: Layout,
dst_layout: Layout,
linear_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
gamma_eval: Box<dyn ToneCurveEvaluator + Send + Sync>,
weights: Vector3f,
bit_depth: usize,
) -> Box<dyn TransformExecutor<T> + Send + Sync>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
match src_layout {
Layout::Rgb => match dst_layout {
Layout::Rgb => unreachable!(),
Layout::Rgba => unreachable!(),
Layout::Gray => Box::new(TransformRgbToGrayExtendedExecutor::<
T,
{ Layout::Rgb as u8 },
{ Layout::Gray as u8 },
> {
linear_eval,
gamma_eval,
weights,
_phantom: PhantomData,
bit_depth,
}),
Layout::GrayAlpha => Box::new(TransformRgbToGrayExtendedExecutor::<
T,
{ Layout::Rgb as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
weights,
_phantom: PhantomData,
bit_depth,
}),
_ => unreachable!(),
},
Layout::Rgba => match dst_layout {
Layout::Rgb => unreachable!(),
Layout::Rgba => unreachable!(),
Layout::Gray => Box::new(TransformRgbToGrayExtendedExecutor::<
T,
{ Layout::Rgba as u8 },
{ Layout::Gray as u8 },
> {
linear_eval,
gamma_eval,
weights,
_phantom: PhantomData,
bit_depth,
}),
Layout::GrayAlpha => Box::new(TransformRgbToGrayExtendedExecutor::<
T,
{ Layout::Rgba as u8 },
{ Layout::GrayAlpha as u8 },
> {
linear_eval,
gamma_eval,
weights,
_phantom: PhantomData,
bit_depth,
}),
_ => unreachable!(),
},
Layout::Gray => unreachable!(),
Layout::GrayAlpha => unreachable!(),
_ => unreachable!(),
}
}
impl<
T: Copy + Default + PointeeSizeExpressible + 'static + AsPrimitive<f32>,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
> TransformExecutor<T> for TransformRgbToGrayExtendedExecutor<T, SRC_LAYOUT, DST_LAYOUT>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let max_value = ((1u32 << self.bit_depth) - 1).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let in_tristimulus = Rgb::<f32>::new(
src[src_cn.r_i()].as_(),
src[src_cn.g_i()].as_(),
src[src_cn.b_i()].as_(),
);
let lin_tristimulus = self.linear_eval.evaluate_tristimulus(in_tristimulus);
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_value
};
let grey = mlaf(
mlaf(
self.weights.v[0] * lin_tristimulus.r,
self.weights.v[1],
lin_tristimulus.g,
),
self.weights.v[2],
lin_tristimulus.b,
)
.min(1.)
.max(0.);
let gamma_value = self.gamma_eval.evaluate_value(grey);
dst[0] = gamma_value.as_();
if dst_channels == 2 {
dst[1] = a;
}
}
Ok(())
}
}

View File

@@ -1,395 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 4/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::TransformMatrixShaper;
use crate::conversions::rgbxyz::{
TransformMatrixShaperOptimized, make_rgb_xyz_rgb_transform, make_rgb_xyz_rgb_transform_opt,
};
use crate::conversions::rgbxyz_fixed::{make_rgb_xyz_q2_13, make_rgb_xyz_q2_13_opt};
use crate::{CmsError, Layout, TransformExecutor, TransformOptions};
use num_traits::AsPrimitive;
const FIXED_POINT_SCALE: i32 = 13; // Q2.13;
pub(crate) trait RgbXyzFactory<T: Clone + AsPrimitive<usize> + Default> {
fn make_transform<const LINEAR_CAP: usize, const GAMMA_LUT: usize, const BIT_DEPTH: usize>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaper<T, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>;
}
pub(crate) trait RgbXyzFactoryOpt<T: Clone + AsPrimitive<usize> + Default> {
fn make_optimized_transform<
const LINEAR_CAP: usize,
const GAMMA_LUT: usize,
const BIT_DEPTH: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaperOptimized<T, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>;
}
impl RgbXyzFactory<u16> for u16 {
fn make_transform<const LINEAR_CAP: usize, const GAMMA_LUT: usize, const BIT_DEPTH: usize>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaper<u16, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<u16> + Send + Sync>, CmsError> {
if BIT_DEPTH < 16 && transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_avx2;
if std::arch::is_x86_feature_detected!("avx2") {
return make_rgb_xyz_q2_13_transform_avx2::<u16, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_sse_41;
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_q2_13_transform_sse_41::<u16, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
{
return make_rgb_xyz_q2_13::<u16, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
make_rgb_xyz_rgb_transform::<u16, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
}
}
impl RgbXyzFactory<f32> for f32 {
fn make_transform<const LINEAR_CAP: usize, const GAMMA_LUT: usize, const BIT_DEPTH: usize>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaper<f32, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<f32> + Send + Sync>, CmsError> {
if transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_avx2;
if std::arch::is_x86_feature_detected!("avx2") {
return make_rgb_xyz_q2_13_transform_avx2::<f32, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_sse_41;
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_q2_13_transform_sse_41::<f32, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
{
return make_rgb_xyz_q2_13::<f32, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
make_rgb_xyz_rgb_transform::<f32, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
}
}
impl RgbXyzFactory<f64> for f64 {
fn make_transform<const LINEAR_CAP: usize, const GAMMA_LUT: usize, const BIT_DEPTH: usize>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaper<f64, LINEAR_CAP>,
_: TransformOptions,
) -> Result<Box<dyn TransformExecutor<f64> + Send + Sync>, CmsError> {
make_rgb_xyz_rgb_transform::<f64, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
}
}
impl RgbXyzFactory<u8> for u8 {
fn make_transform<const LINEAR_CAP: usize, const GAMMA_LUT: usize, const BIT_DEPTH: usize>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaper<u8, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<u8> + Send + Sync>, CmsError> {
if transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_avx2;
if std::arch::is_x86_feature_detected!("avx2") {
return make_rgb_xyz_q2_13_transform_avx2::<u8, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, 8,
);
}
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_sse_41;
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_q2_13_transform_sse_41::<u8, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, 8,
);
}
}
make_rgb_xyz_q2_13::<u8, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, 8,
)
} else {
make_rgb_xyz_rgb_transform::<u8, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, 8,
)
}
}
}
// Optimized factories
impl RgbXyzFactoryOpt<u16> for u16 {
fn make_optimized_transform<
const LINEAR_CAP: usize,
const GAMMA_LUT: usize,
const BIT_DEPTH: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaperOptimized<u16, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<u16> + Send + Sync>, CmsError> {
if BIT_DEPTH >= 12 && transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
{
if std::arch::is_aarch64_feature_detected!("rdm") {
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q1_30_opt;
return make_rgb_xyz_q1_30_opt::<u16, LINEAR_CAP, 30>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
}
if BIT_DEPTH < 16 && transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_avx2_opt;
if std::arch::is_x86_feature_detected!("avx2") {
return make_rgb_xyz_q2_13_transform_avx2_opt::<
u16,
LINEAR_CAP,
FIXED_POINT_SCALE,
>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH
);
}
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_sse_41_opt;
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_q2_13_transform_sse_41_opt::<
u16,
LINEAR_CAP,
FIXED_POINT_SCALE,
>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH
);
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
{
return make_rgb_xyz_q2_13_opt::<u16, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
make_rgb_xyz_rgb_transform_opt::<u16, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
}
}
impl RgbXyzFactoryOpt<f32> for f32 {
fn make_optimized_transform<
const LINEAR_CAP: usize,
const GAMMA_LUT: usize,
const BIT_DEPTH: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaperOptimized<f32, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<f32> + Send + Sync>, CmsError> {
if transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_avx2_opt;
if std::arch::is_x86_feature_detected!("avx2") {
return make_rgb_xyz_q2_13_transform_avx2_opt::<
f32,
LINEAR_CAP,
FIXED_POINT_SCALE,
>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH
);
}
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_sse_41_opt;
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_q2_13_transform_sse_41_opt::<
f32,
LINEAR_CAP,
FIXED_POINT_SCALE,
>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH
);
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
{
return if std::arch::is_aarch64_feature_detected!("rdm") {
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q1_30_opt;
make_rgb_xyz_q1_30_opt::<f32, LINEAR_CAP, 30>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
} else {
make_rgb_xyz_q2_13_opt::<f32, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
};
}
}
make_rgb_xyz_rgb_transform_opt::<f32, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
}
}
impl RgbXyzFactoryOpt<f64> for f64 {
fn make_optimized_transform<
const LINEAR_CAP: usize,
const GAMMA_LUT: usize,
const BIT_DEPTH: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaperOptimized<f64, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<f64> + Send + Sync>, CmsError> {
if transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
{
if std::arch::is_aarch64_feature_detected!("rdm") {
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q1_30_opt;
return make_rgb_xyz_q1_30_opt::<f64, LINEAR_CAP, 30>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
);
}
}
}
make_rgb_xyz_rgb_transform_opt::<f64, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, BIT_DEPTH,
)
}
}
impl RgbXyzFactoryOpt<u8> for u8 {
fn make_optimized_transform<
const LINEAR_CAP: usize,
const GAMMA_LUT: usize,
const BIT_DEPTH: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaperOptimized<u8, LINEAR_CAP>,
transform_options: TransformOptions,
) -> Result<Box<dyn TransformExecutor<u8> + Send + Sync>, CmsError> {
if transform_options.prefer_fixed_point {
#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_avx512_opt;
if std::arch::is_x86_feature_detected!("avx512bw")
&& std::arch::is_x86_feature_detected!("avx512vl")
{
return make_rgb_xyz_q2_13_transform_avx512_opt::<
u8,
LINEAR_CAP,
FIXED_POINT_SCALE,
>(src_layout, dst_layout, profile, GAMMA_LUT, 8);
}
}
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_avx2_opt;
if std::arch::is_x86_feature_detected!("avx2") {
return make_rgb_xyz_q2_13_transform_avx2_opt::<
u8,
LINEAR_CAP,
FIXED_POINT_SCALE,
>(src_layout, dst_layout, profile, GAMMA_LUT, 8);
}
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
{
use crate::conversions::rgbxyz_fixed::make_rgb_xyz_q2_13_transform_sse_41_opt;
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_q2_13_transform_sse_41_opt::<
u8,
LINEAR_CAP,
FIXED_POINT_SCALE,
>(src_layout, dst_layout, profile, GAMMA_LUT, 8);
}
}
make_rgb_xyz_q2_13_opt::<u8, LINEAR_CAP, FIXED_POINT_SCALE>(
src_layout, dst_layout, profile, GAMMA_LUT, 8,
)
} else {
make_rgb_xyz_rgb_transform_opt::<u8, LINEAR_CAP>(
src_layout, dst_layout, profile, GAMMA_LUT, 8,
)
}
}
}

View File

@@ -1,899 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::{CmsError, Layout, Matrix3, Matrix3f, TransformExecutor};
use num_traits::AsPrimitive;
pub(crate) struct TransformMatrixShaper<T: Clone, const BUCKET: usize> {
pub(crate) r_linear: Box<[f32; BUCKET]>,
pub(crate) g_linear: Box<[f32; BUCKET]>,
pub(crate) b_linear: Box<[f32; BUCKET]>,
pub(crate) r_gamma: Box<[T; 65536]>,
pub(crate) g_gamma: Box<[T; 65536]>,
pub(crate) b_gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3f,
}
impl<T: Clone, const BUCKET: usize> TransformMatrixShaper<T, BUCKET> {
#[inline(never)]
#[allow(dead_code)]
fn convert_to_v(self) -> TransformMatrixShaperV<T> {
TransformMatrixShaperV {
r_linear: self.r_linear.iter().copied().collect(),
g_linear: self.g_linear.iter().copied().collect(),
b_linear: self.b_linear.iter().copied().collect(),
r_gamma: self.r_gamma,
g_gamma: self.g_gamma,
b_gamma: self.b_gamma,
adaptation_matrix: self.adaptation_matrix,
}
}
}
#[allow(dead_code)]
pub(crate) struct TransformMatrixShaperV<T: Clone> {
pub(crate) r_linear: Vec<f32>,
pub(crate) g_linear: Vec<f32>,
pub(crate) b_linear: Vec<f32>,
pub(crate) r_gamma: Box<[T; 65536]>,
pub(crate) g_gamma: Box<[T; 65536]>,
pub(crate) b_gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3f,
}
/// Low memory footprint optimized routine for matrix shaper profiles with the same
/// Gamma and linear curves.
pub(crate) struct TransformMatrixShaperOptimized<T: Clone, const BUCKET: usize> {
pub(crate) linear: Box<[f32; BUCKET]>,
pub(crate) gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3f,
}
#[allow(dead_code)]
impl<T: Clone, const BUCKET: usize> TransformMatrixShaperOptimized<T, BUCKET> {
fn convert_to_v(self) -> TransformMatrixShaperOptimizedV<T> {
TransformMatrixShaperOptimizedV {
linear: self.linear.iter().copied().collect::<Vec<_>>(),
gamma: self.gamma,
adaptation_matrix: self.adaptation_matrix,
}
}
}
/// Low memory footprint optimized routine for matrix shaper profiles with the same
/// Gamma and linear curves.
#[allow(dead_code)]
pub(crate) struct TransformMatrixShaperOptimizedV<T: Clone> {
pub(crate) linear: Vec<f32>,
pub(crate) gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3f,
}
impl<T: Clone + PointeeSizeExpressible, const BUCKET: usize> TransformMatrixShaper<T, BUCKET> {
#[inline(never)]
#[allow(dead_code)]
pub(crate) fn to_q2_13_n<
R: Copy + 'static + Default,
const PRECISION: i32,
const LINEAR_CAP: usize,
>(
&self,
gamma_lut: usize,
bit_depth: usize,
) -> TransformMatrixShaperFixedPoint<R, T, BUCKET>
where
f32: AsPrimitive<R>,
{
let linear_scale = if T::FINITE {
let lut_scale = (gamma_lut - 1) as f32 / ((1 << bit_depth) - 1) as f32;
((1 << bit_depth) - 1) as f32 * lut_scale
} else {
let lut_scale = (gamma_lut - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
(T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
};
let mut new_box_r = Box::new([R::default(); BUCKET]);
let mut new_box_g = Box::new([R::default(); BUCKET]);
let mut new_box_b = Box::new([R::default(); BUCKET]);
for (dst, &src) in new_box_r.iter_mut().zip(self.r_linear.iter()) {
*dst = (src * linear_scale).round().as_();
}
for (dst, &src) in new_box_g.iter_mut().zip(self.g_linear.iter()) {
*dst = (src * linear_scale).round().as_();
}
for (dst, &src) in new_box_b.iter_mut().zip(self.b_linear.iter()) {
*dst = (src * linear_scale).round().as_();
}
let scale: f32 = (1i32 << PRECISION) as f32;
let source_matrix = self.adaptation_matrix;
let mut dst_matrix = Matrix3::<i16> { v: [[0i16; 3]; 3] };
for i in 0..3 {
for j in 0..3 {
dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
}
}
TransformMatrixShaperFixedPoint {
r_linear: new_box_r,
g_linear: new_box_g,
b_linear: new_box_b,
r_gamma: self.r_gamma.clone(),
g_gamma: self.g_gamma.clone(),
b_gamma: self.b_gamma.clone(),
adaptation_matrix: dst_matrix,
}
}
#[inline(never)]
#[allow(dead_code)]
pub(crate) fn to_q2_13_i<R: Copy + 'static + Default, const PRECISION: i32>(
&self,
gamma_lut: usize,
bit_depth: usize,
) -> TransformMatrixShaperFp<R, T>
where
f32: AsPrimitive<R>,
{
let linear_scale = if T::FINITE {
let lut_scale = (gamma_lut - 1) as f32 / ((1 << bit_depth) - 1) as f32;
((1 << bit_depth) - 1) as f32 * lut_scale
} else {
let lut_scale = (gamma_lut - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
(T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
};
let new_box_r = self
.r_linear
.iter()
.map(|&x| (x * linear_scale).round().as_())
.collect::<Vec<R>>();
let new_box_g = self
.g_linear
.iter()
.map(|&x| (x * linear_scale).round().as_())
.collect::<Vec<R>>();
let new_box_b = self
.b_linear
.iter()
.map(|&x| (x * linear_scale).round().as_())
.collect::<Vec<_>>();
let scale: f32 = (1i32 << PRECISION) as f32;
let source_matrix = self.adaptation_matrix;
let mut dst_matrix = Matrix3::<i16> { v: [[0i16; 3]; 3] };
for i in 0..3 {
for j in 0..3 {
dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
}
}
TransformMatrixShaperFp {
r_linear: new_box_r,
g_linear: new_box_g,
b_linear: new_box_b,
r_gamma: self.r_gamma.clone(),
g_gamma: self.g_gamma.clone(),
b_gamma: self.b_gamma.clone(),
adaptation_matrix: dst_matrix,
}
}
}
impl<T: Clone + PointeeSizeExpressible, const BUCKET: usize>
TransformMatrixShaperOptimized<T, BUCKET>
{
#[allow(dead_code)]
pub(crate) fn to_q2_13_n<
R: Copy + 'static + Default,
const PRECISION: i32,
const LINEAR_CAP: usize,
>(
&self,
gamma_lut: usize,
bit_depth: usize,
) -> TransformMatrixShaperFixedPointOpt<R, i16, T, BUCKET>
where
f32: AsPrimitive<R>,
{
let linear_scale = if T::FINITE {
let lut_scale = (gamma_lut - 1) as f32 / ((1 << bit_depth) - 1) as f32;
((1 << bit_depth) - 1) as f32 * lut_scale
} else {
let lut_scale = (gamma_lut - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
(T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
};
let mut new_box_linear = Box::new([R::default(); BUCKET]);
for (dst, src) in new_box_linear.iter_mut().zip(self.linear.iter()) {
*dst = (*src * linear_scale).round().as_();
}
let scale: f32 = (1i32 << PRECISION) as f32;
let source_matrix = self.adaptation_matrix;
let mut dst_matrix = Matrix3::<i16> {
v: [[i16::default(); 3]; 3],
};
for i in 0..3 {
for j in 0..3 {
dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
}
}
TransformMatrixShaperFixedPointOpt {
linear: new_box_linear,
gamma: self.gamma.clone(),
adaptation_matrix: dst_matrix,
}
}
#[allow(dead_code)]
pub(crate) fn to_q2_13_i<R: Copy + 'static + Default, const PRECISION: i32>(
&self,
gamma_lut: usize,
bit_depth: usize,
) -> TransformMatrixShaperFpOptVec<R, i16, T>
where
f32: AsPrimitive<R>,
{
let linear_scale = if T::FINITE {
let lut_scale = (gamma_lut - 1) as f32 / ((1 << bit_depth) - 1) as f32;
((1 << bit_depth) - 1) as f32 * lut_scale
} else {
let lut_scale = (gamma_lut - 1) as f32 / (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32;
(T::NOT_FINITE_LINEAR_TABLE_SIZE - 1) as f32 * lut_scale
};
let new_box_linear = self
.linear
.iter()
.map(|&x| (x * linear_scale).round().as_())
.collect::<Vec<R>>();
let scale: f32 = (1i32 << PRECISION) as f32;
let source_matrix = self.adaptation_matrix;
let mut dst_matrix = Matrix3::<i16> {
v: [[i16::default(); 3]; 3],
};
for i in 0..3 {
for j in 0..3 {
dst_matrix.v[i][j] = (source_matrix.v[i][j] * scale) as i16;
}
}
TransformMatrixShaperFpOptVec {
linear: new_box_linear,
gamma: self.gamma.clone(),
adaptation_matrix: dst_matrix,
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
pub(crate) fn to_q1_30_n<R: Copy + 'static + Default, const PRECISION: i32>(
&self,
gamma_lut: usize,
bit_depth: usize,
) -> TransformMatrixShaperFpOptVec<R, i32, T>
where
f32: AsPrimitive<R>,
f64: AsPrimitive<R>,
{
// It is important to scale 1 bit more to compensate vqrdmlah Q0.31, because we're going to use Q1.30
let table_size = if T::FINITE {
(1 << bit_depth) - 1
} else {
T::NOT_FINITE_LINEAR_TABLE_SIZE - 1
};
let ext_bp = if T::FINITE {
bit_depth as u32 + 1
} else {
let bp = (T::NOT_FINITE_LINEAR_TABLE_SIZE - 1).count_ones();
bp + 1
};
let linear_scale = {
let lut_scale = (gamma_lut - 1) as f64 / table_size as f64;
((1u32 << ext_bp) - 1) as f64 * lut_scale
};
let new_box_linear = self
.linear
.iter()
.map(|&v| (v as f64 * linear_scale).round().as_())
.collect::<Vec<R>>();
let scale: f64 = (1i64 << PRECISION) as f64;
let source_matrix = self.adaptation_matrix;
let mut dst_matrix = Matrix3::<i32> {
v: [[i32::default(); 3]; 3],
};
for i in 0..3 {
for j in 0..3 {
dst_matrix.v[i][j] = (source_matrix.v[i][j] as f64 * scale) as i32;
}
}
TransformMatrixShaperFpOptVec {
linear: new_box_linear,
gamma: self.gamma.clone(),
adaptation_matrix: dst_matrix,
}
}
}
#[allow(unused)]
struct TransformMatrixShaperScalar<
T: Clone,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
> {
pub(crate) profile: TransformMatrixShaper<T, LINEAR_CAP>,
pub(crate) gamma_lut: usize,
pub(crate) bit_depth: usize,
}
#[allow(unused)]
struct TransformMatrixShaperOptScalar<
T: Clone,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
> {
pub(crate) profile: TransformMatrixShaperOptimized<T, LINEAR_CAP>,
pub(crate) gamma_lut: usize,
pub(crate) bit_depth: usize,
}
#[cfg(any(
any(target_arch = "x86", target_arch = "x86_64"),
all(target_arch = "aarch64", target_feature = "neon")
))]
#[allow(unused)]
macro_rules! create_rgb_xyz_dependant_executor {
($dep_name: ident, $dependant: ident, $shaper: ident) => {
pub(crate) fn $dep_name<
T: Clone + Send + Sync + Default + PointeeSizeExpressible + Copy + 'static,
const LINEAR_CAP: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: $shaper<T, LINEAR_CAP>,
gamma_lut: usize,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
{
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
gamma_lut,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
};
}
#[cfg(any(
any(target_arch = "x86", target_arch = "x86_64"),
all(target_arch = "aarch64", target_feature = "neon")
))]
#[allow(unused)]
macro_rules! create_rgb_xyz_dependant_executor_to_v {
($dep_name: ident, $dependant: ident, $shaper: ident) => {
pub(crate) fn $dep_name<
T: Clone + Send + Sync + Default + PointeeSizeExpressible + Copy + 'static,
const LINEAR_CAP: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: $shaper<T, LINEAR_CAP>,
gamma_lut: usize,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
{
let profile = profile.convert_to_v();
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
> {
profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
> {
profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
> {
profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
> {
profile,
bit_depth,
gamma_lut,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
};
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
use crate::conversions::sse::{TransformShaperRgbOptSse, TransformShaperRgbSse};
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
use crate::conversions::avx::{TransformShaperRgbAvx, TransformShaperRgbOptAvx};
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
create_rgb_xyz_dependant_executor!(
make_rgb_xyz_rgb_transform_sse_41,
TransformShaperRgbSse,
TransformMatrixShaper
);
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
create_rgb_xyz_dependant_executor_to_v!(
make_rgb_xyz_rgb_transform_sse_41_opt,
TransformShaperRgbOptSse,
TransformMatrixShaperOptimized
);
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
create_rgb_xyz_dependant_executor!(
make_rgb_xyz_rgb_transform_avx2,
TransformShaperRgbAvx,
TransformMatrixShaper
);
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
create_rgb_xyz_dependant_executor_to_v!(
make_rgb_xyz_rgb_transform_avx2_opt,
TransformShaperRgbOptAvx,
TransformMatrixShaperOptimized
);
#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
use crate::conversions::avx512::TransformShaperRgbOptAvx512;
#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
create_rgb_xyz_dependant_executor!(
make_rgb_xyz_rgb_transform_avx512_opt,
TransformShaperRgbOptAvx512,
TransformMatrixShaperOptimized
);
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
pub(crate) fn make_rgb_xyz_rgb_transform<
T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
const LINEAR_CAP: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaper<T, LINEAR_CAP>,
gamma_lut: usize,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
{
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
if std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma") {
return make_rgb_xyz_rgb_transform_avx2::<T, LINEAR_CAP>(
src_layout, dst_layout, profile, gamma_lut, bit_depth,
);
}
#[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_rgb_transform_sse_41::<T, LINEAR_CAP>(
src_layout, dst_layout, profile, gamma_lut, bit_depth,
);
}
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformMatrixShaperScalar::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformMatrixShaperScalar::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformMatrixShaperScalar::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformMatrixShaperScalar::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
pub(crate) fn make_rgb_xyz_rgb_transform_opt<
T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
const LINEAR_CAP: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformMatrixShaperOptimized<T, LINEAR_CAP>,
gamma_lut: usize,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
{
#[cfg(all(feature = "avx512", target_arch = "x86_64"))]
if std::arch::is_x86_feature_detected!("avx512bw")
&& std::arch::is_x86_feature_detected!("avx512vl")
&& std::arch::is_x86_feature_detected!("fma")
{
return make_rgb_xyz_rgb_transform_avx512_opt::<T, LINEAR_CAP>(
src_layout, dst_layout, profile, gamma_lut, bit_depth,
);
}
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
if std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma") {
return make_rgb_xyz_rgb_transform_avx2_opt::<T, LINEAR_CAP>(
src_layout, dst_layout, profile, gamma_lut, bit_depth,
);
}
#[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
if std::arch::is_x86_feature_detected!("sse4.1") {
return make_rgb_xyz_rgb_transform_sse_41_opt::<T, LINEAR_CAP>(
src_layout, dst_layout, profile, gamma_lut, bit_depth,
);
}
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformMatrixShaperOptScalar::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformMatrixShaperOptScalar::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformMatrixShaperOptScalar::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformMatrixShaperOptScalar::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
gamma_lut,
bit_depth,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
use crate::conversions::neon::{TransformShaperRgbNeon, TransformShaperRgbOptNeon};
use crate::conversions::rgbxyz_fixed::TransformMatrixShaperFpOptVec;
use crate::conversions::rgbxyz_fixed::{
TransformMatrixShaperFixedPoint, TransformMatrixShaperFixedPointOpt, TransformMatrixShaperFp,
};
use crate::transform::PointeeSizeExpressible;
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
create_rgb_xyz_dependant_executor_to_v!(
make_rgb_xyz_rgb_transform,
TransformShaperRgbNeon,
TransformMatrixShaper
);
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
create_rgb_xyz_dependant_executor_to_v!(
make_rgb_xyz_rgb_transform_opt,
TransformShaperRgbOptNeon,
TransformMatrixShaperOptimized
);
#[allow(unused)]
impl<
T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
> TransformExecutor<T> for TransformMatrixShaperScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP>
where
u32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
use crate::mlaf::mlaf;
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let transform = self.profile.adaptation_matrix;
let scale = (self.gamma_lut - 1) as f32;
let max_colors: T = ((1 << self.bit_depth) - 1).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_colors
};
let new_r = mlaf(
0.5f32,
mlaf(
mlaf(r * transform.v[0][0], g, transform.v[0][1]),
b,
transform.v[0][2],
)
.max(0f32)
.min(1f32),
scale,
);
let new_g = mlaf(
0.5f32,
mlaf(
mlaf(r * transform.v[1][0], g, transform.v[1][1]),
b,
transform.v[1][2],
)
.max(0f32)
.min(1f32),
scale,
);
let new_b = mlaf(
0.5f32,
mlaf(
mlaf(r * transform.v[2][0], g, transform.v[2][1]),
b,
transform.v[2][2],
)
.max(0f32)
.min(1f32),
scale,
);
dst[dst_cn.r_i()] = self.profile.r_gamma[(new_r as u16) as usize];
dst[dst_cn.g_i()] = self.profile.g_gamma[(new_g as u16) as usize];
dst[dst_cn.b_i()] = self.profile.b_gamma[(new_b as u16) as usize];
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
}
Ok(())
}
}
#[allow(unused)]
impl<
T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
> TransformExecutor<T> for TransformMatrixShaperOptScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP>
where
u32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
use crate::mlaf::mlaf;
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let transform = self.profile.adaptation_matrix;
let scale = (self.gamma_lut - 1) as f32;
let max_colors: T = ((1 << self.bit_depth) - 1).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let r = self.profile.linear[src[src_cn.r_i()]._as_usize()];
let g = self.profile.linear[src[src_cn.g_i()]._as_usize()];
let b = self.profile.linear[src[src_cn.b_i()]._as_usize()];
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_colors
};
let new_r = mlaf(
0.5f32,
mlaf(
mlaf(r * transform.v[0][0], g, transform.v[0][1]),
b,
transform.v[0][2],
)
.max(0f32)
.min(1f32),
scale,
);
let new_g = mlaf(
0.5f32,
mlaf(
mlaf(r * transform.v[1][0], g, transform.v[1][1]),
b,
transform.v[1][2],
)
.max(0f32)
.min(1f32),
scale,
);
let new_b = mlaf(
0.5f32,
mlaf(
mlaf(r * transform.v[2][0], g, transform.v[2][1]),
b,
transform.v[2][2],
)
.max(0f32)
.min(1f32),
scale,
);
dst[dst_cn.r_i()] = self.profile.gamma[(new_r as u16) as usize];
dst[dst_cn.g_i()] = self.profile.gamma[(new_g as u16) as usize];
dst[dst_cn.b_i()] = self.profile.gamma[(new_b as u16) as usize];
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
}
Ok(())
}
}

View File

@@ -1,560 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::Layout;
use crate::conversions::TransformMatrixShaper;
use crate::matrix::Matrix3;
use crate::{CmsError, TransformExecutor};
use num_traits::AsPrimitive;
/// Fixed point conversion Q2.13
pub(crate) struct TransformMatrixShaperFixedPoint<R, T, const LINEAR_CAP: usize> {
pub(crate) r_linear: Box<[R; LINEAR_CAP]>,
pub(crate) g_linear: Box<[R; LINEAR_CAP]>,
pub(crate) b_linear: Box<[R; LINEAR_CAP]>,
pub(crate) r_gamma: Box<[T; 65536]>,
pub(crate) g_gamma: Box<[T; 65536]>,
pub(crate) b_gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3<i16>,
}
/// Fixed point conversion Q2.13
#[allow(dead_code)]
pub(crate) struct TransformMatrixShaperFp<R, T> {
pub(crate) r_linear: Vec<R>,
pub(crate) g_linear: Vec<R>,
pub(crate) b_linear: Vec<R>,
pub(crate) r_gamma: Box<[T; 65536]>,
pub(crate) g_gamma: Box<[T; 65536]>,
pub(crate) b_gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3<i16>,
}
/// Fixed point conversion Q2.13
///
/// Optimized routine for *all same curves* matrix shaper.
pub(crate) struct TransformMatrixShaperFixedPointOpt<R, W, T, const LINEAR_CAP: usize> {
pub(crate) linear: Box<[R; LINEAR_CAP]>,
pub(crate) gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3<W>,
}
/// Fixed point conversion Q2.13
///
/// Optimized routine for *all same curves* matrix shaper.
#[allow(dead_code)]
pub(crate) struct TransformMatrixShaperFpOptVec<R, W, T> {
pub(crate) linear: Vec<R>,
pub(crate) gamma: Box<[T; 65536]>,
pub(crate) adaptation_matrix: Matrix3<W>,
}
#[allow(unused)]
struct TransformMatrixShaperQ2_13<
T: Copy,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
const PRECISION: i32,
> {
pub(crate) profile: TransformMatrixShaperFixedPoint<i16, T, LINEAR_CAP>,
pub(crate) bit_depth: usize,
pub(crate) gamma_lut: usize,
}
#[allow(unused)]
struct TransformMatrixShaperQ2_13Optimized<
T: Copy,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
const PRECISION: i32,
> {
pub(crate) profile: TransformMatrixShaperFixedPointOpt<i16, i16, T, LINEAR_CAP>,
pub(crate) bit_depth: usize,
pub(crate) gamma_lut: usize,
}
#[allow(unused)]
impl<
T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
const PRECISION: i32,
> TransformExecutor<T>
for TransformMatrixShaperQ2_13<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, PRECISION>
where
u32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let transform = self.profile.adaptation_matrix;
let max_colors: T = ((1 << self.bit_depth as u32) - 1u32).as_();
let rnd: i32 = (1i32 << (PRECISION - 1));
let v_gamma_max = self.gamma_lut as i32 - 1;
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_colors
};
let new_r = r as i32 * transform.v[0][0] as i32
+ g as i32 * transform.v[0][1] as i32
+ b as i32 * transform.v[0][2] as i32
+ rnd;
let r_q2_13 = (new_r >> PRECISION).min(v_gamma_max).max(0) as u16;
let new_g = r as i32 * transform.v[1][0] as i32
+ g as i32 * transform.v[1][1] as i32
+ b as i32 * transform.v[1][2] as i32
+ rnd;
let g_q2_13 = (new_g >> PRECISION).min(v_gamma_max).max(0) as u16;
let new_b = r as i32 * transform.v[2][0] as i32
+ g as i32 * transform.v[2][1] as i32
+ b as i32 * transform.v[2][2] as i32
+ rnd;
let b_q2_13 = (new_b >> PRECISION).min(v_gamma_max).max(0) as u16;
dst[dst_cn.r_i()] = self.profile.r_gamma[r_q2_13 as usize];
dst[dst_cn.g_i()] = self.profile.g_gamma[g_q2_13 as usize];
dst[dst_cn.b_i()] = self.profile.b_gamma[b_q2_13 as usize];
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
}
Ok(())
}
}
#[allow(unused)]
impl<
T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
const PRECISION: i32,
> TransformExecutor<T>
for TransformMatrixShaperQ2_13Optimized<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP, PRECISION>
where
u32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let transform = self.profile.adaptation_matrix;
let max_colors: T = ((1 << self.bit_depth as u32) - 1u32).as_();
let rnd: i32 = (1i32 << (PRECISION - 1));
let v_gamma_max = self.gamma_lut as i32 - 1;
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let r = self.profile.linear[src[src_cn.r_i()]._as_usize()];
let g = self.profile.linear[src[src_cn.g_i()]._as_usize()];
let b = self.profile.linear[src[src_cn.b_i()]._as_usize()];
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_colors
};
let new_r = r as i32 * transform.v[0][0] as i32
+ g as i32 * transform.v[0][1] as i32
+ b as i32 * transform.v[0][2] as i32
+ rnd;
let r_q2_13 = (new_r >> PRECISION).min(v_gamma_max).max(0) as u16;
let new_g = r as i32 * transform.v[1][0] as i32
+ g as i32 * transform.v[1][1] as i32
+ b as i32 * transform.v[1][2] as i32
+ rnd;
let g_q2_13 = (new_g >> PRECISION).min(v_gamma_max).max(0) as u16;
let new_b = r as i32 * transform.v[2][0] as i32
+ g as i32 * transform.v[2][1] as i32
+ b as i32 * transform.v[2][2] as i32
+ rnd;
let b_q2_13 = (new_b >> PRECISION).min(v_gamma_max).max(0) as u16;
dst[dst_cn.r_i()] = self.profile.gamma[r_q2_13 as usize];
dst[dst_cn.g_i()] = self.profile.gamma[g_q2_13 as usize];
dst[dst_cn.b_i()] = self.profile.gamma[b_q2_13 as usize];
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
}
Ok(())
}
}
#[allow(unused_macros)]
macro_rules! create_rgb_xyz_dependant_q2_13_executor {
($dep_name: ident, $dependant: ident, $resolution: ident, $shaper: ident) => {
pub(crate) fn $dep_name<
T: Clone + Send + Sync + AsPrimitive<usize> + Default + PointeeSizeExpressible,
const LINEAR_CAP: usize,
const PRECISION: i32,
>(
src_layout: Layout,
dst_layout: Layout,
profile: $shaper<T, LINEAR_CAP>,
gamma_lut: usize,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
{
let q2_13_profile =
profile.to_q2_13_n::<$resolution, PRECISION, LINEAR_CAP>(gamma_lut, bit_depth);
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
};
}
#[allow(unused_macros)]
macro_rules! create_rgb_xyz_dependant_q2_13_executor_fp {
($dep_name: ident, $dependant: ident, $resolution: ident, $shaper: ident) => {
pub(crate) fn $dep_name<
T: Clone + Send + Sync + AsPrimitive<usize> + Default + PointeeSizeExpressible,
const LINEAR_CAP: usize,
const PRECISION: i32,
>(
src_layout: Layout,
dst_layout: Layout,
profile: $shaper<T, LINEAR_CAP>,
gamma_lut: usize,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
{
let q2_13_profile = profile.to_q2_13_i::<$resolution, PRECISION>(gamma_lut, bit_depth);
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
PRECISION,
> {
profile: q2_13_profile,
bit_depth,
gamma_lut,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
};
}
#[cfg(all(target_arch = "aarch64", feature = "neon"))]
macro_rules! create_rgb_xyz_dependant_q1_30_executor {
($dep_name: ident, $dependant: ident, $resolution: ident, $shaper: ident) => {
pub(crate) fn $dep_name<
T: Clone + Send + Sync + AsPrimitive<usize> + Default + PointeeSizeExpressible,
const LINEAR_CAP: usize,
const PRECISION: i32,
>(
src_layout: Layout,
dst_layout: Layout,
profile: $shaper<T, LINEAR_CAP>,
gamma_lut: usize,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
{
let q1_30_profile = profile.to_q1_30_n::<$resolution, PRECISION>(gamma_lut, bit_depth);
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
> {
profile: q1_30_profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
> {
profile: q1_30_profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
> {
profile: q1_30_profile,
gamma_lut,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new($dependant::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
> {
profile: q1_30_profile,
gamma_lut,
bit_depth,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
};
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
use crate::conversions::neon::{
TransformShaperQ1_30NeonOpt, TransformShaperQ2_13Neon, TransformShaperQ2_13NeonOpt,
};
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
create_rgb_xyz_dependant_q2_13_executor_fp!(
make_rgb_xyz_q2_13,
TransformShaperQ2_13Neon,
i16,
TransformMatrixShaper
);
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
create_rgb_xyz_dependant_q2_13_executor_fp!(
make_rgb_xyz_q2_13_opt,
TransformShaperQ2_13NeonOpt,
i16,
TransformMatrixShaperOptimized
);
#[cfg(all(target_arch = "aarch64", target_feature = "neon", feature = "neon"))]
create_rgb_xyz_dependant_q1_30_executor!(
make_rgb_xyz_q1_30_opt,
TransformShaperQ1_30NeonOpt,
i32,
TransformMatrixShaperOptimized
);
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
create_rgb_xyz_dependant_q2_13_executor!(
make_rgb_xyz_q2_13,
TransformMatrixShaperQ2_13,
i16,
TransformMatrixShaper
);
#[cfg(not(all(target_arch = "aarch64", target_feature = "neon", feature = "neon")))]
create_rgb_xyz_dependant_q2_13_executor!(
make_rgb_xyz_q2_13_opt,
TransformMatrixShaperQ2_13Optimized,
i16,
TransformMatrixShaperOptimized
);
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
use crate::conversions::sse::{TransformShaperQ2_13OptSse, TransformShaperQ2_13Sse};
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
create_rgb_xyz_dependant_q2_13_executor_fp!(
make_rgb_xyz_q2_13_transform_sse_41,
TransformShaperQ2_13Sse,
i32,
TransformMatrixShaper
);
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
create_rgb_xyz_dependant_q2_13_executor_fp!(
make_rgb_xyz_q2_13_transform_sse_41_opt,
TransformShaperQ2_13OptSse,
i32,
TransformMatrixShaperOptimized
);
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
use crate::conversions::avx::{TransformShaperRgbQ2_13Avx, TransformShaperRgbQ2_13OptAvx};
use crate::conversions::rgbxyz::TransformMatrixShaperOptimized;
use crate::transform::PointeeSizeExpressible;
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
create_rgb_xyz_dependant_q2_13_executor_fp!(
make_rgb_xyz_q2_13_transform_avx2,
TransformShaperRgbQ2_13Avx,
i32,
TransformMatrixShaper
);
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
create_rgb_xyz_dependant_q2_13_executor_fp!(
make_rgb_xyz_q2_13_transform_avx2_opt,
TransformShaperRgbQ2_13OptAvx,
i32,
TransformMatrixShaperOptimized
);
#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
use crate::conversions::avx512::TransformShaperRgbQ2_13OptAvx512;
#[cfg(all(target_arch = "x86_64", feature = "avx512"))]
create_rgb_xyz_dependant_q2_13_executor!(
make_rgb_xyz_q2_13_transform_avx512_opt,
TransformShaperRgbQ2_13OptAvx512,
i32,
TransformMatrixShaperOptimized
);

View File

@@ -1,330 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::trc::ToneCurveEvaluator;
use crate::{CmsError, Layout, Matrix3f, PointeeSizeExpressible, Rgb, TransformExecutor};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) struct TransformShaperRgbFloat<T: Clone, const BUCKET: usize> {
pub(crate) r_linear: Box<[f32; BUCKET]>,
pub(crate) g_linear: Box<[f32; BUCKET]>,
pub(crate) b_linear: Box<[f32; BUCKET]>,
pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
pub(crate) adaptation_matrix: Matrix3f,
pub(crate) phantom_data: PhantomData<T>,
}
pub(crate) struct TransformShaperFloatInOut<T: Clone> {
pub(crate) linear_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
pub(crate) gamma_evaluator: Box<dyn ToneCurveEvaluator + Send + Sync>,
pub(crate) adaptation_matrix: Matrix3f,
pub(crate) phantom_data: PhantomData<T>,
}
struct TransformShaperFloatScalar<
T: Clone,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
> {
pub(crate) profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
pub(crate) bit_depth: usize,
}
struct TransformShaperRgbFloatInOut<T: Clone, const SRC_LAYOUT: u8, const DST_LAYOUT: u8> {
pub(crate) profile: TransformShaperFloatInOut<T>,
pub(crate) bit_depth: usize,
}
pub(crate) fn make_rgb_xyz_rgb_transform_float<
T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default,
const LINEAR_CAP: usize,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformShaperRgbFloat<T, LINEAR_CAP>,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformShaperFloatScalar::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformShaperFloatScalar::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformShaperFloatScalar::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformShaperFloatScalar::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
LINEAR_CAP,
> {
profile,
bit_depth,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
pub(crate) fn make_rgb_xyz_rgb_transform_float_in_out<
T: Clone + Send + Sync + PointeeSizeExpressible + 'static + Copy + Default + AsPrimitive<f32>,
>(
src_layout: Layout,
dst_layout: Layout,
profile: TransformShaperFloatInOut<T>,
bit_depth: usize,
) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformShaperRgbFloatInOut::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgba as u8 },
> {
profile,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgba) {
return Ok(Box::new(TransformShaperRgbFloatInOut::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgba as u8 },
> {
profile,
bit_depth,
}));
} else if (src_layout == Layout::Rgba) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformShaperRgbFloatInOut::<
T,
{ Layout::Rgba as u8 },
{ Layout::Rgb as u8 },
> {
profile,
bit_depth,
}));
} else if (src_layout == Layout::Rgb) && (dst_layout == Layout::Rgb) {
return Ok(Box::new(TransformShaperRgbFloatInOut::<
T,
{ Layout::Rgb as u8 },
{ Layout::Rgb as u8 },
> {
profile,
bit_depth,
}));
}
Err(CmsError::UnsupportedProfileConnection)
}
impl<
T: Clone + PointeeSizeExpressible + Copy + Default + 'static,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const LINEAR_CAP: usize,
> TransformExecutor<T> for TransformShaperFloatScalar<T, SRC_LAYOUT, DST_LAYOUT, LINEAR_CAP>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
use crate::mlaf::mlaf;
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let transform = self.profile.adaptation_matrix;
let max_colors: T = ((1 << self.bit_depth) - 1).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let r = self.profile.r_linear[src[src_cn.r_i()]._as_usize()];
let g = self.profile.g_linear[src[src_cn.g_i()]._as_usize()];
let b = self.profile.b_linear[src[src_cn.b_i()]._as_usize()];
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_colors
};
let new_r = mlaf(
mlaf(r * transform.v[0][0], g, transform.v[0][1]),
b,
transform.v[0][2],
);
let new_g = mlaf(
mlaf(r * transform.v[1][0], g, transform.v[1][1]),
b,
transform.v[1][2],
);
let new_b = mlaf(
mlaf(r * transform.v[2][0], g, transform.v[2][1]),
b,
transform.v[2][2],
);
let mut rgb = Rgb::new(new_r, new_g, new_b);
rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
dst[dst_cn.r_i()] = rgb.r.as_();
dst[dst_cn.g_i()] = rgb.g.as_();
dst[dst_cn.b_i()] = rgb.b.as_();
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
}
Ok(())
}
}
impl<
T: Clone + PointeeSizeExpressible + Copy + Default + 'static + AsPrimitive<f32>,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
> TransformExecutor<T> for TransformShaperRgbFloatInOut<T, SRC_LAYOUT, DST_LAYOUT>
where
u32: AsPrimitive<T>,
f32: AsPrimitive<T>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
use crate::mlaf::mlaf;
let src_cn = Layout::from(SRC_LAYOUT);
let dst_cn = Layout::from(DST_LAYOUT);
let src_channels = src_cn.channels();
let dst_channels = dst_cn.channels();
if src.len() / src_channels != dst.len() / dst_channels {
return Err(CmsError::LaneSizeMismatch);
}
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let transform = self.profile.adaptation_matrix;
let max_colors: T = ((1 << self.bit_depth) - 1).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let mut src_rgb = Rgb::new(
src[src_cn.r_i()].as_(),
src[src_cn.g_i()].as_(),
src[src_cn.b_i()].as_(),
);
src_rgb = self.profile.linear_evaluator.evaluate_tristimulus(src_rgb);
let r = src_rgb.r;
let g = src_rgb.g;
let b = src_rgb.b;
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_colors
};
let new_r = mlaf(
mlaf(r * transform.v[0][0], g, transform.v[0][1]),
b,
transform.v[0][2],
);
let new_g = mlaf(
mlaf(r * transform.v[1][0], g, transform.v[1][1]),
b,
transform.v[1][2],
);
let new_b = mlaf(
mlaf(r * transform.v[2][0], g, transform.v[2][1]),
b,
transform.v[2][2],
);
let mut rgb = Rgb::new(new_r, new_g, new_b);
rgb = self.profile.gamma_evaluator.evaluate_tristimulus(rgb);
dst[dst_cn.r_i()] = rgb.r.as_();
dst[dst_cn.g_i()] = rgb.g.as_();
dst[dst_cn.b_i()] = rgb.b.as_();
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
}
Ok(())
}
}

View File

@@ -1,266 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#![allow(dead_code)]
use crate::conversions::LutBarycentricReduction;
use crate::conversions::interpolator::{BarycentricWeight, MultidimensionalInterpolation};
use crate::conversions::lut_transforms::Lut3x3Factory;
use crate::transform::PointeeSizeExpressible;
use crate::{
BarycentricWeightScale, CmsError, DataColorSpace, InterpolationMethod, Layout,
TransformExecutor, TransformOptions,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) struct TransformLut3x3<
T,
U,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> {
pub(crate) lut: Vec<f32>,
pub(crate) _phantom: PhantomData<T>,
pub(crate) _phantom1: PhantomData<U>,
pub(crate) interpolation_method: InterpolationMethod,
pub(crate) weights: Box<[BarycentricWeight<f32>; BINS]>,
pub(crate) color_space: DataColorSpace,
pub(crate) is_linear: bool,
}
impl<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
U: AsPrimitive<usize>,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> TransformLut3x3<T, U, SRC_LAYOUT, DST_LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, U>,
{
#[inline(never)]
fn transform_chunk(
&self,
src: &[T],
dst: &mut [T],
interpolator: Box<dyn MultidimensionalInterpolation + Send + Sync>,
) {
let src_cn = Layout::from(SRC_LAYOUT);
let src_channels = src_cn.channels();
let dst_cn = Layout::from(DST_LAYOUT);
let dst_channels = dst_cn.channels();
let value_scale = ((1 << BIT_DEPTH) - 1) as f32;
let max_value = ((1u32 << BIT_DEPTH) - 1).as_();
for (src, dst) in src
.chunks_exact(src_channels)
.zip(dst.chunks_exact_mut(dst_channels))
{
let x = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[src_cn.r_i()],
);
let y = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[src_cn.g_i()],
);
let z = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[src_cn.b_i()],
);
let a = if src_channels == 4 {
src[src_cn.a_i()]
} else {
max_value
};
let v = interpolator.inter3(
&self.lut,
&self.weights[x.as_()],
&self.weights[y.as_()],
&self.weights[z.as_()],
);
if T::FINITE {
let r = v * value_scale + 0.5;
dst[dst_cn.r_i()] = r.v[0].min(value_scale).max(0.).as_();
dst[dst_cn.g_i()] = r.v[1].min(value_scale).max(0.).as_();
dst[dst_cn.b_i()] = r.v[2].min(value_scale).max(0.).as_();
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
} else {
dst[dst_cn.r_i()] = v.v[0].as_();
dst[dst_cn.g_i()] = v.v[1].as_();
dst[dst_cn.b_i()] = v.v[2].as_();
if dst_channels == 4 {
dst[dst_cn.a_i()] = a;
}
}
}
}
}
impl<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
U: AsPrimitive<usize>,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> TransformExecutor<T>
for TransformLut3x3<T, U, SRC_LAYOUT, DST_LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, U>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let src_cn = Layout::from(SRC_LAYOUT);
let src_channels = src_cn.channels();
let dst_cn = Layout::from(DST_LAYOUT);
let dst_channels = dst_cn.channels();
if src.len() % src_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % dst_channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let src_chunks = src.len() / src_channels;
let dst_chunks = dst.len() / dst_channels;
if src_chunks != dst_chunks {
return Err(CmsError::LaneSizeMismatch);
}
if self.color_space == DataColorSpace::Lab
|| (self.is_linear && self.color_space == DataColorSpace::Rgb)
|| self.color_space == DataColorSpace::Xyz
{
use crate::conversions::interpolator::Trilinear;
self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
} else {
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
use crate::conversions::interpolator::Tetrahedral;
self.transform_chunk(src, dst, Box::new(Tetrahedral::<GRID_SIZE> {}));
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
use crate::conversions::interpolator::Pyramidal;
self.transform_chunk(src, dst, Box::new(Pyramidal::<GRID_SIZE> {}));
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
use crate::conversions::interpolator::Prismatic;
self.transform_chunk(src, dst, Box::new(Prismatic::<GRID_SIZE> {}));
}
InterpolationMethod::Linear => {
use crate::conversions::interpolator::Trilinear;
self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
}
}
}
Ok(())
}
}
pub(crate) struct DefaultLut3x3Factory {}
impl Lut3x3Factory for DefaultLut3x3Factory {
fn make_transform_3x3<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
const SRC_LAYOUT: u8,
const DST_LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
>(
lut: Vec<f32>,
options: TransformOptions,
color_space: DataColorSpace,
is_linear: bool,
) -> Box<dyn TransformExecutor<T> + Send + Sync>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>,
{
match options.barycentric_weight_scale {
BarycentricWeightScale::Low => Box::new(TransformLut3x3::<
T,
u8,
SRC_LAYOUT,
DST_LAYOUT,
GRID_SIZE,
BIT_DEPTH,
256,
256,
> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
color_space,
is_linear,
}),
#[cfg(feature = "options")]
BarycentricWeightScale::High => Box::new(TransformLut3x3::<
T,
u16,
SRC_LAYOUT,
DST_LAYOUT,
GRID_SIZE,
BIT_DEPTH,
65536,
65536,
> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
color_space,
is_linear,
}),
}
}
}

View File

@@ -1,274 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::LutBarycentricReduction;
use crate::conversions::interpolator::{BarycentricWeight, MultidimensionalInterpolation};
use crate::transform::PointeeSizeExpressible;
use crate::{
BarycentricWeightScale, CmsError, DataColorSpace, InterpolationMethod, Layout,
TransformExecutor, TransformOptions,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) struct TransformLut3x4<
T,
U: AsPrimitive<usize>,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> {
pub(crate) lut: Vec<f32>,
pub(crate) _phantom: PhantomData<T>,
pub(crate) _phantom1: PhantomData<U>,
pub(crate) interpolation_method: InterpolationMethod,
pub(crate) weights: Box<[BarycentricWeight<f32>; BINS]>,
pub(crate) color_space: DataColorSpace,
pub(crate) is_linear: bool,
}
impl<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
U: AsPrimitive<usize>,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> TransformLut3x4<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, U>,
{
#[inline(never)]
fn transform_chunk(
&self,
src: &[T],
dst: &mut [T],
interpolator: Box<dyn MultidimensionalInterpolation + Send + Sync>,
) {
let cn = Layout::from(LAYOUT);
let channels = cn.channels();
let value_scale = ((1 << BIT_DEPTH) - 1) as f32;
for (src, dst) in src.chunks_exact(channels).zip(dst.chunks_exact_mut(4)) {
let x = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[cn.r_i()],
);
let y = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[cn.g_i()],
);
let z = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[cn.b_i()],
);
let v = interpolator.inter4(
&self.lut,
&self.weights[x.as_()],
&self.weights[y.as_()],
&self.weights[z.as_()],
);
if T::FINITE {
let r = v * value_scale + 0.5;
dst[0] = r.v[0].min(value_scale).max(0.).as_();
dst[1] = r.v[1].min(value_scale).max(0.).as_();
dst[2] = r.v[2].min(value_scale).max(0.).as_();
dst[3] = r.v[3].min(value_scale).max(0.).as_();
} else {
dst[0] = v.v[0].as_();
dst[1] = v.v[1].as_();
dst[2] = v.v[2].as_();
dst[3] = v.v[3].as_();
}
}
}
}
impl<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
U: AsPrimitive<usize>,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> TransformExecutor<T>
for TransformLut3x4<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, U>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let cn = Layout::from(LAYOUT);
let channels = cn.channels();
if src.len() % channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % 4 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let src_chunks = src.len() / channels;
let dst_chunks = dst.len() / 4;
if src_chunks != dst_chunks {
return Err(CmsError::LaneSizeMismatch);
}
if self.color_space == DataColorSpace::Lab
|| (self.is_linear && self.color_space == DataColorSpace::Rgb)
|| self.color_space == DataColorSpace::Xyz
{
use crate::conversions::interpolator::Trilinear;
self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
} else {
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
use crate::conversions::interpolator::Tetrahedral;
self.transform_chunk(src, dst, Box::new(Tetrahedral::<GRID_SIZE> {}));
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
use crate::conversions::interpolator::Pyramidal;
self.transform_chunk(src, dst, Box::new(Pyramidal::<GRID_SIZE> {}));
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
use crate::conversions::interpolator::Prismatic;
self.transform_chunk(src, dst, Box::new(Prismatic::<GRID_SIZE> {}));
}
InterpolationMethod::Linear => {
use crate::conversions::interpolator::Trilinear;
self.transform_chunk(src, dst, Box::new(Trilinear::<GRID_SIZE> {}));
}
}
}
Ok(())
}
}
pub(crate) fn make_transform_3x4<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
>(
layout: Layout,
lut: Vec<f32>,
options: TransformOptions,
color_space: DataColorSpace,
is_linear: bool,
) -> Box<dyn TransformExecutor<T> + Sync + Send>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>,
{
match layout {
Layout::Rgb => match options.barycentric_weight_scale {
BarycentricWeightScale::Low => Box::new(TransformLut3x4::<
T,
u8,
{ Layout::Rgb as u8 },
GRID_SIZE,
BIT_DEPTH,
256,
256,
> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
color_space,
is_linear,
}),
#[cfg(feature = "options")]
BarycentricWeightScale::High => Box::new(TransformLut3x4::<
T,
u16,
{ Layout::Rgb as u8 },
GRID_SIZE,
BIT_DEPTH,
65536,
65536,
> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
color_space,
is_linear,
}),
},
Layout::Rgba => match options.barycentric_weight_scale {
BarycentricWeightScale::Low => Box::new(TransformLut3x4::<
T,
u8,
{ Layout::Rgba as u8 },
GRID_SIZE,
BIT_DEPTH,
256,
256,
> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
color_space,
is_linear,
}),
#[cfg(feature = "options")]
BarycentricWeightScale::High => Box::new(TransformLut3x4::<
T,
u16,
{ Layout::Rgba as u8 },
GRID_SIZE,
BIT_DEPTH,
65536,
65536,
> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
color_space,
is_linear,
}),
},
_ => unimplemented!(),
}
}

View File

@@ -1,351 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::conversions::interpolator::*;
use crate::conversions::lut_transforms::Lut4x3Factory;
use crate::math::{FusedMultiplyAdd, FusedMultiplyNegAdd, m_clamp};
use crate::{
BarycentricWeightScale, CmsError, DataColorSpace, InterpolationMethod, Layout,
PointeeSizeExpressible, TransformExecutor, TransformOptions, Vector3f,
};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
pub(crate) trait Vector3fCmykLerp {
fn interpolate(a: Vector3f, b: Vector3f, t: f32, scale: f32) -> Vector3f;
}
#[allow(unused)]
#[derive(Copy, Clone, Default)]
struct DefaultVector3fLerp;
impl Vector3fCmykLerp for DefaultVector3fLerp {
#[inline(always)]
fn interpolate(a: Vector3f, b: Vector3f, t: f32, scale: f32) -> Vector3f {
let t = Vector3f::from(t);
let inter = a.neg_mla(a, t).mla(b, t);
let mut new_vec = Vector3f::from(0.5).mla(inter, Vector3f::from(scale));
new_vec.v[0] = m_clamp(new_vec.v[0], 0.0, scale);
new_vec.v[1] = m_clamp(new_vec.v[1], 0.0, scale);
new_vec.v[2] = m_clamp(new_vec.v[2], 0.0, scale);
new_vec
}
}
#[allow(unused)]
#[derive(Copy, Clone, Default)]
pub(crate) struct NonFiniteVector3fLerp;
impl Vector3fCmykLerp for NonFiniteVector3fLerp {
#[inline(always)]
fn interpolate(a: Vector3f, b: Vector3f, t: f32, _: f32) -> Vector3f {
let t = Vector3f::from(t);
a.neg_mla(a, t).mla(b, t)
}
}
#[allow(unused)]
#[derive(Copy, Clone, Default)]
pub(crate) struct NonFiniteVector3fLerpUnbound;
impl Vector3fCmykLerp for NonFiniteVector3fLerpUnbound {
#[inline(always)]
fn interpolate(a: Vector3f, b: Vector3f, t: f32, _: f32) -> Vector3f {
let t = Vector3f::from(t);
a.neg_mla(a, t).mla(b, t)
}
}
#[allow(unused)]
struct TransformLut4To3<
T,
U,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> {
lut: Vec<f32>,
_phantom: PhantomData<T>,
_phantom1: PhantomData<U>,
interpolation_method: InterpolationMethod,
weights: Box<[BarycentricWeight<f32>; BINS]>,
color_space: DataColorSpace,
is_linear: bool,
}
#[allow(unused)]
impl<
T: Copy + AsPrimitive<f32> + Default,
U: AsPrimitive<usize>,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> TransformLut4To3<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, U>,
{
#[inline(never)]
fn transform_chunk<Interpolation: Vector3fCmykLerp>(
&self,
src: &[T],
dst: &mut [T],
interpolator: Box<dyn MultidimensionalInterpolation + Send + Sync>,
) {
let cn = Layout::from(LAYOUT);
let channels = cn.channels();
let grid_size = GRID_SIZE as i32;
let grid_size3 = grid_size * grid_size * grid_size;
let value_scale = ((1 << BIT_DEPTH) - 1) as f32;
let max_value = ((1 << BIT_DEPTH) - 1u32).as_();
for (src, dst) in src.chunks_exact(4).zip(dst.chunks_exact_mut(channels)) {
let c = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[0],
);
let m = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[1],
);
let y = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[2],
);
let k = <() as LutBarycentricReduction<T, U>>::reduce::<BIT_DEPTH, BARYCENTRIC_BINS>(
src[3],
);
let k_weights = self.weights[k.as_()];
let w: i32 = k_weights.x;
let w_n: i32 = k_weights.x_n;
let t: f32 = k_weights.w;
let table1 = &self.lut[(w * grid_size3 * 3) as usize..];
let table2 = &self.lut[(w_n * grid_size3 * 3) as usize..];
let r1 = interpolator.inter3(
table1,
&self.weights[c.as_()],
&self.weights[m.as_()],
&self.weights[y.as_()],
);
let r2 = interpolator.inter3(
table2,
&self.weights[c.as_()],
&self.weights[m.as_()],
&self.weights[y.as_()],
);
let r = Interpolation::interpolate(r1, r2, t, value_scale);
dst[cn.r_i()] = r.v[0].as_();
dst[cn.g_i()] = r.v[1].as_();
dst[cn.b_i()] = r.v[2].as_();
if channels == 4 {
dst[cn.a_i()] = max_value;
}
}
}
}
#[allow(unused)]
impl<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible,
U: AsPrimitive<usize>,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
const BINS: usize,
const BARYCENTRIC_BINS: usize,
> TransformExecutor<T>
for TransformLut4To3<T, U, LAYOUT, GRID_SIZE, BIT_DEPTH, BINS, BARYCENTRIC_BINS>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, U>,
{
fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
let cn = Layout::from(LAYOUT);
let channels = cn.channels();
if src.len() % 4 != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
if dst.len() % channels != 0 {
return Err(CmsError::LaneMultipleOfChannels);
}
let src_chunks = src.len() / 4;
let dst_chunks = dst.len() / channels;
if src_chunks != dst_chunks {
return Err(CmsError::LaneSizeMismatch);
}
if self.color_space == DataColorSpace::Lab
|| (self.is_linear && self.color_space == DataColorSpace::Rgb)
|| self.color_space == DataColorSpace::Xyz
{
if T::FINITE {
self.transform_chunk::<DefaultVector3fLerp>(
src,
dst,
Box::new(Trilinear::<GRID_SIZE> {}),
);
} else {
self.transform_chunk::<NonFiniteVector3fLerp>(
src,
dst,
Box::new(Trilinear::<GRID_SIZE> {}),
);
}
} else {
match self.interpolation_method {
#[cfg(feature = "options")]
InterpolationMethod::Tetrahedral => {
if T::FINITE {
self.transform_chunk::<DefaultVector3fLerp>(
src,
dst,
Box::new(Tetrahedral::<GRID_SIZE> {}),
);
} else {
self.transform_chunk::<NonFiniteVector3fLerp>(
src,
dst,
Box::new(Tetrahedral::<GRID_SIZE> {}),
);
}
}
#[cfg(feature = "options")]
InterpolationMethod::Pyramid => {
if T::FINITE {
self.transform_chunk::<DefaultVector3fLerp>(
src,
dst,
Box::new(Pyramidal::<GRID_SIZE> {}),
);
} else {
self.transform_chunk::<NonFiniteVector3fLerp>(
src,
dst,
Box::new(Pyramidal::<GRID_SIZE> {}),
);
}
}
#[cfg(feature = "options")]
InterpolationMethod::Prism => {
if T::FINITE {
self.transform_chunk::<DefaultVector3fLerp>(
src,
dst,
Box::new(Prismatic::<GRID_SIZE> {}),
);
} else {
self.transform_chunk::<NonFiniteVector3fLerp>(
src,
dst,
Box::new(Prismatic::<GRID_SIZE> {}),
);
}
}
InterpolationMethod::Linear => {
if T::FINITE {
self.transform_chunk::<DefaultVector3fLerp>(
src,
dst,
Box::new(Trilinear::<GRID_SIZE> {}),
);
} else {
self.transform_chunk::<NonFiniteVector3fLerp>(
src,
dst,
Box::new(Trilinear::<GRID_SIZE> {}),
);
}
}
}
}
Ok(())
}
}
#[allow(dead_code)]
pub(crate) struct DefaultLut4x3Factory {}
#[allow(dead_code)]
impl Lut4x3Factory for DefaultLut4x3Factory {
fn make_transform_4x3<
T: Copy + AsPrimitive<f32> + Default + PointeeSizeExpressible + 'static + Send + Sync,
const LAYOUT: u8,
const GRID_SIZE: usize,
const BIT_DEPTH: usize,
>(
lut: Vec<f32>,
options: TransformOptions,
color_space: DataColorSpace,
is_linear: bool,
) -> Box<dyn TransformExecutor<T> + Sync + Send>
where
f32: AsPrimitive<T>,
u32: AsPrimitive<T>,
(): LutBarycentricReduction<T, u8>,
(): LutBarycentricReduction<T, u16>,
{
match options.barycentric_weight_scale {
BarycentricWeightScale::Low => {
Box::new(
TransformLut4To3::<T, u8, LAYOUT, GRID_SIZE, BIT_DEPTH, 256, 256> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_ranged_256::<GRID_SIZE>(),
color_space,
is_linear,
},
)
}
#[cfg(feature = "options")]
BarycentricWeightScale::High => {
Box::new(
TransformLut4To3::<T, u16, LAYOUT, GRID_SIZE, BIT_DEPTH, 65536, 65536> {
lut,
_phantom: PhantomData,
_phantom1: PhantomData,
interpolation_method: options.interpolation_method,
weights: BarycentricWeight::<f32>::create_binned::<GRID_SIZE, 65536>(),
color_space,
is_linear,
},
)
}
}
}
}

View File

@@ -1,61 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::{CmsError, InPlaceStage, Lab, Xyz};
#[derive(Default)]
pub(crate) struct StageLabToXyz {}
impl InPlaceStage for StageLabToXyz {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
for dst in dst.chunks_exact_mut(3) {
let lab = Lab::new(dst[0], dst[1], dst[2]);
let xyz = lab.to_pcs_xyz();
dst[0] = xyz.x;
dst[1] = xyz.y;
dst[2] = xyz.z;
}
Ok(())
}
}
#[derive(Default)]
pub(crate) struct StageXyzToLab {}
impl InPlaceStage for StageXyzToLab {
fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError> {
for dst in dst.chunks_exact_mut(3) {
let xyz = Xyz::new(dst[0], dst[1], dst[2]);
let lab = Lab::from_pcs_xyz(xyz);
dst[0] = lab.l;
dst[1] = lab.a;
dst[2] = lab.b;
}
Ok(())
}
}

154
deps/moxcms/src/dat.rs vendored
View File

@@ -1,154 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::CmsError;
use crate::writer::write_u16_be;
use std::time::{SystemTime, UNIX_EPOCH};
#[repr(C)]
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)]
pub struct ColorDateTime {
pub year: u16,
pub month: u16,
pub day_of_the_month: u16,
pub hours: u16,
pub minutes: u16,
pub seconds: u16,
}
fn is_leap(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
fn days_in_month(year: i32, month: i32) -> i32 {
match month {
1 => 31,
2 => {
if is_leap(year) {
29
} else {
28
}
}
3 => 31,
4 => 30,
5 => 31,
6 => 30,
7 => 31,
8 => 31,
9 => 30,
10 => 31,
11 => 30,
12 => 31,
_ => unreachable!("Unknown month"),
}
}
impl ColorDateTime {
/// Parses slice for date time
pub fn new_from_slice(slice: &[u8]) -> Result<ColorDateTime, CmsError> {
if slice.len() != 12 {
return Err(CmsError::InvalidProfile);
}
let year = u16::from_be_bytes([slice[0], slice[1]]);
let month = u16::from_be_bytes([slice[2], slice[3]]);
let day_of_the_month = u16::from_be_bytes([slice[4], slice[5]]);
let hours = u16::from_be_bytes([slice[6], slice[7]]);
let minutes = u16::from_be_bytes([slice[8], slice[9]]);
let seconds = u16::from_be_bytes([slice[10], slice[11]]);
Ok(ColorDateTime {
year,
month,
day_of_the_month,
hours,
minutes,
seconds,
})
}
/// Creates a new `ColorDateTime` from the current system time (UTC)
pub fn now() -> Self {
let now = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(v) => v,
Err(_) => return Self::default(),
};
let mut days = (now.as_secs() / 86_400) as i64;
let secs_of_day = (now.as_secs() % 86_400) as i64;
let mut year = 1970;
loop {
let year_days = if is_leap(year) { 366 } else { 365 };
if days >= year_days {
days -= year_days;
year += 1;
} else {
break;
}
}
let mut month = 1;
loop {
let mdays = days_in_month(year, month);
if days >= mdays as i64 {
days -= mdays as i64;
month += 1;
} else {
break;
}
}
let day = days + 1; // days from zero based to 1 base
let hour = secs_of_day / 3600;
let min = (secs_of_day % 3600) / 60;
let sec = secs_of_day % 60;
Self {
year: year as u16,
month: month as u16,
day_of_the_month: day as u16,
hours: hour as u16,
minutes: min as u16,
seconds: sec as u16,
}
}
#[inline]
pub(crate) fn encode(&self, into: &mut Vec<u8>) {
let year = self.year;
let month = self.month;
let day_of_the_month = self.day_of_the_month;
let hours = self.hours;
let minutes = self.minutes;
let seconds = self.seconds;
write_u16_be(into, year);
write_u16_be(into, month);
write_u16_be(into, day_of_the_month);
write_u16_be(into, hours);
write_u16_be(into, minutes);
write_u16_be(into, seconds);
}
}

View File

@@ -1,541 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::chad::BRADFORD_D;
use crate::cicp::create_rec709_parametric;
use crate::trc::{ToneReprCurve, curve_from_gamma};
use crate::{
CicpColorPrimaries, CicpProfile, ColorPrimaries, ColorProfile, DataColorSpace,
LocalizableString, Matrix3d, MatrixCoefficients, ProfileClass, ProfileText, RenderingIntent,
TransferCharacteristics, XyY,
};
use pxfm::{copysignk, exp, floor, pow};
/// 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()`
const fn white_point_from_temperature(temp_k: i32) -> XyY {
let mut white_point = XyY {
x: 0.,
y: 0.,
yb: 0.,
};
// No optimization provided.
let temp_k = temp_k as f64; // Square
let temp_k2 = temp_k * temp_k; // Cube
let temp_k3 = temp_k2 * temp_k;
// For correlated color temperature (T) between 4000K and 7000K:
let x = if temp_k > 4000.0 && temp_k <= 7000.0 {
-4.6070 * (1E9 / temp_k3) + 2.9678 * (1E6 / temp_k2) + 0.09911 * (1E3 / temp_k) + 0.244063
} else if temp_k > 7000.0 && temp_k <= 25000.0 {
-2.0064 * (1E9 / temp_k3) + 1.9018 * (1E6 / temp_k2) + 0.24748 * (1E3 / temp_k) + 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.yb = -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.yb = 1.0;
white_point
}
pub const WHITE_POINT_D50: XyY = white_point_from_temperature(5003);
pub const WHITE_POINT_D60: XyY = white_point_from_temperature(6000);
pub const WHITE_POINT_D65: XyY = white_point_from_temperature(6504);
pub const WHITE_POINT_DCI_P3: XyY = white_point_from_temperature(6300);
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
// Perceptual Quantization / SMPTE standard ST.2084
#[inline]
const fn pq_curve(x: f64) -> f64 {
const M1: f64 = 2610.0 / 16384.0;
const M2: f64 = (2523.0 / 4096.0) * 128.0;
const C1: f64 = 3424.0 / 4096.0;
const C2: f64 = (2413.0 / 4096.0) * 32.0;
const C3: f64 = (2392.0 / 4096.0) * 32.0;
if x == 0.0 {
return 0.0;
}
let sign = x;
let x = x.abs();
let xpo = pow(x, 1.0 / M2);
let num = (xpo - C1).max(0.0);
let den = C2 - C3 * xpo;
let res = pow(num / den, 1.0 / M1);
copysignk(res, sign)
}
pub(crate) const fn build_trc_table_pq() -> [u16; 4096] {
let mut table = [0u16; 4096];
const NUM_ENTRIES: usize = 4096;
let mut i = 0usize;
while i < NUM_ENTRIES {
let x: f64 = i as f64 / (NUM_ENTRIES - 1) as f64;
let y: f64 = pq_curve(x);
let mut output: f64;
output = y * 65535.0 + 0.5;
if output > 65535.0 {
output = 65535.0
}
if output < 0.0 {
output = 0.0
}
table[i] = floor(output) as u16;
i += 1;
}
table
}
pub(crate) const fn build_trc_table_hlg() -> [u16; 4096] {
let mut table = [0u16; 4096];
const NUM_ENTRIES: usize = 4096;
let mut i = 0usize;
while i < NUM_ENTRIES {
let x: f64 = i as f64 / (NUM_ENTRIES - 1) as f64;
let y: f64 = hlg_curve(x);
let mut output: f64;
output = y * 65535.0 + 0.5;
if output > 65535.0 {
output = 65535.0
}
if output < 0.0 {
output = 0.0
}
table[i] = floor(output) as u16;
i += 1;
}
table
}
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
// Hybrid Log-Gamma
const fn hlg_curve(x: f64) -> f64 {
const BETA: f64 = 0.04;
const RA: f64 = 5.591816309728916; // 1.0 / A where A = 0.17883277
const B: f64 = 0.28466892; // 1.0 - 4.0 * A
const C: f64 = 0.5599107295; // 0,5 aln(4a)
let e = (x * (1.0 - BETA) + BETA).max(0.0);
if e == 0.0 {
return 0.0;
}
let sign = e.abs();
let res = if e <= 0.5 {
e * e / 3.0
} else {
(exp((e - C) * RA) + B) / 12.0
};
copysignk(res, sign)
}
/// Perceptual Quantizer Lookup table
pub const PQ_LUT_TABLE: [u16; 4096] = build_trc_table_pq();
/// Hybrid Log Gamma Lookup table
pub const HLG_LUT_TABLE: [u16; 4096] = build_trc_table_hlg();
impl ColorProfile {
const SRGB_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::BT_709);
const DISPLAY_P3_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::SMPTE_432);
const ADOBE_RGB_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::ADOBE_RGB);
const DCI_P3_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_DCI_P3, ColorPrimaries::DCI_P3);
const PRO_PHOTO_RGB_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_D50, ColorPrimaries::PRO_PHOTO_RGB);
const BT2020_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_D65, ColorPrimaries::BT_2020);
const ACES_2065_1_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_D60, ColorPrimaries::ACES_2065_1);
const ACES_CG_COLORANTS: Matrix3d =
ColorProfile::colorants_matrix(WHITE_POINT_D60, ColorPrimaries::ACES_CG);
#[inline]
fn basic_rgb_profile() -> ColorProfile {
ColorProfile {
profile_class: ProfileClass::DisplayDevice,
rendering_intent: RenderingIntent::Perceptual,
color_space: DataColorSpace::Rgb,
pcs: DataColorSpace::Xyz,
chromatic_adaptation: Some(BRADFORD_D),
white_point: WHITE_POINT_D50.to_xyzd(),
..Default::default()
}
}
/// Creates new profile from CICP
pub fn new_from_cicp(cicp_color_primaries: CicpProfile) -> ColorProfile {
let mut basic = ColorProfile::basic_rgb_profile();
basic.update_rgb_colorimetry_from_cicp(cicp_color_primaries);
basic
}
/// Creates new sRGB profile
pub fn new_srgb() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::SRGB_COLORANTS);
let curve =
ToneReprCurve::Parametric(vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045]);
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
profile.cicp = Some(CicpProfile {
color_primaries: CicpColorPrimaries::Bt709,
transfer_characteristics: TransferCharacteristics::Srgb,
matrix_coefficients: MatrixCoefficients::Bt709,
full_range: false,
});
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"sRGB IEC61966-2.1".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new Adobe RGB profile
pub fn new_adobe_rgb() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::ADOBE_RGB_COLORANTS);
let curve = curve_from_gamma(2.19921875f32);
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
profile.white_point = WHITE_POINT_D50.to_xyzd();
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Adobe RGB 1998".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new Display P3 profile
pub fn new_display_p3() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::DISPLAY_P3_COLORANTS);
let curve =
ToneReprCurve::Parametric(vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045]);
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
profile.cicp = Some(CicpProfile {
color_primaries: CicpColorPrimaries::Smpte431,
transfer_characteristics: TransferCharacteristics::Srgb,
matrix_coefficients: MatrixCoefficients::Bt709,
full_range: false,
});
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Display P3".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new Display P3 PQ profile
pub fn new_display_p3_pq() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::DISPLAY_P3_COLORANTS);
let curve = ToneReprCurve::Lut(PQ_LUT_TABLE.to_vec());
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
profile.cicp = Some(CicpProfile {
color_primaries: CicpColorPrimaries::Smpte431,
transfer_characteristics: TransferCharacteristics::Smpte2084,
matrix_coefficients: MatrixCoefficients::Bt709,
full_range: false,
});
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Display P3 PQ".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new DCI P3 profile
pub fn new_dci_p3() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::DCI_P3_COLORANTS);
let curve = curve_from_gamma(2.6f32);
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_DCI_P3.to_xyzd());
profile.cicp = Some(CicpProfile {
color_primaries: CicpColorPrimaries::Smpte432,
transfer_characteristics: TransferCharacteristics::Srgb,
matrix_coefficients: MatrixCoefficients::Bt709,
full_range: false,
});
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"DCI P3".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new ProPhoto RGB profile
pub fn new_pro_photo_rgb() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::PRO_PHOTO_RGB_COLORANTS);
let curve = curve_from_gamma(1.8f32);
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D50.to_xyzd());
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"ProPhoto RGB".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new Bt.2020 profile
pub fn new_bt2020() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::BT2020_COLORANTS);
let curve = ToneReprCurve::Parametric(create_rec709_parametric().to_vec());
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Rec.2020".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new Bt.2020 PQ profile
pub fn new_bt2020_pq() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::BT2020_COLORANTS);
let curve = ToneReprCurve::Lut(PQ_LUT_TABLE.to_vec());
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
profile.cicp = Some(CicpProfile {
color_primaries: CicpColorPrimaries::Bt2020,
transfer_characteristics: TransferCharacteristics::Smpte2084,
matrix_coefficients: MatrixCoefficients::Bt709,
full_range: false,
});
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Rec.2020 PQ".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new Bt.2020 HLG profile
pub fn new_bt2020_hlg() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::BT2020_COLORANTS);
let curve = ToneReprCurve::Lut(HLG_LUT_TABLE.to_vec());
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D65.to_xyzd());
profile.cicp = Some(CicpProfile {
color_primaries: CicpColorPrimaries::Bt2020,
transfer_characteristics: TransferCharacteristics::Hlg,
matrix_coefficients: MatrixCoefficients::Bt709,
full_range: false,
});
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Rec.2020 HLG".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new Monochrome profile
pub fn new_gray_with_gamma(gamma: f32) -> ColorProfile {
ColorProfile {
gray_trc: Some(curve_from_gamma(gamma)),
profile_class: ProfileClass::DisplayDevice,
rendering_intent: RenderingIntent::Perceptual,
color_space: DataColorSpace::Gray,
media_white_point: Some(WHITE_POINT_D65.to_xyzd()),
white_point: WHITE_POINT_D50.to_xyzd(),
chromatic_adaptation: Some(BRADFORD_D),
copyright: Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)])),
..Default::default()
}
}
/// Creates new ACES 2065-1/AP0 profile
pub fn new_aces_aces_2065_1_linear() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::ACES_2065_1_COLORANTS);
let curve = ToneReprCurve::Lut(vec![]);
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D60.to_xyzd());
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"ACES 2065-1".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
/// Creates new ACEScg profile
pub fn new_aces_cg_linear() -> ColorProfile {
let mut profile = ColorProfile::basic_rgb_profile();
profile.update_colorants(ColorProfile::ACES_CG_COLORANTS);
let curve = ToneReprCurve::Lut(vec![]);
profile.red_trc = Some(curve.clone());
profile.blue_trc = Some(curve.clone());
profile.green_trc = Some(curve);
profile.media_white_point = Some(WHITE_POINT_D60.to_xyzd());
profile.description = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"ACEScg/AP1".to_string(),
)]));
profile.copyright = Some(ProfileText::Localizable(vec![LocalizableString::new(
"en".to_string(),
"US".to_string(),
"Public Domain".to_string(),
)]));
profile
}
}

View File

@@ -1,359 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::Xyz;
use crate::mlaf::mlaf;
use pxfm::{f_atan2f, f_powf, f_sincosf};
/// Darktable UCS JCH ( Darktable Uniform Color Space )
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
pub struct DtUchJch {
pub j: f32,
pub c: f32,
pub h: f32,
}
/// Darktable UCS HSB ( Darktable Uniform Color Space )
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
pub struct DtUchHsb {
pub h: f32,
pub s: f32,
pub b: f32,
}
/// Darktable HCB ( Darktable Uniform Color Space )
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
pub struct DtUchHcb {
pub h: f32,
pub c: f32,
pub b: f32,
}
const DT_UCS_L_STAR_RANGE: f32 = 2.098883786377;
#[inline]
fn y_to_dt_ucs_l_star(y: f32) -> f32 {
let y_hat = f_powf(y, 0.631651345306265);
DT_UCS_L_STAR_RANGE * y_hat / (y_hat + 1.12426773749357)
}
#[inline]
fn dt_ucs_l_star_to_y(x: f32) -> f32 {
f_powf(
1.12426773749357 * x / (DT_UCS_L_STAR_RANGE - x),
1.5831518565279648,
)
}
const L_WHITE: f32 = 0.98805060;
#[inline]
fn dt_ucs_luv_to_ucs_jch(
l_star: f32,
l_white: f32,
u_star_prime: f32,
v_star_prime: f32,
) -> DtUchJch {
let m2: f32 = mlaf(u_star_prime * u_star_prime, v_star_prime, v_star_prime); // square of colorfulness M
// should be JCH[0] = powf(L_star / L_white), cz) but we treat only the case where cz = 1
let j = l_star / l_white;
let c =
15.932993652962535 * f_powf(l_star, 0.6523997524738018) * f_powf(m2, 0.6007557017508491)
/ l_white;
let h = f_atan2f(v_star_prime, u_star_prime);
DtUchJch::new(j, c, h)
}
#[inline]
fn dt_ucs_xy_to_uv(x: f32, y: f32) -> (f32, f32) {
const X_C: [f32; 3] = [-0.783941002840055, 0.745273540913283, 0.318707282433486];
const Y_C: [f32; 3] = [0.277512987809202, -0.205375866083878, 2.16743692732158];
const BIAS: [f32; 3] = [0.153836578598858, -0.165478376301988, 0.291320554395942];
let mut u_c = mlaf(mlaf(BIAS[0], Y_C[0], y), X_C[0], x);
let mut v_c = mlaf(mlaf(BIAS[1], Y_C[1], y), X_C[1], x);
let d_c = mlaf(mlaf(BIAS[2], Y_C[2], y), X_C[2], x);
let div = if d_c >= 0.0 {
d_c.max(f32::MIN)
} else {
d_c.min(-f32::MIN)
};
u_c /= div;
v_c /= div;
const STAR_C: [f32; 2] = [1.39656225667, 1.4513954287];
const STAR_HF_C: [f32; 2] = [1.49217352929, 1.52488637914];
let u_star = STAR_C[0] * u_c / (u_c.abs() + STAR_HF_C[0]);
let v_star = STAR_C[1] * v_c / (v_c.abs() + STAR_HF_C[1]);
// The following is equivalent to a 2D matrix product
let u_star_prime = mlaf(-1.124983854323892 * u_star, -0.980483721769325, v_star);
let v_star_prime = mlaf(1.86323315098672 * u_star, 1.971853092390862, v_star);
(u_star_prime, v_star_prime)
}
impl DtUchJch {
#[inline]
pub fn new(j: f32, c: f32, h: f32) -> DtUchJch {
DtUchJch { j, c, h }
}
#[inline]
pub fn from_xyz(xyz: Xyz) -> DtUchJch {
DtUchJch::from_xyy(xyz.to_xyy())
}
#[inline]
pub fn to_xyz(&self) -> Xyz {
let xyy = self.to_xyy();
Xyz::from_xyy(xyy)
}
#[inline]
pub fn from_xyy(xyy: [f32; 3]) -> DtUchJch {
let l_star = y_to_dt_ucs_l_star(xyy[2]);
// let l_white = y_to_dt_ucs_l_star(1.);
let (u_star_prime, v_star_prime) = dt_ucs_xy_to_uv(xyy[0], xyy[1]);
dt_ucs_luv_to_ucs_jch(l_star, L_WHITE, u_star_prime, v_star_prime)
}
#[inline]
pub fn to_xyy(&self) -> [f32; 3] {
// let l_white: f32 = y_to_dt_ucs_l_star(1.0);
let l_star = (self.j * L_WHITE).max(0.0).min(2.09885);
let m = if l_star != 0. {
f_powf(
self.c * L_WHITE / (15.932993652962535 * f_powf(l_star, 0.6523997524738018)),
0.8322850678616855,
)
} else {
0.
};
let sin_cos_h = f_sincosf(self.h);
let u_star_prime = m * sin_cos_h.1;
let v_star_prime = m * sin_cos_h.0;
// The following is equivalent to a 2D matrix product
let u_star = mlaf(
-5.037522385190711 * u_star_prime,
-2.504856328185843,
v_star_prime,
);
let v_star = mlaf(
4.760029407436461 * u_star_prime,
2.874012963239247,
v_star_prime,
);
const F: [f32; 2] = [1.39656225667, 1.4513954287];
const HF: [f32; 2] = [1.49217352929, 1.52488637914];
let u_c = -HF[0] * u_star / (u_star.abs() - F[0]);
let v_c = -HF[1] * v_star / (v_star.abs() - F[1]);
const U_C: [f32; 3] = [0.167171472114775, -0.150959086409163, 0.940254742367256];
const V_C: [f32; 3] = [0.141299802443708, -0.155185060382272, 1.000000000000000];
const BIAS: [f32; 3] = [
-0.00801531300850582,
-0.00843312433578007,
-0.0256325967652889,
];
let mut x = mlaf(mlaf(BIAS[0], V_C[0], v_c), U_C[0], u_c);
let mut y = mlaf(mlaf(BIAS[1], V_C[1], v_c), U_C[1], u_c);
let d = mlaf(mlaf(BIAS[2], V_C[2], v_c), U_C[2], u_c);
let div = if d >= 0.0 {
d.max(f32::MIN)
} else {
d.min(-f32::MIN)
};
x /= div;
y /= div;
let yb = dt_ucs_l_star_to_y(l_star);
[x, y, yb]
}
}
impl DtUchHsb {
#[inline]
pub fn new(h: f32, s: f32, b: f32) -> DtUchHsb {
DtUchHsb { h, s, b }
}
#[inline]
pub fn from_jch(jch: DtUchJch) -> DtUchHsb {
let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
let s = if b > 0. { jch.c / b } else { 0. };
let h = jch.h;
DtUchHsb::new(h, s, b)
}
#[inline]
pub fn to_jch(&self) -> DtUchJch {
let h = self.h;
let c = self.s * self.b;
let j = self.b / (f_powf(c, 1.33654221029386) + 1.);
DtUchJch::new(j, c, h)
}
}
impl DtUchHcb {
#[inline]
pub fn new(h: f32, c: f32, b: f32) -> DtUchHcb {
DtUchHcb { h, c, b }
}
#[inline]
pub fn from_jch(jch: DtUchJch) -> DtUchHcb {
let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
let c = jch.c;
let h = jch.h;
DtUchHcb::new(h, c, b)
}
#[inline]
pub fn to_jch(&self) -> DtUchJch {
let h = self.h;
let c = self.c;
let j = self.b / (f_powf(self.c, 1.33654221029386) + 1.);
DtUchJch::new(j, c, h)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_darktable_ucs_jch() {
let xyy = [0.4, 0.2, 0.5];
let ucs = DtUchJch::from_xyy(xyy);
let xyy_rev = ucs.to_xyy();
assert!(
(xyy[0] - xyy_rev[0]).abs() < 1e-5,
"Expected {}, got {}",
xyy[0],
xyy_rev[0]
);
assert!(
(xyy[1] - xyy_rev[1]).abs() < 1e-5,
"Expected {}, got {}",
xyy[1],
xyy_rev[1]
);
assert!(
(xyy[2] - xyy_rev[2]).abs() < 1e-5,
"Expected {}, got {}",
xyy[2],
xyy_rev[2]
);
}
#[test]
fn test_darktable_hsb() {
let jch = DtUchJch::new(0.3, 0.6, 0.4);
let hsb = DtUchHsb::from_jch(jch);
let r_jch = hsb.to_jch();
assert!(
(r_jch.j - jch.j).abs() < 1e-5,
"Expected {}, got {}",
jch.j,
r_jch.j
);
assert!(
(r_jch.c - jch.c).abs() < 1e-5,
"Expected {}, got {}",
jch.c,
r_jch.c
);
assert!(
(r_jch.h - jch.h).abs() < 1e-5,
"Expected {}, got {}",
jch.h,
r_jch.h
);
}
#[test]
fn test_darktable_hcb() {
let jch = DtUchJch::new(0.3, 0.6, 0.4);
let hcb = DtUchHcb::from_jch(jch);
let r_jch = hcb.to_jch();
assert!(
(r_jch.j - jch.j).abs() < 1e-5,
"Expected {}, got {}",
jch.j,
r_jch.j
);
assert!(
(r_jch.c - jch.c).abs() < 1e-5,
"Expected {}, got {}",
jch.c,
r_jch.c
);
assert!(
(r_jch.h - jch.h).abs() < 1e-5,
"Expected {}, got {}",
jch.h,
r_jch.h
);
}
#[test]
fn test_darktable_ucs_jch_from_xyz() {
let xyz = Xyz::new(0.4, 0.2, 0.5);
let ucs = DtUchJch::from_xyz(xyz);
let xyy_rev = ucs.to_xyz();
assert!(
(xyz.x - xyz.x).abs() < 1e-5,
"Expected {}, got {}",
xyz.x,
xyy_rev.x
);
assert!(
(xyz.y - xyz.y).abs() < 1e-5,
"Expected {}, got {}",
xyz.y,
xyy_rev.y
);
assert!(
(xyz.z - xyz.z).abs() < 1e-5,
"Expected {}, got {}",
xyz.z,
xyy_rev.z
);
}
}

141
deps/moxcms/src/err.rs vendored
View File

@@ -1,141 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::RenderingIntent;
use std::error::Error;
use std::fmt::Display;
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub struct MalformedSize {
pub size: usize,
pub expected: usize,
}
#[derive(Debug, Clone, PartialOrd, PartialEq)]
pub enum CmsError {
LaneSizeMismatch,
LaneMultipleOfChannels,
InvalidProfile,
InvalidTrcCurve,
InvalidCicp,
CurveLutIsTooLarge,
ParametricCurveZeroDivision,
InvalidRenderingIntent,
DivisionByZero,
UnsupportedColorPrimaries(u8),
UnsupportedTrc(u8),
InvalidLayout,
UnsupportedProfileConnection,
BuildTransferFunction,
UnsupportedChannelConfiguration,
UnknownTag(u32),
UnknownTagTypeDefinition(u32),
UnsupportedLutRenderingIntent(RenderingIntent),
InvalidAtoBLut,
OverflowingError,
LUTTablesInvalidKind,
MalformedClut(MalformedSize),
MalformedCurveLutTable(MalformedSize),
InvalidInksCountForProfile,
MalformedTrcCurve(String),
OutOfMemory(usize),
}
impl Display for CmsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CmsError::LaneSizeMismatch => f.write_str("Lanes length must match"),
CmsError::LaneMultipleOfChannels => {
f.write_str("Lane length must not be multiple of channel count")
}
CmsError::InvalidProfile => f.write_str("Invalid ICC profile"),
CmsError::InvalidCicp => {
f.write_str("Invalid Code Independent point (CICP) in ICC profile")
}
CmsError::InvalidTrcCurve => f.write_str("Invalid TRC curve"),
CmsError::CurveLutIsTooLarge => f.write_str("Curve Lut is too large"),
CmsError::ParametricCurveZeroDivision => {
f.write_str("Parametric Curve definition causes division by zero")
}
CmsError::InvalidRenderingIntent => f.write_str("Invalid rendering intent"),
CmsError::DivisionByZero => f.write_str("Division by zero"),
CmsError::UnsupportedColorPrimaries(value) => {
f.write_fmt(format_args!("Unsupported color primaries, {value}"))
}
CmsError::UnsupportedTrc(value) => f.write_fmt(format_args!("Unsupported TRC {value}")),
CmsError::InvalidLayout => f.write_str("Invalid layout"),
CmsError::UnsupportedProfileConnection => f.write_str("Unsupported profile connection"),
CmsError::BuildTransferFunction => f.write_str("Can't reconstruct transfer function"),
CmsError::UnsupportedChannelConfiguration => {
f.write_str("Can't reconstruct channel configuration")
}
CmsError::UnknownTag(t) => f.write_fmt(format_args!("Unknown tag: {t}")),
CmsError::UnknownTagTypeDefinition(t) => {
f.write_fmt(format_args!("Unknown tag type definition: {t}"))
}
CmsError::UnsupportedLutRenderingIntent(intent) => f.write_fmt(format_args!(
"Can't find LUT for rendering intent: {intent:?}"
)),
CmsError::InvalidAtoBLut => f.write_str("Invalid A to B Lut"),
CmsError::OverflowingError => {
f.write_str("Overflowing was happen, that is not allowed")
}
CmsError::LUTTablesInvalidKind => f.write_str("All LUT curves must have same kind"),
CmsError::MalformedClut(size) => {
f.write_fmt(format_args!("Invalid CLUT size: {size:?}"))
}
CmsError::MalformedCurveLutTable(size) => {
f.write_fmt(format_args!("Malformed curve LUT size: {size:?}"))
}
CmsError::InvalidInksCountForProfile => {
f.write_str("Invalid inks count for profile was provided")
}
CmsError::MalformedTrcCurve(str) => f.write_str(str),
CmsError::OutOfMemory(capacity) => f.write_fmt(format_args!(
"There is no enough memory to allocate {capacity} bytes"
)),
}
}
}
impl Error for CmsError {}
macro_rules! try_vec {
() => {
Vec::new()
};
($elem:expr; $n:expr) => {{
let mut v = Vec::new();
v.try_reserve_exact($n)
.map_err(|_| crate::err::CmsError::OutOfMemory($n))?;
v.resize($n, $elem);
v
}};
}
pub(crate) use try_vec;

1078
deps/moxcms/src/gamma.rs vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::Rgb;
#[inline]
fn filmlike_clip_rgb_tone(r: &mut f32, g: &mut f32, b: &mut f32, l: f32) {
let new_r = r.min(l);
let new_b = b.min(l);
let new_g = new_b + ((new_r - new_b) * (*g - *b) / (*r - *b));
*r = new_r;
*g = new_g;
*b = new_b;
}
/// Soft clipping out-of-bounds values in S-curve
///
/// Works only on highlights, negative values are skipped
#[inline]
pub fn filmlike_clip(rgb: Rgb<f32>) -> Rgb<f32> {
const L: f32 = 1.;
let mut rgb = rgb;
if rgb.r >= rgb.g {
if rgb.g > rgb.b {
filmlike_clip_rgb_tone(&mut rgb.r, &mut rgb.g, &mut rgb.b, L);
} else if rgb.b > rgb.r {
filmlike_clip_rgb_tone(&mut rgb.b, &mut rgb.r, &mut rgb.g, L);
} else if rgb.b > rgb.g {
filmlike_clip_rgb_tone(&mut rgb.r, &mut rgb.b, &mut rgb.g, L);
} else {
Rgb::new(rgb.r.min(L), rgb.g.min(L), rgb.g);
}
} else if rgb.r >= rgb.b {
filmlike_clip_rgb_tone(&mut rgb.g, &mut rgb.r, &mut rgb.b, L);
} else if rgb.b > rgb.g {
filmlike_clip_rgb_tone(&mut rgb.b, &mut rgb.g, &mut rgb.r, L);
} else {
filmlike_clip_rgb_tone(&mut rgb.g, &mut rgb.b, &mut rgb.r, L);
}
rgb
}

View File

@@ -1,223 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::matan::{
does_curve_have_discontinuity, is_curve_ascending, is_curve_degenerated, is_curve_descending,
is_curve_linear8, is_curve_linear16, is_curve_monotonic,
};
use crate::reader::{
s15_fixed16_number_to_double, uint8_number_to_float_fast, uint16_number_to_float_fast,
};
use crate::{CmsError, LutStore, Matrix3d, ToneReprCurve, Vector3d};
impl LutStore {
pub fn to_clut_f32(&self) -> Vec<f32> {
match self {
LutStore::Store8(store) => store
.iter()
.map(|x| uint8_number_to_float_fast(*x))
.collect(),
LutStore::Store16(store) => store
.iter()
.map(|x| uint16_number_to_float_fast(*x as u32))
.collect(),
}
}
pub(crate) fn is_degenerated(&self, entries: usize, channel: usize) -> bool {
let start = entries * channel;
let end = start + entries;
match &self {
LutStore::Store8(v) => is_curve_degenerated(&v[start..end]),
LutStore::Store16(v) => is_curve_degenerated(&v[start..end]),
}
}
pub(crate) fn is_monotonic(&self, entries: usize, channel: usize) -> bool {
let start = entries * channel;
let end = start + entries;
match &self {
LutStore::Store8(v) => is_curve_monotonic(&v[start..end]),
LutStore::Store16(v) => is_curve_monotonic(&v[start..end]),
}
}
pub(crate) fn have_discontinuities(&self, entries: usize, channel: usize) -> bool {
let start = entries * channel;
let end = start + entries;
match &self {
LutStore::Store8(v) => does_curve_have_discontinuity(&v[start..end]),
LutStore::Store16(v) => does_curve_have_discontinuity(&v[start..end]),
}
}
#[allow(dead_code)]
pub(crate) fn is_linear(&self, entries: usize, channel: usize) -> bool {
let start = entries * channel;
let end = start + entries;
match &self {
LutStore::Store8(v) => is_curve_linear8(&v[start..end]),
LutStore::Store16(v) => is_curve_linear16(&v[start..end]),
}
}
#[allow(dead_code)]
pub(crate) fn is_descending(&self, entries: usize, channel: usize) -> bool {
let start = entries * channel;
let end = start + entries;
match &self {
LutStore::Store8(v) => is_curve_descending(&v[start..end]),
LutStore::Store16(v) => is_curve_descending(&v[start..end]),
}
}
#[allow(dead_code)]
pub(crate) fn is_ascending(&self, entries: usize, channel: usize) -> bool {
let start = entries * channel;
let end = start + entries;
match &self {
LutStore::Store8(v) => is_curve_ascending(&v[start..end]),
LutStore::Store16(v) => is_curve_ascending(&v[start..end]),
}
}
}
impl ToneReprCurve {
pub(crate) fn is_linear(&self) -> bool {
match &self {
ToneReprCurve::Lut(lut) => {
if lut.is_empty() {
return true;
}
if lut.len() == 1 {
let gamma = 1. / crate::trc::u8_fixed_8number_to_float(lut[0]);
if (gamma - 1.).abs() < 1e-4 {
return true;
}
}
is_curve_linear16(lut)
}
ToneReprCurve::Parametric(parametric) => {
if parametric.is_empty() {
return true;
}
if parametric.len() == 1 && parametric[0] == 1. {
return true;
}
false
}
}
}
pub(crate) fn is_monotonic(&self) -> bool {
match &self {
ToneReprCurve::Lut(lut) => is_curve_monotonic(lut),
ToneReprCurve::Parametric(_) => true,
}
}
pub(crate) fn is_degenerated(&self) -> bool {
match &self {
ToneReprCurve::Lut(lut) => is_curve_degenerated(lut),
ToneReprCurve::Parametric(_) => false,
}
}
pub(crate) fn have_discontinuities(&self) -> bool {
match &self {
ToneReprCurve::Lut(lut) => does_curve_have_discontinuity(lut),
ToneReprCurve::Parametric(_) => false,
}
}
}
pub(crate) fn read_matrix_3d(arr: &[u8]) -> Result<Matrix3d, CmsError> {
if arr.len() < 36 {
return Err(CmsError::InvalidProfile);
}
let m_tag = &arr[..36];
let e00 = i32::from_be_bytes([m_tag[0], m_tag[1], m_tag[2], m_tag[3]]);
let e01 = i32::from_be_bytes([m_tag[4], m_tag[5], m_tag[6], m_tag[7]]);
let e02 = i32::from_be_bytes([m_tag[8], m_tag[9], m_tag[10], m_tag[11]]);
let e10 = i32::from_be_bytes([m_tag[12], m_tag[13], m_tag[14], m_tag[15]]);
let e11 = i32::from_be_bytes([m_tag[16], m_tag[17], m_tag[18], m_tag[19]]);
let e12 = i32::from_be_bytes([m_tag[20], m_tag[21], m_tag[22], m_tag[23]]);
let e20 = i32::from_be_bytes([m_tag[24], m_tag[25], m_tag[26], m_tag[27]]);
let e21 = i32::from_be_bytes([m_tag[28], m_tag[29], m_tag[30], m_tag[31]]);
let e22 = i32::from_be_bytes([m_tag[32], m_tag[33], m_tag[34], m_tag[35]]);
Ok(Matrix3d {
v: [
[
s15_fixed16_number_to_double(e00),
s15_fixed16_number_to_double(e01),
s15_fixed16_number_to_double(e02),
],
[
s15_fixed16_number_to_double(e10),
s15_fixed16_number_to_double(e11),
s15_fixed16_number_to_double(e12),
],
[
s15_fixed16_number_to_double(e20),
s15_fixed16_number_to_double(e21),
s15_fixed16_number_to_double(e22),
],
],
})
}
pub(crate) fn read_vector_3d(arr: &[u8]) -> Result<Vector3d, CmsError> {
if arr.len() < 12 {
return Err(CmsError::InvalidProfile);
}
let m_tag = &arr[..12];
let b0 = i32::from_be_bytes([m_tag[0], m_tag[1], m_tag[2], m_tag[3]]);
let b1 = i32::from_be_bytes([m_tag[4], m_tag[5], m_tag[6], m_tag[7]]);
let b2 = i32::from_be_bytes([m_tag[8], m_tag[9], m_tag[10], m_tag[11]]);
Ok(Vector3d {
v: [
s15_fixed16_number_to_double(b0),
s15_fixed16_number_to_double(b1),
s15_fixed16_number_to_double(b2),
],
})
}

View File

@@ -1,192 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::gamma::{pq_from_linearf, pq_to_linearf};
use crate::{Matrix3f, Rgb, Vector3f, Xyz};
const CROSSTALK: Matrix3f = Matrix3f {
v: [[0.92, 0.04, 0.04], [0.04, 0.92, 0.04], [0.04, 0.04, 0.92]],
};
const HPE_LMS: Matrix3f = Matrix3f {
v: [
[0.4002, 0.7076, -0.0808],
[-0.2263, 1.1653, 0.0457],
[0f32, 0f32, 0.9182],
],
};
const XYZ_TO_LMS: Matrix3f = CROSSTALK.mat_mul_const(HPE_LMS);
const LMS_TO_XYZ: Matrix3f = XYZ_TO_LMS.inverse();
const L_LMS_TO_ICTCP: Matrix3f = Matrix3f {
v: [
[2048. / 4096., 2048. / 4096., 0.],
[6610. / 4096., -13613. / 4096., 7003. / 4096.],
[17933. / 4096., -17390. / 4096., -543. / 4096.],
],
};
const ICTCP_TO_L_LMS: Matrix3f = L_LMS_TO_ICTCP.inverse();
#[derive(Copy, Clone, Default, PartialOrd, PartialEq)]
pub struct ICtCp {
/// Lightness
pub i: f32,
/// Tritan
pub ct: f32,
/// Protan
pub cp: f32,
}
impl ICtCp {
#[inline]
pub const fn new(i: f32, ct: f32, cp: f32) -> ICtCp {
ICtCp { i, ct, cp }
}
/// Converts XYZ D65 to ICtCp
#[inline]
pub fn from_xyz(xyz: Xyz) -> ICtCp {
let lms = XYZ_TO_LMS.mul_vector(xyz.to_vector());
let lin_l = pq_from_linearf(lms.v[0]);
let lin_m = pq_from_linearf(lms.v[1]);
let lin_s = pq_from_linearf(lms.v[2]);
let ictcp = L_LMS_TO_ICTCP.mul_vector(Vector3f {
v: [lin_l, lin_m, lin_s],
});
ICtCp {
i: ictcp.v[0],
ct: ictcp.v[1],
cp: ictcp.v[2],
}
}
/// Converts to [ICtCp] from linear light [Rgb]
///
/// Precompute forward matrix by [ICtCp::prepare_to_lms].
/// D65 white point is assumed.
#[inline]
pub fn from_linear_rgb(rgb: Rgb<f32>, matrix: Matrix3f) -> ICtCp {
let lms = matrix.mul_vector(rgb.to_vector());
let lin_l = pq_from_linearf(lms.v[0]);
let lin_m = pq_from_linearf(lms.v[1]);
let lin_s = pq_from_linearf(lms.v[2]);
let ictcp = L_LMS_TO_ICTCP.mul_vector(Vector3f {
v: [lin_l, lin_m, lin_s],
});
ICtCp {
i: ictcp.v[0],
ct: ictcp.v[1],
cp: ictcp.v[2],
}
}
/// Converts [ICtCp] to [Rgb]
///
/// Precompute forward matrix by [ICtCp::prepare_to_lms] and then inverse it
#[inline]
pub fn to_linear_rgb(&self, matrix: Matrix3f) -> Rgb<f32> {
let l_lms = ICTCP_TO_L_LMS.mul_vector(Vector3f {
v: [self.i, self.ct, self.cp],
});
let gamma_l = pq_to_linearf(l_lms.v[0]);
let gamma_m = pq_to_linearf(l_lms.v[1]);
let gamma_s = pq_to_linearf(l_lms.v[2]);
let lms = matrix.mul_vector(Vector3f {
v: [gamma_l, gamma_m, gamma_s],
});
Rgb {
r: lms.v[0],
g: lms.v[1],
b: lms.v[2],
}
}
/// Converts ICtCp to XYZ D65
#[inline]
pub fn to_xyz(&self) -> Xyz {
let l_lms = ICTCP_TO_L_LMS.mul_vector(Vector3f {
v: [self.i, self.ct, self.cp],
});
let gamma_l = pq_to_linearf(l_lms.v[0]);
let gamma_m = pq_to_linearf(l_lms.v[1]);
let gamma_s = pq_to_linearf(l_lms.v[2]);
let lms = LMS_TO_XYZ.mul_vector(Vector3f {
v: [gamma_l, gamma_m, gamma_s],
});
Xyz {
x: lms.v[0],
y: lms.v[1],
z: lms.v[2],
}
}
/// Prepares RGB->LMS matrix
#[inline]
pub const fn prepare_to_lms(rgb_to_xyz: Matrix3f) -> Matrix3f {
XYZ_TO_LMS.mat_mul_const(rgb_to_xyz)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_roundtrip() {
let xyz = Xyz::new(0.5, 0.4, 0.3);
let ictcp = ICtCp::from_xyz(xyz);
let r_xyz = ictcp.to_xyz();
assert!((r_xyz.x - xyz.x).abs() < 1e-4);
assert!((r_xyz.y - xyz.y).abs() < 1e-4);
assert!((r_xyz.z - xyz.z).abs() < 1e-4);
}
#[test]
fn check_roundtrip_rgb() {
let rgb_to_xyz = Matrix3f {
v: [
[0.67345345, 0.165661961, 0.125096574],
[0.27903071, 0.675341845, 0.045627553],
[-0.00193137419, 0.0299795717, 0.797140181],
],
};
let prepared_matrix = ICtCp::prepare_to_lms(rgb_to_xyz);
let inversed_matrix = prepared_matrix.inverse();
let rgb = Rgb::new(0.5, 0.4, 0.3);
let ictcp = ICtCp::from_linear_rgb(rgb, prepared_matrix);
let r_xyz = ictcp.to_linear_rgb(inversed_matrix);
assert!((r_xyz.r - rgb.r).abs() < 1e-4);
assert!((r_xyz.g - rgb.g).abs() < 1e-4);
assert!((r_xyz.b - rgb.b).abs() < 1e-4);
}
}

View File

@@ -1,434 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::Xyz;
use crate::jzczhz::Jzczhz;
use crate::mlaf::mlaf;
use num_traits::Pow;
use pxfm::{dirty_powf, f_cbrtf, f_powf};
use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
};
#[inline]
fn perceptual_quantizer(x: f32) -> f32 {
if x <= 0. {
return 0.;
}
let xx = dirty_powf(x * 1e-4, 0.1593017578125);
let rs = dirty_powf(
mlaf(0.8359375, 18.8515625, xx) / mlaf(1., 18.6875, xx),
134.034375,
);
if rs.is_nan() {
return 0.;
}
rs
}
#[inline]
fn perceptual_quantizer_inverse(x: f32) -> f32 {
if x <= 0. {
return 0.;
}
let xx = dirty_powf(x, 7.460772656268214e-03);
let rs = 1e4
* dirty_powf(
(0.8359375 - xx) / mlaf(-18.8515625, 18.6875, xx),
6.277394636015326,
);
if rs.is_nan() {
return 0.;
}
rs
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default)]
/// Represents Jzazbz
pub struct Jzazbz {
/// Jz(lightness) generally expects to be between `0.0..1.0`.
pub jz: f32,
/// Az generally expects to be between `-0.5..0.5`.
pub az: f32,
/// Bz generally expects to be between `-0.5..0.5`.
pub bz: f32,
}
impl Jzazbz {
/// Constructs new instance
#[inline]
pub fn new(jz: f32, az: f32, bz: f32) -> Jzazbz {
Jzazbz { jz, az, bz }
}
/// Creates new [Jzazbz] from CIE [Xyz].
///
/// JzAzBz is defined in D65 white point, adapt XYZ if needed first.
#[inline]
pub fn from_xyz(xyz: Xyz) -> Jzazbz {
Self::from_xyz_with_display_luminance(xyz, 200.)
}
/// Creates new [Jzazbz] from CIE [Xyz].
///
/// JzAzBz is defined in D65 white point, adapt XYZ if needed first.
#[inline]
pub fn from_xyz_with_display_luminance(xyz: Xyz, display_luminance: f32) -> Jzazbz {
let abs_xyz = xyz * display_luminance;
let lp = perceptual_quantizer(mlaf(
mlaf(0.674207838 * abs_xyz.x, 0.382799340, abs_xyz.y),
-0.047570458,
abs_xyz.z,
));
let mp = perceptual_quantizer(mlaf(
mlaf(0.149284160 * abs_xyz.x, 0.739628340, abs_xyz.y),
0.083327300,
abs_xyz.z,
));
let sp = perceptual_quantizer(mlaf(
mlaf(0.070941080 * abs_xyz.x, 0.174768000, abs_xyz.y),
0.670970020,
abs_xyz.z,
));
let iz = 0.5 * (lp + mp);
let az = mlaf(mlaf(3.524000 * lp, -4.066708, mp), 0.542708, sp);
let bz = mlaf(mlaf(0.199076 * lp, 1.096799, mp), -1.295875, sp);
let jz = (0.44 * iz) / mlaf(1., -0.56, iz) - 1.6295499532821566e-11;
Jzazbz::new(jz, az, bz)
}
/// Converts [Jzazbz] to [Xyz] D65
#[inline]
pub fn to_xyz(&self, display_luminance: f32) -> Xyz {
let jz = self.jz + 1.6295499532821566e-11;
let iz = jz / mlaf(0.44f32, 0.56, jz);
let l = perceptual_quantizer_inverse(mlaf(
mlaf(iz, 1.386050432715393e-1, self.az),
5.804731615611869e-2,
self.bz,
));
let m = perceptual_quantizer_inverse(mlaf(
mlaf(iz, -1.386050432715393e-1, self.az),
-5.804731615611891e-2,
self.bz,
));
let s = perceptual_quantizer_inverse(mlaf(
mlaf(iz, -9.601924202631895e-2, self.az),
-8.118918960560390e-1,
self.bz,
));
let x = mlaf(
mlaf(1.661373055774069e+00 * l, -9.145230923250668e-01, m),
2.313620767186147e-01,
s,
);
let y = mlaf(
mlaf(-3.250758740427037e-01 * l, 1.571847038366936e+00, m),
-2.182538318672940e-01,
s,
);
let z = mlaf(
mlaf(-9.098281098284756e-02 * l, -3.127282905230740e-01, m),
1.522766561305260e+00,
s,
);
let rel_luminance = 1f32 / display_luminance;
Xyz::new(x, y, z) * rel_luminance
}
/// Converts into *Jzczhz*
#[inline]
pub fn to_jzczhz(&self) -> Jzczhz {
Jzczhz::from_jzazbz(*self)
}
#[inline]
pub fn euclidean_distance(&self, other: Self) -> f32 {
let djz = self.jz - other.jz;
let daz = self.az - other.az;
let dbz = self.bz - other.bz;
(djz * djz + daz * daz + dbz * dbz).sqrt()
}
#[inline]
pub fn taxicab_distance(&self, other: Self) -> f32 {
let djz = self.jz - other.jz;
let daz = self.az - other.az;
let dbz = self.bz - other.bz;
djz.abs() + daz.abs() + dbz.abs()
}
}
impl Index<usize> for Jzazbz {
type Output = f32;
#[inline]
fn index(&self, index: usize) -> &f32 {
match index {
0 => &self.jz,
1 => &self.az,
2 => &self.bz,
_ => panic!("Index out of bounds for Jzazbz"),
}
}
}
impl IndexMut<usize> for Jzazbz {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut f32 {
match index {
0 => &mut self.jz,
1 => &mut self.az,
2 => &mut self.bz,
_ => panic!("Index out of bounds for Jzazbz"),
}
}
}
impl Add<f32> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn add(self, rhs: f32) -> Self::Output {
Jzazbz::new(self.jz + rhs, self.az + rhs, self.bz + rhs)
}
}
impl Sub<f32> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn sub(self, rhs: f32) -> Self::Output {
Jzazbz::new(self.jz - rhs, self.az - rhs, self.bz - rhs)
}
}
impl Mul<f32> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn mul(self, rhs: f32) -> Self::Output {
Jzazbz::new(self.jz * rhs, self.az * rhs, self.bz * rhs)
}
}
impl Div<f32> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn div(self, rhs: f32) -> Self::Output {
Jzazbz::new(self.jz / rhs, self.az / rhs, self.bz / rhs)
}
}
impl Add<Jzazbz> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn add(self, rhs: Jzazbz) -> Self::Output {
Jzazbz::new(self.jz + rhs.jz, self.az + rhs.az, self.bz + rhs.bz)
}
}
impl Sub<Jzazbz> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn sub(self, rhs: Jzazbz) -> Self::Output {
Jzazbz::new(self.jz - rhs.jz, self.az - rhs.az, self.bz - rhs.bz)
}
}
impl Mul<Jzazbz> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn mul(self, rhs: Jzazbz) -> Self::Output {
Jzazbz::new(self.jz * rhs.jz, self.az * rhs.az, self.bz * rhs.bz)
}
}
impl Div<Jzazbz> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn div(self, rhs: Jzazbz) -> Self::Output {
Jzazbz::new(self.jz / rhs.jz, self.az / rhs.az, self.bz / rhs.bz)
}
}
impl AddAssign<Jzazbz> for Jzazbz {
#[inline]
fn add_assign(&mut self, rhs: Jzazbz) {
self.jz += rhs.jz;
self.az += rhs.az;
self.bz += rhs.bz;
}
}
impl SubAssign<Jzazbz> for Jzazbz {
#[inline]
fn sub_assign(&mut self, rhs: Jzazbz) {
self.jz -= rhs.jz;
self.az -= rhs.az;
self.bz -= rhs.bz;
}
}
impl MulAssign<Jzazbz> for Jzazbz {
#[inline]
fn mul_assign(&mut self, rhs: Jzazbz) {
self.jz *= rhs.jz;
self.az *= rhs.az;
self.bz *= rhs.bz;
}
}
impl DivAssign<Jzazbz> for Jzazbz {
#[inline]
fn div_assign(&mut self, rhs: Jzazbz) {
self.jz /= rhs.jz;
self.az /= rhs.az;
self.bz /= rhs.bz;
}
}
impl AddAssign<f32> for Jzazbz {
#[inline]
fn add_assign(&mut self, rhs: f32) {
self.jz += rhs;
self.az += rhs;
self.bz += rhs;
}
}
impl SubAssign<f32> for Jzazbz {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
self.jz -= rhs;
self.az -= rhs;
self.bz -= rhs;
}
}
impl MulAssign<f32> for Jzazbz {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
self.jz *= rhs;
self.az *= rhs;
self.bz *= rhs;
}
}
impl DivAssign<f32> for Jzazbz {
#[inline]
fn div_assign(&mut self, rhs: f32) {
self.jz /= rhs;
self.az /= rhs;
self.bz /= rhs;
}
}
impl Neg for Jzazbz {
type Output = Jzazbz;
#[inline]
fn neg(self) -> Self::Output {
Jzazbz::new(-self.jz, -self.az, -self.bz)
}
}
impl Jzazbz {
#[inline]
pub fn sqrt(&self) -> Jzazbz {
Jzazbz::new(self.jz.sqrt(), self.az.sqrt(), self.bz.sqrt())
}
#[inline]
pub fn cbrt(&self) -> Jzazbz {
Jzazbz::new(f_cbrtf(self.jz), f_cbrtf(self.az), f_cbrtf(self.bz))
}
}
impl Pow<f32> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn pow(self, rhs: f32) -> Self::Output {
Jzazbz::new(
f_powf(self.jz, rhs),
f_powf(self.az, rhs),
f_powf(self.bz, rhs),
)
}
}
impl Pow<Jzazbz> for Jzazbz {
type Output = Jzazbz;
#[inline]
fn pow(self, rhs: Jzazbz) -> Self::Output {
Jzazbz::new(
f_powf(self.jz, rhs.jz),
f_powf(self.az, self.az),
f_powf(self.bz, self.bz),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn jzazbz_round() {
let xyz = Xyz::new(0.5, 0.4, 0.3);
let jzazbz = Jzazbz::from_xyz_with_display_luminance(xyz, 253f32);
let old_xyz = jzazbz.to_xyz(253f32);
assert!(
(xyz.x - old_xyz.x).abs() <= 1e-3,
"{:?} != {:?}",
xyz,
old_xyz
);
assert!(
(xyz.y - old_xyz.y).abs() <= 1e-3,
"{:?} != {:?}",
xyz,
old_xyz
);
assert!(
(xyz.z - old_xyz.z).abs() <= 1e-3,
"{:?} != {:?}",
xyz,
old_xyz
);
}
}

View File

@@ -1,375 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::Xyz;
use crate::jzazbz::Jzazbz;
use num_traits::Pow;
use pxfm::{f_atan2f, f_cbrtf, f_hypot3f, f_hypotf, f_powf, f_sincosf, f_sinf};
use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
};
/// Represents Jzazbz in polar coordinates as Jzczhz
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub struct Jzczhz {
/// Jz(lightness) generally expects to be between `0.0..1.0`.
pub jz: f32,
/// Cz generally expects to be between `-1.0..1.0`.
pub cz: f32,
/// Hz generally expects to be between `-1.0..1.0`.
pub hz: f32,
}
impl Jzczhz {
/// Creates new instance of Jzczhz
#[inline]
pub fn new(jz: f32, cz: f32, hz: f32) -> Jzczhz {
Jzczhz { jz, cz, hz }
}
/// Converts Jzazbz to polar coordinates Jzczhz
#[inline]
pub fn from_jzazbz(jzazbz: Jzazbz) -> Jzczhz {
let cz = f_hypotf(jzazbz.az, jzazbz.bz);
let hz = f_atan2f(jzazbz.bz, jzazbz.az);
Jzczhz::new(jzazbz.jz, cz, hz)
}
/// Converts Jzczhz into Jzazbz
#[inline]
pub fn to_jzazbz(&self) -> Jzazbz {
let sincos = f_sincosf(self.hz);
let az = self.cz * sincos.1;
let bz = self.cz * sincos.0;
Jzazbz::new(self.jz, az, bz)
}
/// Converts Jzczhz into Jzazbz
#[inline]
pub fn to_jzazbz_with_luminance(&self) -> Jzazbz {
let sincos = f_sincosf(self.hz);
let az = self.cz * sincos.1;
let bz = self.cz * sincos.0;
Jzazbz::new(self.jz, az, bz)
}
/// Converts Jzczhz to *Xyz*
#[inline]
pub fn to_xyz(&self, display_luminance: f32) -> Xyz {
let jzazbz = self.to_jzazbz();
jzazbz.to_xyz(display_luminance)
}
/// Converts [Xyz] to [Jzczhz]
#[inline]
pub fn from_xyz(xyz: Xyz) -> Jzczhz {
let jzazbz = Jzazbz::from_xyz(xyz);
Jzczhz::from_jzazbz(jzazbz)
}
/// Converts [Xyz] to [Jzczhz]
#[inline]
pub fn from_xyz_with_display_luminance(xyz: Xyz, luminance: f32) -> Jzczhz {
let jzazbz = Jzazbz::from_xyz_with_display_luminance(xyz, luminance);
Jzczhz::from_jzazbz(jzazbz)
}
/// Computes distance for *Jzczhz*
#[inline]
pub fn distance(&self, other: Jzczhz) -> f32 {
let djz = self.jz - other.jz;
let dcz = self.cz - other.cz;
let dhz = self.hz - other.hz;
let dh = 2. * (self.cz * other.cz).sqrt() * f_sinf(dhz * 0.5);
f_hypot3f(djz, dcz, dh)
}
#[inline]
pub fn euclidean_distance(&self, other: Self) -> f32 {
let djz = self.jz - other.jz;
let dhz = self.hz - other.hz;
let dcz = self.cz - other.cz;
(djz * djz + dhz * dhz + dcz * dcz).sqrt()
}
#[inline]
pub fn taxicab_distance(&self, other: Self) -> f32 {
let djz = self.jz - other.jz;
let dhz = self.hz - other.hz;
let dcz = self.cz - other.cz;
djz.abs() + dhz.abs() + dcz.abs()
}
}
impl Index<usize> for Jzczhz {
type Output = f32;
#[inline]
fn index(&self, index: usize) -> &f32 {
match index {
0 => &self.jz,
1 => &self.cz,
2 => &self.hz,
_ => panic!("Index out of bounds for Jzczhz"),
}
}
}
impl IndexMut<usize> for Jzczhz {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut f32 {
match index {
0 => &mut self.jz,
1 => &mut self.cz,
2 => &mut self.hz,
_ => panic!("Index out of bounds for Jzczhz"),
}
}
}
impl Add<f32> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn add(self, rhs: f32) -> Self::Output {
Jzczhz::new(self.jz + rhs, self.cz + rhs, self.hz + rhs)
}
}
impl Sub<f32> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn sub(self, rhs: f32) -> Self::Output {
Jzczhz::new(self.jz - rhs, self.cz - rhs, self.hz - rhs)
}
}
impl Mul<f32> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn mul(self, rhs: f32) -> Self::Output {
Jzczhz::new(self.jz * rhs, self.cz * rhs, self.hz * rhs)
}
}
impl Div<f32> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn div(self, rhs: f32) -> Self::Output {
Jzczhz::new(self.jz / rhs, self.cz / rhs, self.hz / rhs)
}
}
impl Add<Jzczhz> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn add(self, rhs: Jzczhz) -> Self::Output {
Jzczhz::new(self.jz + rhs.jz, self.cz + rhs.cz, self.hz + rhs.hz)
}
}
impl Sub<Jzczhz> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn sub(self, rhs: Jzczhz) -> Self::Output {
Jzczhz::new(self.jz - rhs.jz, self.cz - rhs.cz, self.hz - rhs.hz)
}
}
impl Mul<Jzczhz> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn mul(self, rhs: Jzczhz) -> Self::Output {
Jzczhz::new(self.jz * rhs.jz, self.cz * rhs.cz, self.hz * rhs.hz)
}
}
impl Div<Jzczhz> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn div(self, rhs: Jzczhz) -> Self::Output {
Jzczhz::new(self.jz / rhs.jz, self.cz / rhs.cz, self.hz / rhs.hz)
}
}
impl AddAssign<Jzczhz> for Jzczhz {
#[inline]
fn add_assign(&mut self, rhs: Jzczhz) {
self.jz += rhs.jz;
self.cz += rhs.cz;
self.hz += rhs.hz;
}
}
impl SubAssign<Jzczhz> for Jzczhz {
#[inline]
fn sub_assign(&mut self, rhs: Jzczhz) {
self.jz -= rhs.jz;
self.cz -= rhs.cz;
self.hz -= rhs.hz;
}
}
impl MulAssign<Jzczhz> for Jzczhz {
#[inline]
fn mul_assign(&mut self, rhs: Jzczhz) {
self.jz *= rhs.jz;
self.cz *= rhs.cz;
self.hz *= rhs.hz;
}
}
impl DivAssign<Jzczhz> for Jzczhz {
#[inline]
fn div_assign(&mut self, rhs: Jzczhz) {
self.jz /= rhs.jz;
self.cz /= rhs.cz;
self.hz /= rhs.hz;
}
}
impl AddAssign<f32> for Jzczhz {
#[inline]
fn add_assign(&mut self, rhs: f32) {
self.jz += rhs;
self.cz += rhs;
self.hz += rhs;
}
}
impl SubAssign<f32> for Jzczhz {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
self.jz -= rhs;
self.cz -= rhs;
self.hz -= rhs;
}
}
impl MulAssign<f32> for Jzczhz {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
self.jz *= rhs;
self.cz *= rhs;
self.hz *= rhs;
}
}
impl DivAssign<f32> for Jzczhz {
#[inline]
fn div_assign(&mut self, rhs: f32) {
self.jz /= rhs;
self.cz /= rhs;
self.hz /= rhs;
}
}
impl Jzczhz {
#[inline]
pub fn sqrt(&self) -> Jzczhz {
Jzczhz::new(self.jz.sqrt(), self.cz.sqrt(), self.hz.sqrt())
}
#[inline]
pub fn cbrt(&self) -> Jzczhz {
Jzczhz::new(f_cbrtf(self.jz), f_cbrtf(self.cz), f_cbrtf(self.hz))
}
}
impl Pow<f32> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn pow(self, rhs: f32) -> Self::Output {
Jzczhz::new(
f_powf(self.jz, rhs),
f_powf(self.cz, rhs),
f_powf(self.hz, rhs),
)
}
}
impl Pow<Jzczhz> for Jzczhz {
type Output = Jzczhz;
#[inline]
fn pow(self, rhs: Jzczhz) -> Self::Output {
Jzczhz::new(
f_powf(self.jz, rhs.jz),
f_powf(self.cz, self.cz),
f_powf(self.hz, self.hz),
)
}
}
impl Neg for Jzczhz {
type Output = Jzczhz;
#[inline]
fn neg(self) -> Self::Output {
Jzczhz::new(-self.jz, -self.cz, -self.hz)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn jzczhz_round() {
let xyz = Xyz::new(0.5, 0.4, 0.3);
let jzczhz = Jzczhz::from_xyz_with_display_luminance(xyz, 253.);
let old_xyz = jzczhz.to_xyz(253f32);
assert!(
(xyz.x - old_xyz.x).abs() <= 1e-3,
"{:?} != {:?}",
xyz,
old_xyz
);
assert!(
(xyz.y - old_xyz.y).abs() <= 1e-3,
"{:?} != {:?}",
xyz,
old_xyz
);
assert!(
(xyz.z - old_xyz.z).abs() <= 1e-3,
"{:?} != {:?}",
xyz,
old_xyz
);
}
}

242
deps/moxcms/src/lab.rs vendored
View File

@@ -1,242 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::mlaf::{fmla, mlaf};
use crate::{Chromaticity, LCh, Xyz};
use pxfm::f_cbrtf;
/// Holds CIE LAB values
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)]
pub struct Lab {
/// `l`: lightness component (0 to 100)
pub l: f32,
/// `a`: green (negative) and red (positive) component.
pub a: f32,
/// `b`: blue (negative) and yellow (positive) component
pub b: f32,
}
impl Lab {
/// Create a new CIELAB color.
///
/// # Arguments
///
/// * `l`: lightness component (0 to 100).
/// * `a`: green (negative) and red (positive) component.
/// * `b`: blue (negative) and yellow (positive) component.
#[inline]
pub const fn new(l: f32, a: f32, b: f32) -> Self {
Self { l, a, b }
}
}
#[inline(always)]
const 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
}
}
#[inline(always)]
fn f(t: f32) -> f32 {
if t <= 24. / 116. * (24. / 116.) * (24. / 116.) {
(841. / 108. * t) + 16. / 116.
} else {
f_cbrtf(t)
}
}
impl Lab {
/// Converts to CIE Lab from CIE XYZ for PCS encoding
#[inline]
pub fn from_pcs_xyz(xyz: Xyz) -> Self {
const WP: Xyz = Chromaticity::D50.to_xyz();
let device_x = (xyz.x as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WP.x as f64) as f32;
let device_y = (xyz.y as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WP.y as f64) as f32;
let device_z = (xyz.z as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WP.z as f64) as f32;
let fx = f(device_x);
let fy = f(device_y);
let fz = f(device_z);
let lb = mlaf(-16.0, 116.0, fy);
let a = 500.0 * (fx - fy);
let b = 200.0 * (fy - fz);
let l = lb / 100.0;
let a = (a + 128.0) / 255.0;
let b = (b + 128.0) / 255.0;
Self::new(l, a, b)
}
/// Converts to CIE Lab from CIE XYZ
#[inline]
pub fn from_xyz(xyz: Xyz) -> Self {
const WP: Xyz = Chromaticity::D50.to_xyz();
let device_x = (xyz.x as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WP.x as f64) as f32;
let device_y = (xyz.y as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WP.y as f64) as f32;
let device_z = (xyz.z as f64 * (1.0f64 + 32767.0f64 / 32768.0f64) / WP.z as f64) as f32;
let fx = f(device_x);
let fy = f(device_y);
let fz = f(device_z);
let lb = mlaf(-16.0, 116.0, fy);
let a = 500.0 * (fx - fy);
let b = 200.0 * (fy - fz);
Self::new(lb, a, b)
}
/// Converts CIE [Lab] into CIE [Xyz] for PCS encoding
#[inline]
pub fn to_pcs_xyz(self) -> Xyz {
let device_l = self.l * 100.0;
let device_a = fmla(self.a, 255.0, -128.0);
let device_b = fmla(self.b, 255.0, -128.0);
let y = (device_l + 16.0) / 116.0;
const WP: Xyz = Chromaticity::D50.to_xyz();
let x = f_1(mlaf(y, 0.002, device_a)) * WP.x;
let y1 = f_1(y) * WP.y;
let z = f_1(mlaf(y, -0.005, device_b)) * WP.z;
let x = (x as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
let y = (y1 as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
let z = (z as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
Xyz::new(x, y, z)
}
/// Converts CIE [Lab] into CIE [Xyz]
#[inline]
pub fn to_xyz(self) -> Xyz {
let device_l = self.l;
let device_a = self.a;
let device_b = self.b;
let y = (device_l + 16.0) / 116.0;
const WP: Xyz = Chromaticity::D50.to_xyz();
let x = f_1(mlaf(y, 0.002, device_a)) * WP.x;
let y1 = f_1(y) * WP.y;
let z = f_1(mlaf(y, -0.005, device_b)) * WP.z;
let x = (x as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
let y = (y1 as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
let z = (z as f64 / (1.0f64 + 32767.0f64 / 32768.0f64)) as f32;
Xyz::new(x, y, z)
}
/// Desaturates out of gamut PCS encoded LAB
pub fn desaturate_pcs(self) -> Lab {
if self.l < 0. {
return Lab::new(0., 0., 0.);
}
let mut new_lab = self;
if new_lab.l > 1. {
new_lab.l = 1.;
}
let amax = 1.0;
let amin = 0.0;
let bmin = 0.0;
let bmax = 1.0;
if self.a < amin || self.a > amax || self.b < bmin || self.b > bmax {
if self.a == 0.0 {
// Is hue exactly 90?
// atan will not work, so clamp here
new_lab.b = if new_lab.b < bmin { bmin } else { bmax };
return Lab::new(self.l, self.a, self.b);
}
let lch = LCh::from_lab(new_lab);
let slope = new_lab.b / new_lab.a;
let h = lch.h * (180.0 / std::f32::consts::PI);
// There are 4 zones
if (0. ..45.).contains(&h) || (315. ..=360.).contains(&h) {
// clip by amax
new_lab.a = amax;
new_lab.b = amax * slope;
} else if (45. ..135.).contains(&h) {
// clip by bmax
new_lab.b = bmax;
new_lab.a = bmax / slope;
} else if (135. ..225.).contains(&h) {
// clip by amin
new_lab.a = amin;
new_lab.b = amin * slope;
} else if (225. ..315.).contains(&h) {
// clip by bmin
new_lab.b = bmin;
new_lab.a = bmin / slope;
}
}
new_lab
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let xyz = Xyz::new(0.1, 0.2, 0.3);
let lab = Lab::from_xyz(xyz);
let rolled_back = lab.to_xyz();
let dx = (xyz.x - rolled_back.x).abs();
let dy = (xyz.y - rolled_back.y).abs();
let dz = (xyz.z - rolled_back.z).abs();
assert!(dx < 1e-5);
assert!(dy < 1e-5);
assert!(dz < 1e-5);
}
#[test]
fn round_pcs_trip() {
let xyz = Xyz::new(0.1, 0.2, 0.3);
let lab = Lab::from_pcs_xyz(xyz);
let rolled_back = lab.to_pcs_xyz();
let dx = (xyz.x - rolled_back.x).abs();
let dy = (xyz.y - rolled_back.y).abs();
let dz = (xyz.z - rolled_back.z).abs();
assert!(dx < 1e-5);
assert!(dy < 1e-5);
assert!(dz < 1e-5);
}
}

133
deps/moxcms/src/lib.rs vendored
View File

@@ -1,133 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#![allow(clippy::manual_clamp, clippy::excessive_precision)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(unreachable_pub)]
#![deny(
clippy::print_stdout,
clippy::print_stderr,
clippy::print_literal,
clippy::print_in_format_impl
)]
#![allow(stable_features)]
#![cfg_attr(
not(any(feature = "avx", feature = "sse", feature = "avx512", feature = "neon")),
forbid(unsafe_code)
)]
#![cfg_attr(all(feature = "avx512", target_arch = "x86_64"), feature(cfg_version))]
#![cfg_attr(
all(feature = "avx512", target_arch = "x86_64"),
feature(avx512_target_feature)
)]
#![cfg_attr(
all(feature = "avx512", target_arch = "x86_64"),
feature(stdarch_x86_avx512)
)]
mod chad;
mod cicp;
mod conversions;
mod dat;
mod defaults;
mod err;
mod gamma;
mod gamut;
mod ictcp;
mod jzazbz;
mod jzczhz;
mod lab;
mod luv;
/// One of main intent is to provide fast math available in const context
/// ULP most of the methods <= 0.5
mod math;
mod matrix;
mod mlaf;
mod nd_array;
mod oklab;
mod oklch;
mod profile;
mod reader;
mod rgb;
mod safe_math;
mod tag;
mod transform;
mod trc;
mod writer;
mod yrg;
// Simple math analysis module
mod chromaticity;
mod dt_ucs;
mod helpers;
mod lut_hint;
mod matan;
mod srlab2;
mod xyy;
pub use chad::{
adapt_to_d50, adapt_to_d50_d, adapt_to_illuminant, adapt_to_illuminant_d,
adapt_to_illuminant_xyz, adapt_to_illuminant_xyz_d, adaption_matrix, adaption_matrix_d,
};
pub use chromaticity::Chromaticity;
pub use cicp::{CicpColorPrimaries, ColorPrimaries, MatrixCoefficients, TransferCharacteristics};
pub use dat::ColorDateTime;
pub use defaults::{
HLG_LUT_TABLE, PQ_LUT_TABLE, WHITE_POINT_D50, WHITE_POINT_D60, WHITE_POINT_D65,
WHITE_POINT_DCI_P3,
};
pub use dt_ucs::{DtUchHcb, DtUchHsb, DtUchJch};
pub use err::{CmsError, MalformedSize};
pub use gamut::filmlike_clip;
pub use ictcp::ICtCp;
pub use jzazbz::Jzazbz;
pub use jzczhz::Jzczhz;
pub use lab::Lab;
pub use luv::{LCh, Luv};
pub use math::rounding_div_ceil;
pub use matrix::{
BT2020_MATRIX, DISPLAY_P3_MATRIX, Matrix3, Matrix3d, Matrix3f, Matrix4f, SRGB_MATRIX, Vector3,
Vector3d, Vector3f, Vector3i, Vector3u, Vector4, Vector4d, Vector4f, Vector4i, Xyz, Xyzd,
};
pub use nd_array::{Cube, Hypercube};
pub use oklab::Oklab;
pub use oklch::Oklch;
pub use profile::{
CicpProfile, ColorProfile, DataColorSpace, DescriptionString, LocalizableString, LutDataType,
LutMultidimensionalType, LutStore, LutType, LutWarehouse, Measurement, MeasurementGeometry,
ParsingOptions, ProfileClass, ProfileSignature, ProfileText, ProfileVersion, RenderingIntent,
StandardIlluminant, StandardObserver, TechnologySignatures, ViewingConditions,
};
pub use rgb::{FusedExp, FusedExp2, FusedExp10, FusedLog, FusedLog2, FusedLog10, FusedPow, Rgb};
pub use srlab2::Srlab2;
pub use transform::{
BarycentricWeightScale, InPlaceStage, InterpolationMethod, Layout, PointeeSizeExpressible,
Stage, Transform8BitExecutor, Transform16BitExecutor, TransformExecutor,
TransformF32BitExecutor, TransformF64BitExecutor, TransformOptions,
};
pub use trc::{GammaLutInterpolate, ToneCurveEvaluator, ToneReprCurve, curve_from_gamma};
pub use xyy::{XyY, XyYRepresentable};
pub use yrg::{Ych, Yrg, cie_y_1931_to_cie_y_2006};

View File

@@ -1,106 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::LutWarehouse;
impl LutWarehouse {
/// Method tests if mathematical fusion on LUT table is allowed.
/// If it's not, full brute-force pass in [Katana] is required.
pub(crate) fn is_katana_required(&self) -> bool {
match self {
LutWarehouse::Lut(lut) => {
let input_entries = lut.num_input_channels as usize;
let output_entries = lut.num_output_channels as usize;
for i in 0..input_entries {
if lut.input_table.is_degenerated(input_entries, i) {
return true;
}
if !lut.input_table.is_monotonic(input_entries, i) {
return true;
}
if lut.input_table.have_discontinuities(input_entries, i) {
return true;
}
}
for i in 0..output_entries {
if lut.output_table.is_degenerated(output_entries, i) {
return true;
}
if !lut.output_table.is_monotonic(output_entries, i) {
return true;
}
if lut.output_table.have_discontinuities(output_entries, i) {
return true;
}
}
false
}
LutWarehouse::Multidimensional(mab) => {
for curve in mab.a_curves.iter() {
if curve.is_degenerated() {
return true;
}
if !curve.is_monotonic() {
return true;
}
if curve.have_discontinuities() {
return true;
}
}
for curve in mab.m_curves.iter() {
if curve.is_degenerated() {
return true;
}
if !curve.is_monotonic() {
return true;
}
if curve.have_discontinuities() {
return true;
}
}
for curve in mab.b_curves.iter() {
if curve.is_degenerated() {
return true;
}
if !curve.is_monotonic() {
return true;
}
if curve.have_discontinuities() {
return true;
}
}
false
}
}
}
}

698
deps/moxcms/src/luv.rs vendored
View File

@@ -1,698 +0,0 @@
/*
* // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
* //
* // Use of this source code is governed by a BSD-style
* // license that can be found in the LICENSE file.
*/
//! # Luv
/// Struct representing a color in CIE LUV, a.k.a. L\*u\*v\*, color space
#[repr(C)]
#[derive(Debug, Copy, Clone, Default, PartialOrd)]
pub struct Luv {
/// The L\* value (achromatic luminance) of the colour in 0100 range.
pub l: f32,
/// The u\* value of the colour.
///
/// Together with v\* value, it defines chromaticity of the colour. The u\*
/// coordinate represents colours position on red-green axis with negative
/// values indicating more red and positive more green colour. Typical
/// values are in -134220 range (but exact range for valid colours
/// depends on luminance and v\* value).
pub u: f32,
/// The u\* value of the colour.
///
/// Together with u\* value, it defines chromaticity of the colour. The v\*
/// coordinate represents colours position on blue-yellow axis with
/// negative values indicating more blue and positive more yellow colour.
/// Typical values are in -140122 range (but exact range for valid
/// colours depends on luminance and u\* value).
pub v: f32,
}
/// Representing a color in cylindrical CIE LCh(uv) color space
#[repr(C)]
#[derive(Debug, Copy, Clone, Default, PartialOrd)]
pub struct LCh {
/// The L\* value (achromatic luminance) of the colour in 0100 range.
///
/// This is the same value as in the [`Luv`] object.
pub l: f32,
/// The C\*_uv value (chroma) of the colour.
///
/// Together with h_uv, it defines chromaticity of the colour. The typical
/// values of the coordinate go from zero up to around 150 (but exact range
/// for valid colours depends on luminance and hue). Zero represents
/// shade of grey.
pub c: f32,
/// The h_uv value (hue) of the colour measured in radians.
///
/// Together with C\*_uv, it defines chromaticity of the colour. The value
/// represents an angle thus it wraps around τ. Typically, the value will
/// be in the -π–π range. The value is undefined if C\*_uv is zero.
pub h: f32,
}
use crate::mlaf::mlaf;
use crate::{Chromaticity, Lab, Xyz};
use num_traits::Pow;
use pxfm::{f_atan2f, f_cbrtf, f_hypotf, f_powf, f_sincosf};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
pub(crate) const LUV_WHITE_U_PRIME: f32 = 4.0f32 * Chromaticity::D50.to_xyz().y
/ (Chromaticity::D50.to_xyz().x
+ 15.0 * Chromaticity::D50.to_xyz().y
+ 3.0 * Chromaticity::D50.to_xyz().z);
pub(crate) const LUV_WHITE_V_PRIME: f32 = 9.0f32 * Chromaticity::D50.to_xyz().y
/ (Chromaticity::D50.to_xyz().x
+ 15.0 * Chromaticity::D50.to_xyz().y
+ 3.0 * Chromaticity::D50.to_xyz().z);
pub(crate) const LUV_CUTOFF_FORWARD_Y: f32 = (6f32 / 29f32) * (6f32 / 29f32) * (6f32 / 29f32);
pub(crate) const LUV_MULTIPLIER_FORWARD_Y: f32 = (29f32 / 3f32) * (29f32 / 3f32) * (29f32 / 3f32);
pub(crate) const LUV_MULTIPLIER_INVERSE_Y: f32 = (3f32 / 29f32) * (3f32 / 29f32) * (3f32 / 29f32);
impl Luv {
/// Converts CIE XYZ to CIE Luv using D50 white point
#[inline]
#[allow(clippy::manual_clamp)]
pub fn from_xyz(xyz: Xyz) -> Self {
let [x, y, z] = [xyz.x, xyz.y, xyz.z];
let den = mlaf(mlaf(x, 15.0, y), 3.0, z);
let l = (if y < LUV_CUTOFF_FORWARD_Y {
LUV_MULTIPLIER_FORWARD_Y * y
} else {
116. * f_cbrtf(y) - 16.
})
.min(100.)
.max(0.);
let (u, v);
if den != 0f32 {
let u_prime = 4. * x / den;
let v_prime = 9. * y / den;
u = 13. * l * (u_prime - LUV_WHITE_U_PRIME);
v = 13. * l * (v_prime - LUV_WHITE_V_PRIME);
} else {
u = 0.;
v = 0.;
}
Luv { l, u, v }
}
/// To [Xyz] using D50 colorimetry
#[inline]
pub fn to_xyz(&self) -> Xyz {
if self.l <= 0. {
return Xyz::new(0., 0., 0.);
}
let l13 = 1. / (13. * self.l);
let u = mlaf(LUV_WHITE_U_PRIME, self.u, l13);
let v = mlaf(LUV_WHITE_V_PRIME, self.v, l13);
let y = if self.l > 8. {
let jx = (self.l + 16.) / 116.;
jx * jx * jx
} else {
self.l * LUV_MULTIPLIER_INVERSE_Y
};
let (x, z);
if v != 0. {
let den = 1. / (4. * v);
x = y * 9. * u * den;
z = y * mlaf(mlaf(12.0, -3.0, u), -20., v) * den;
} else {
x = 0.;
z = 0.;
}
Xyz::new(x, y, z)
}
#[inline]
pub const fn new(l: f32, u: f32, v: f32) -> Luv {
Luv { l, u, v }
}
}
impl LCh {
#[inline]
pub const fn new(l: f32, c: f32, h: f32) -> Self {
LCh { l, c, h }
}
/// Converts Lab to LCh(uv)
#[inline]
pub fn from_luv(luv: Luv) -> Self {
LCh {
l: luv.l,
c: f_hypotf(luv.u, luv.v),
h: f_atan2f(luv.v, luv.u),
}
}
/// Converts Lab to LCh(ab)
#[inline]
pub fn from_lab(lab: Lab) -> Self {
LCh {
l: lab.l,
c: f_hypotf(lab.a, lab.b),
h: f_atan2f(lab.b, lab.a),
}
}
/// Computes LCh(uv)
#[inline]
pub fn from_xyz(xyz: Xyz) -> Self {
Self::from_luv(Luv::from_xyz(xyz))
}
/// Computes LCh(ab)
#[inline]
pub fn from_xyz_lab(xyz: Xyz) -> Self {
Self::from_lab(Lab::from_xyz(xyz))
}
/// Converts LCh(uv) to Luv
#[inline]
pub fn to_xyz(&self) -> Xyz {
self.to_luv().to_xyz()
}
/// Converts LCh(ab) to Lab
#[inline]
pub fn to_xyz_lab(&self) -> Xyz {
self.to_lab().to_xyz()
}
#[inline]
pub fn to_luv(&self) -> Luv {
let sincos = f_sincosf(self.h);
Luv {
l: self.l,
u: self.c * sincos.1,
v: self.c * sincos.0,
}
}
#[inline]
pub fn to_lab(&self) -> Lab {
let sincos = f_sincosf(self.h);
Lab {
l: self.l,
a: self.c * sincos.1,
b: self.c * sincos.0,
}
}
}
impl PartialEq<Luv> for Luv {
/// Compares two colours ignoring chromaticity if L\* is zero.
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.l != other.l {
false
} else if self.l == 0.0 {
true
} else {
self.u == other.u && self.v == other.v
}
}
}
impl PartialEq<LCh> for LCh {
/// Compares two colours ignoring chromaticity if L\* is zero and hue if C\*
/// is zero. Hues which are τ apart are compared equal.
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.l != other.l {
false
} else if self.l == 0.0 {
true
} else if self.c != other.c {
false
} else if self.c == 0.0 {
true
} else {
use std::f32::consts::TAU;
self.h.rem_euclid(TAU) == other.h.rem_euclid(TAU)
}
}
}
impl Luv {
#[inline]
pub fn euclidean_distance(&self, other: Luv) -> f32 {
let dl = self.l - other.l;
let du = self.u - other.u;
let dv = self.v - other.v;
(dl * dl + du * du + dv * dv).sqrt()
}
}
impl LCh {
#[inline]
pub fn euclidean_distance(&self, other: LCh) -> f32 {
let dl = self.l - other.l;
let dc = self.c - other.c;
let dh = self.h - other.h;
(dl * dl + dc * dc + dh * dh).sqrt()
}
}
impl Luv {
#[inline]
pub const fn taxicab_distance(&self, other: Self) -> f32 {
let dl = self.l - other.l;
let du = self.u - other.u;
let dv = self.v - other.v;
dl.abs() + du.abs() + dv.abs()
}
}
impl LCh {
#[inline]
pub const fn taxicab_distance(&self, other: Self) -> f32 {
let dl = self.l - other.l;
let dc = self.c - other.c;
let dh = self.h - other.h;
dl.abs() + dc.abs() + dh.abs()
}
}
impl Add<Luv> for Luv {
type Output = Luv;
#[inline]
fn add(self, rhs: Luv) -> Luv {
Luv::new(self.l + rhs.l, self.u + rhs.u, self.v + rhs.v)
}
}
impl Add<LCh> for LCh {
type Output = LCh;
#[inline]
fn add(self, rhs: LCh) -> LCh {
LCh::new(self.l + rhs.l, self.c + rhs.c, self.h + rhs.h)
}
}
impl Sub<Luv> for Luv {
type Output = Luv;
#[inline]
fn sub(self, rhs: Luv) -> Luv {
Luv::new(self.l - rhs.l, self.u - rhs.u, self.v - rhs.v)
}
}
impl Sub<LCh> for LCh {
type Output = LCh;
#[inline]
fn sub(self, rhs: LCh) -> LCh {
LCh::new(self.l - rhs.l, self.c - rhs.c, self.h - rhs.h)
}
}
impl Mul<Luv> for Luv {
type Output = Luv;
#[inline]
fn mul(self, rhs: Luv) -> Luv {
Luv::new(self.l * rhs.l, self.u * rhs.u, self.v * rhs.v)
}
}
impl Mul<LCh> for LCh {
type Output = LCh;
#[inline]
fn mul(self, rhs: LCh) -> LCh {
LCh::new(self.l * rhs.l, self.c * rhs.c, self.h * rhs.h)
}
}
impl Div<Luv> for Luv {
type Output = Luv;
#[inline]
fn div(self, rhs: Luv) -> Luv {
Luv::new(self.l / rhs.l, self.u / rhs.u, self.v / rhs.v)
}
}
impl Div<LCh> for LCh {
type Output = LCh;
#[inline]
fn div(self, rhs: LCh) -> LCh {
LCh::new(self.l / rhs.l, self.c / rhs.c, self.h / rhs.h)
}
}
impl Add<f32> for Luv {
type Output = Luv;
#[inline]
fn add(self, rhs: f32) -> Self::Output {
Luv::new(self.l + rhs, self.u + rhs, self.v + rhs)
}
}
impl Add<f32> for LCh {
type Output = LCh;
#[inline]
fn add(self, rhs: f32) -> Self::Output {
LCh::new(self.l + rhs, self.c + rhs, self.h + rhs)
}
}
impl Sub<f32> for Luv {
type Output = Luv;
#[inline]
fn sub(self, rhs: f32) -> Self::Output {
Luv::new(self.l - rhs, self.u - rhs, self.v - rhs)
}
}
impl Sub<f32> for LCh {
type Output = LCh;
#[inline]
fn sub(self, rhs: f32) -> Self::Output {
LCh::new(self.l - rhs, self.c - rhs, self.h - rhs)
}
}
impl Mul<f32> for Luv {
type Output = Luv;
#[inline]
fn mul(self, rhs: f32) -> Self::Output {
Luv::new(self.l * rhs, self.u * rhs, self.v * rhs)
}
}
impl Mul<f32> for LCh {
type Output = LCh;
#[inline]
fn mul(self, rhs: f32) -> Self::Output {
LCh::new(self.l * rhs, self.c * rhs, self.h * rhs)
}
}
impl Div<f32> for Luv {
type Output = Luv;
#[inline]
fn div(self, rhs: f32) -> Self::Output {
Luv::new(self.l / rhs, self.u / rhs, self.v / rhs)
}
}
impl Div<f32> for LCh {
type Output = LCh;
#[inline]
fn div(self, rhs: f32) -> Self::Output {
LCh::new(self.l / rhs, self.c / rhs, self.h / rhs)
}
}
impl AddAssign<Luv> for Luv {
#[inline]
fn add_assign(&mut self, rhs: Luv) {
self.l += rhs.l;
self.u += rhs.u;
self.v += rhs.v;
}
}
impl AddAssign<LCh> for LCh {
#[inline]
fn add_assign(&mut self, rhs: LCh) {
self.l += rhs.l;
self.c += rhs.c;
self.h += rhs.h;
}
}
impl SubAssign<Luv> for Luv {
#[inline]
fn sub_assign(&mut self, rhs: Luv) {
self.l -= rhs.l;
self.u -= rhs.u;
self.v -= rhs.v;
}
}
impl SubAssign<LCh> for LCh {
#[inline]
fn sub_assign(&mut self, rhs: LCh) {
self.l -= rhs.l;
self.c -= rhs.c;
self.h -= rhs.h;
}
}
impl MulAssign<Luv> for Luv {
#[inline]
fn mul_assign(&mut self, rhs: Luv) {
self.l *= rhs.l;
self.u *= rhs.u;
self.v *= rhs.v;
}
}
impl MulAssign<LCh> for LCh {
#[inline]
fn mul_assign(&mut self, rhs: LCh) {
self.l *= rhs.l;
self.c *= rhs.c;
self.h *= rhs.h;
}
}
impl DivAssign<Luv> for Luv {
#[inline]
fn div_assign(&mut self, rhs: Luv) {
self.l /= rhs.l;
self.u /= rhs.u;
self.v /= rhs.v;
}
}
impl DivAssign<LCh> for LCh {
#[inline]
fn div_assign(&mut self, rhs: LCh) {
self.l /= rhs.l;
self.c /= rhs.c;
self.h /= rhs.h;
}
}
impl AddAssign<f32> for Luv {
#[inline]
fn add_assign(&mut self, rhs: f32) {
self.l += rhs;
self.u += rhs;
self.v += rhs;
}
}
impl AddAssign<f32> for LCh {
#[inline]
fn add_assign(&mut self, rhs: f32) {
self.l += rhs;
self.c += rhs;
self.h += rhs;
}
}
impl SubAssign<f32> for Luv {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
self.l -= rhs;
self.u -= rhs;
self.v -= rhs;
}
}
impl SubAssign<f32> for LCh {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
self.l -= rhs;
self.c -= rhs;
self.h -= rhs;
}
}
impl MulAssign<f32> for Luv {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
self.l *= rhs;
self.u *= rhs;
self.v *= rhs;
}
}
impl MulAssign<f32> for LCh {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
self.l *= rhs;
self.c *= rhs;
self.h *= rhs;
}
}
impl DivAssign<f32> for Luv {
#[inline]
fn div_assign(&mut self, rhs: f32) {
self.l /= rhs;
self.u /= rhs;
self.v /= rhs;
}
}
impl DivAssign<f32> for LCh {
#[inline]
fn div_assign(&mut self, rhs: f32) {
self.l /= rhs;
self.c /= rhs;
self.h /= rhs;
}
}
impl Neg for LCh {
type Output = LCh;
#[inline]
fn neg(self) -> Self::Output {
LCh::new(-self.l, -self.c, -self.h)
}
}
impl Neg for Luv {
type Output = Luv;
#[inline]
fn neg(self) -> Self::Output {
Luv::new(-self.l, -self.u, -self.v)
}
}
impl Pow<f32> for Luv {
type Output = Luv;
#[inline]
fn pow(self, rhs: f32) -> Self::Output {
Luv::new(
f_powf(self.l, rhs),
f_powf(self.u, rhs),
f_powf(self.v, rhs),
)
}
}
impl Pow<f32> for LCh {
type Output = LCh;
#[inline]
fn pow(self, rhs: f32) -> Self::Output {
LCh::new(
f_powf(self.l, rhs),
f_powf(self.c, rhs),
f_powf(self.h, rhs),
)
}
}
impl Pow<Luv> for Luv {
type Output = Luv;
#[inline]
fn pow(self, rhs: Luv) -> Self::Output {
Luv::new(
f_powf(self.l, rhs.l),
f_powf(self.u, rhs.u),
f_powf(self.v, rhs.v),
)
}
}
impl Pow<LCh> for LCh {
type Output = LCh;
#[inline]
fn pow(self, rhs: LCh) -> Self::Output {
LCh::new(
f_powf(self.l, rhs.l),
f_powf(self.c, rhs.c),
f_powf(self.h, rhs.h),
)
}
}
impl Luv {
#[inline]
pub fn sqrt(&self) -> Luv {
Luv::new(self.l.sqrt(), self.u.sqrt(), self.v.sqrt())
}
#[inline]
pub fn cbrt(&self) -> Luv {
Luv::new(f_cbrtf(self.l), f_cbrtf(self.u), f_cbrtf(self.v))
}
}
impl LCh {
#[inline]
pub fn sqrt(&self) -> LCh {
LCh::new(
if self.l < 0. { 0. } else { self.l.sqrt() },
if self.c < 0. { 0. } else { self.c.sqrt() },
if self.h < 0. { 0. } else { self.h.sqrt() },
)
}
#[inline]
pub fn cbrt(&self) -> LCh {
LCh::new(f_cbrtf(self.l), f_cbrtf(self.c), f_cbrtf(self.h))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_luv() {
let xyz = Xyz::new(0.1, 0.2, 0.3);
let lab = Luv::from_xyz(xyz);
let rolled_back = lab.to_xyz();
let dx = (xyz.x - rolled_back.x).abs();
let dy = (xyz.y - rolled_back.y).abs();
let dz = (xyz.z - rolled_back.z).abs();
assert!(dx < 1e-5);
assert!(dy < 1e-5);
assert!(dz < 1e-5);
}
#[test]
fn round_trip_lch() {
let xyz = Xyz::new(0.1, 0.2, 0.3);
let luv = Luv::from_xyz(xyz);
let lab = LCh::from_luv(luv);
let rolled_back = lab.to_luv();
let dx = (luv.l - rolled_back.l).abs();
let dy = (luv.u - rolled_back.u).abs();
let dz = (luv.v - rolled_back.v).abs();
assert!(dx < 1e-4);
assert!(dy < 1e-4);
assert!(dz < 1e-4);
}
}

View File

@@ -1,72 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
pub(crate) fn is_curve_linear16(curve: &[u16]) -> bool {
let scale = 1. / (curve.len() - 1) as f32 * 65535.;
for (index, &value) in curve.iter().enumerate() {
let quantized = (index as f32 * scale).round() as u16;
let diff = (quantized as i32 - value as i32).abs();
if diff > 0x0f {
return false;
}
}
true
}
pub(crate) fn is_curve_descending<T: PartialOrd>(v: &[T]) -> bool {
if v.is_empty() {
return false;
}
if v.len() == 1 {
return false;
}
v[0] > v[v.len() - 1]
}
pub(crate) fn is_curve_ascending<T: PartialOrd>(v: &[T]) -> bool {
if v.is_empty() {
return false;
}
if v.len() == 1 {
return false;
}
v[0] < v[v.len() - 1]
}
pub(crate) fn is_curve_linear8(curve: &[u8]) -> bool {
let scale = 1. / (curve.len() - 1) as f32 * 255.;
for (index, &value) in curve.iter().enumerate() {
let quantized = (index as f32 * scale).round() as u16;
let diff = (quantized as i32 - value as i32).abs();
if diff > 0x03 {
return false;
}
}
true
}

View File

@@ -1,60 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#[derive(Copy, Clone, Default, Debug)]
struct DegenerationAmount {
leading: usize,
trailing: usize,
}
/// Counts amount of duplicates on each side of curve
fn count_leading_trailing_duplicated<T: PartialOrd>(lut: &[T]) -> DegenerationAmount {
if lut.is_empty() {
return DegenerationAmount::default();
}
let first = lut.first().unwrap();
let last = lut.last().unwrap();
let leading = lut.iter().take_while(|&v| v.eq(first)).count();
let trailing = lut.iter().rev().take_while(|&v| v.eq(last)).count();
DegenerationAmount { leading, trailing }
}
/// Finds out if curve is degenerated on the sides.
pub(crate) fn is_curve_degenerated<T: PartialOrd>(v: &[T]) -> bool {
if v.is_empty() || v.len() < 2 {
return false;
}
let degeneration_amount = count_leading_trailing_duplicated(v);
if degeneration_amount.trailing <= 1 && degeneration_amount.leading <= 1 {
return false;
}
let leading_percentage = degeneration_amount.leading;
let trailing_percentage = degeneration_amount.trailing;
((leading_percentage / 20) > 0) || ((trailing_percentage / 20) > 0)
}

View File

@@ -1,74 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use num_traits::AsPrimitive;
pub(crate) trait DiscontinuitySpike {
const SPIKE: f64;
}
impl DiscontinuitySpike for u8 {
const SPIKE: f64 = 16.0;
}
impl DiscontinuitySpike for u16 {
const SPIKE: f64 = 2100.;
}
impl DiscontinuitySpike for f32 {
const SPIKE: f64 = 0.07;
}
/// Searches LUT curve for discontinuity
pub(crate) fn does_curve_have_discontinuity<
T: Copy + PartialEq + DiscontinuitySpike + AsPrimitive<f64> + 'static,
>(
curve: &[T],
) -> bool {
if curve.len() < 2 {
return false;
}
let threshold: f64 = T::SPIKE;
let mut discontinuities = 0u64;
let mut previous_element: f64 = curve[0].as_();
let diff: f64 = (curve[1].as_() - previous_element).abs();
if diff > threshold {
discontinuities += 1;
}
for element in curve.iter().skip(1) {
let new_diff: f64 = (element.as_() - previous_element).abs();
if new_diff > threshold {
discontinuities += 1;
if discontinuities > 3 {
break;
}
}
previous_element = element.as_();
}
discontinuities > 3
}

View File

@@ -1,40 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
mod curve_shape;
mod degeneration;
mod discontinuity;
mod monotonic;
mod slope_limit;
pub(crate) use curve_shape::{
is_curve_ascending, is_curve_descending, is_curve_linear8, is_curve_linear16,
};
pub(crate) use degeneration::is_curve_degenerated;
pub(crate) use discontinuity::does_curve_have_discontinuity;
pub(crate) use monotonic::is_curve_monotonic;

View File

@@ -1,52 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::matan::is_curve_ascending;
/// Finds out if curve is monotonic.
pub(crate) fn is_curve_monotonic<T: PartialOrd>(lut: &[T]) -> bool {
if lut.len() < 2 {
return true;
}
let is_ascending = is_curve_ascending(lut);
let mut violations = 0usize;
if is_ascending {
for (current, previous) in lut.iter().skip(1).zip(lut.iter().take(lut.len() - 1)) {
if current.lt(previous) {
violations += 1;
}
}
} else {
for (current, previous) in lut.iter().skip(1).zip(lut.iter().take(lut.len() - 1)) {
if current.gt(previous) {
violations += 1;
}
}
}
(violations as f64 / lut.len() as f64) < 0.05
}

View File

@@ -1,84 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#![allow(dead_code)]
use crate::PointeeSizeExpressible;
use crate::matan::is_curve_descending;
use num_traits::AsPrimitive;
pub(crate) fn limit_slope<T: Copy + AsPrimitive<f32> + PartialOrd + PointeeSizeExpressible>(
curve: &mut [T],
value_cap: f32,
) where
f32: AsPrimitive<T>,
{
let at_begin = (curve.len() as f32 * 0.02 + 0.5).floor() as usize; // Cutoff at 2%
if at_begin == 0 {
return;
}
let at_end = curve.len() - at_begin - 1; // And 98%
let (begin_val, end_val) = if is_curve_descending(curve) {
(value_cap, 0.)
} else {
(0., value_cap)
};
let val = curve[at_begin].as_();
let slope = (val - begin_val) / at_begin as f32;
let beta = val - slope * at_begin as f32;
if T::FINITE {
for v in curve.iter_mut().take(at_begin) {
*v = (v.as_() * slope + beta)
.round()
.min(value_cap)
.max(0.0)
.as_();
}
} else {
for v in curve.iter_mut().take(at_begin) {
*v = (v.as_() * slope + beta).min(value_cap).max(0.0).as_();
}
}
let val = curve[at_end].as_();
let slope = (end_val - val) / at_begin as f32;
let beta = val - slope * at_end as f32;
if T::FINITE {
for v in curve.iter_mut().skip(at_end) {
*v = (v.as_() * slope + beta)
.round()
.min(value_cap)
.max(0.0)
.as_();
}
} else {
for v in curve.iter_mut().skip(at_end) {
*v = (v.as_() * slope + beta).min(value_cap).max(0.0).as_();
}
}
}

View File

@@ -1,68 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#![allow(clippy::approx_constant, clippy::manual_range_contains)]
use num_traits::Num;
#[inline(always)]
pub const fn rounding_div_ceil(value: i32, div: i32) -> i32 {
(value + div - 1) / div
}
// Generic function for max
#[inline(always)]
pub(crate) fn m_max<T: Num + PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
// Generic function for min
#[inline(always)]
pub(crate) fn m_min<T: Num + PartialOrd>(a: T, b: T) -> T {
if a < b { a } else { b }
}
#[inline]
pub(crate) fn m_clamp<T: Num + PartialOrd>(a: T, min: T, max: T) -> T {
if a > max {
max
} else if a >= min {
a
} else {
// a < min or a is NaN
min
}
}
pub trait FusedMultiplyAdd<T> {
fn mla(&self, b: T, c: T) -> T;
}
pub(crate) trait FusedMultiplyNegAdd<T> {
fn neg_mla(&self, b: T, c: T) -> T;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,82 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use num_traits::MulAdd;
use std::ops::{Add, Mul, Neg};
#[cfg(any(
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "fma"
),
all(target_arch = "aarch64", target_feature = "neon")
))]
#[inline(always)]
pub(crate) fn mlaf<T: Copy + Mul<T, Output = T> + Add<T, Output = T> + MulAdd<T, Output = T>>(
acc: T,
a: T,
b: T,
) -> T {
MulAdd::mul_add(a, b, acc)
}
#[inline(always)]
#[cfg(not(any(
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "fma"
),
all(target_arch = "aarch64", target_feature = "neon")
)))]
pub(crate) fn mlaf<T: Copy + Mul<T, Output = T> + Add<T, Output = T> + MulAdd<T, Output = T>>(
acc: T,
a: T,
b: T,
) -> T {
acc + a * b
}
#[inline(always)]
pub(crate) fn neg_mlaf<
T: Copy + Mul<T, Output = T> + Add<T, Output = T> + MulAdd<T, Output = T> + Neg<Output = T>,
>(
acc: T,
a: T,
b: T,
) -> T {
mlaf(acc, a, -b)
}
#[inline(always)]
pub(crate) fn fmla<T: Copy + Mul<T, Output = T> + Add<T, Output = T> + MulAdd<T, Output = T>>(
a: T,
b: T,
acc: T,
) -> T {
mlaf(acc, a, b)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,354 +0,0 @@
/*
* // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
* //
* // Use of this source code is governed by a BSD-style
* // license that can be found in the LICENSE file.
*/
use crate::Rgb;
use crate::mlaf::mlaf;
use num_traits::Pow;
use pxfm::{f_cbrtf, f_powf};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
/// Struct that represent *Oklab* colorspace
pub struct Oklab {
/// All values in Oklab intended to be normalized \[0; 1\]
pub l: f32,
/// A value range \[-0.5; 0.5\]
pub a: f32,
/// B value range \[-0.5; 0.5\]
pub b: f32,
}
impl Oklab {
#[inline]
pub const fn new(l: f32, a: f32, b: f32) -> Oklab {
Oklab { l, a, b }
}
#[inline]
/// Convert Linear Rgb to [Oklab]
pub fn from_linear_rgb(rgb: Rgb<f32>) -> Oklab {
Self::linear_rgb_to_oklab(rgb)
}
#[inline]
fn linear_rgb_to_oklab(rgb: Rgb<f32>) -> Oklab {
let l = mlaf(
mlaf(0.4122214708f32 * rgb.r, 0.5363325363f32, rgb.g),
0.0514459929f32,
rgb.b,
);
let m = mlaf(
mlaf(0.2119034982f32 * rgb.r, 0.6806995451f32, rgb.g),
0.1073969566f32,
rgb.b,
);
let s = mlaf(
mlaf(0.0883024619f32 * rgb.r, 0.2817188376f32, rgb.g),
0.6299787005f32,
rgb.b,
);
let l_cone = f_cbrtf(l);
let m_cone = f_cbrtf(m);
let s_cone = f_cbrtf(s);
Oklab {
l: mlaf(
mlaf(0.2104542553f32 * l_cone, 0.7936177850f32, m_cone),
-0.0040720468f32,
s_cone,
),
a: mlaf(
mlaf(1.9779984951f32 * l_cone, -2.4285922050f32, m_cone),
0.4505937099f32,
s_cone,
),
b: mlaf(
mlaf(0.0259040371f32 * l_cone, 0.7827717662f32, m_cone),
-0.8086757660f32,
s_cone,
),
}
}
#[inline]
/// Converts to linear RGB
pub fn to_linear_rgb(&self) -> Rgb<f32> {
let l_ = mlaf(
mlaf(self.l, 0.3963377774f32, self.a),
0.2158037573f32,
self.b,
);
let m_ = mlaf(
mlaf(self.l, -0.1055613458f32, self.a),
-0.0638541728f32,
self.b,
);
let s_ = mlaf(
mlaf(self.l, -0.0894841775f32, self.a),
-1.2914855480f32,
self.b,
);
let l = l_ * l_ * l_;
let m = m_ * m_ * m_;
let s = s_ * s_ * s_;
Rgb::new(
mlaf(
mlaf(4.0767416621f32 * l, -3.3077115913f32, m),
0.2309699292f32,
s,
),
mlaf(
mlaf(-1.2684380046f32 * l, 2.6097574011f32, m),
-0.3413193965f32,
s,
),
mlaf(
mlaf(-0.0041960863f32 * l, -0.7034186147f32, m),
1.7076147010f32,
s,
),
)
}
#[inline]
pub fn hybrid_distance(&self, other: Self) -> f32 {
let lax = self.l - other.l;
let dax = self.a - other.a;
let bax = self.b - other.b;
(dax * dax + bax * bax).sqrt() + lax.abs()
}
}
impl Oklab {
pub fn euclidean_distance(&self, other: Self) -> f32 {
let lax = self.l - other.l;
let dax = self.a - other.a;
let bax = self.b - other.b;
(lax * lax + dax * dax + bax * bax).sqrt()
}
}
impl Oklab {
pub fn taxicab_distance(&self, other: Self) -> f32 {
let lax = self.l - other.l;
let dax = self.a - other.a;
let bax = self.b - other.b;
lax.abs() + dax.abs() + bax.abs()
}
}
impl Add<Oklab> for Oklab {
type Output = Oklab;
#[inline]
fn add(self, rhs: Self) -> Oklab {
Oklab::new(self.l + rhs.l, self.a + rhs.a, self.b + rhs.b)
}
}
impl Add<f32> for Oklab {
type Output = Oklab;
#[inline]
fn add(self, rhs: f32) -> Oklab {
Oklab::new(self.l + rhs, self.a + rhs, self.b + rhs)
}
}
impl AddAssign<Oklab> for Oklab {
#[inline]
fn add_assign(&mut self, rhs: Oklab) {
self.l += rhs.l;
self.a += rhs.a;
self.b += rhs.b;
}
}
impl AddAssign<f32> for Oklab {
#[inline]
fn add_assign(&mut self, rhs: f32) {
self.l += rhs;
self.a += rhs;
self.b += rhs;
}
}
impl Mul<f32> for Oklab {
type Output = Oklab;
#[inline]
fn mul(self, rhs: f32) -> Self::Output {
Oklab::new(self.l * rhs, self.a * rhs, self.b * rhs)
}
}
impl Mul<Oklab> for Oklab {
type Output = Oklab;
#[inline]
fn mul(self, rhs: Oklab) -> Self::Output {
Oklab::new(self.l * rhs.l, self.a * rhs.a, self.b * rhs.b)
}
}
impl MulAssign<f32> for Oklab {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
self.l *= rhs;
self.a *= rhs;
self.b *= rhs;
}
}
impl MulAssign<Oklab> for Oklab {
#[inline]
fn mul_assign(&mut self, rhs: Oklab) {
self.l *= rhs.l;
self.a *= rhs.a;
self.b *= rhs.b;
}
}
impl Sub<f32> for Oklab {
type Output = Oklab;
#[inline]
fn sub(self, rhs: f32) -> Self::Output {
Oklab::new(self.l - rhs, self.a - rhs, self.b - rhs)
}
}
impl Sub<Oklab> for Oklab {
type Output = Oklab;
#[inline]
fn sub(self, rhs: Oklab) -> Self::Output {
Oklab::new(self.l - rhs.l, self.a - rhs.a, self.b - rhs.b)
}
}
impl SubAssign<f32> for Oklab {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
self.l -= rhs;
self.a -= rhs;
self.b -= rhs;
}
}
impl SubAssign<Oklab> for Oklab {
#[inline]
fn sub_assign(&mut self, rhs: Oklab) {
self.l -= rhs.l;
self.a -= rhs.a;
self.b -= rhs.b;
}
}
impl Div<f32> for Oklab {
type Output = Oklab;
#[inline]
fn div(self, rhs: f32) -> Self::Output {
Oklab::new(self.l / rhs, self.a / rhs, self.b / rhs)
}
}
impl Div<Oklab> for Oklab {
type Output = Oklab;
#[inline]
fn div(self, rhs: Oklab) -> Self::Output {
Oklab::new(self.l / rhs.l, self.a / rhs.a, self.b / rhs.b)
}
}
impl DivAssign<f32> for Oklab {
#[inline]
fn div_assign(&mut self, rhs: f32) {
self.l /= rhs;
self.a /= rhs;
self.b /= rhs;
}
}
impl DivAssign<Oklab> for Oklab {
#[inline]
fn div_assign(&mut self, rhs: Oklab) {
self.l /= rhs.l;
self.a /= rhs.a;
self.b /= rhs.b;
}
}
impl Neg for Oklab {
type Output = Oklab;
#[inline]
fn neg(self) -> Self::Output {
Oklab::new(-self.l, -self.a, -self.b)
}
}
impl Pow<f32> for Oklab {
type Output = Oklab;
#[inline]
fn pow(self, rhs: f32) -> Self::Output {
Oklab::new(
f_powf(self.l, rhs),
f_powf(self.a, rhs),
f_powf(self.b, rhs),
)
}
}
impl Pow<Oklab> for Oklab {
type Output = Oklab;
#[inline]
fn pow(self, rhs: Oklab) -> Self::Output {
Oklab::new(
f_powf(self.l, rhs.l),
f_powf(self.a, rhs.a),
f_powf(self.b, rhs.b),
)
}
}
impl Oklab {
#[inline]
pub fn sqrt(&self) -> Oklab {
Oklab::new(self.l.sqrt(), self.a.sqrt(), self.b.sqrt())
}
#[inline]
pub fn cbrt(&self) -> Oklab {
Oklab::new(f_cbrtf(self.l), f_cbrtf(self.a), f_cbrtf(self.b))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let xyz = Rgb::new(0.1, 0.2, 0.3);
let lab = Oklab::from_linear_rgb(xyz);
let rolled_back = lab.to_linear_rgb();
let dx = (xyz.r - rolled_back.r).abs();
let dy = (xyz.g - rolled_back.g).abs();
let dz = (xyz.b - rolled_back.b).abs();
assert!(dx < 1e-5);
assert!(dy < 1e-5);
assert!(dz < 1e-5);
}
}

View File

@@ -1,294 +0,0 @@
/*
* // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
* //
* // Use of this source code is governed by a BSD-style
* // license that can be found in the LICENSE file.
*/
use crate::{Oklab, Rgb};
use num_traits::Pow;
use pxfm::{f_atan2f, f_cbrtf, f_hypotf, f_powf, f_sincosf};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
/// Represents *Oklch* colorspace
#[repr(C)]
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub struct Oklch {
/// Lightness
pub l: f32,
/// Chroma
pub c: f32,
/// Hue
pub h: f32,
}
impl Oklch {
/// Creates new instance
#[inline]
pub const fn new(l: f32, c: f32, h: f32) -> Oklch {
Oklch { l, c, h }
}
/// Converts Linear [Rgb] into [Oklch]
///
/// # Arguments
/// `transfer_function` - Transfer function into linear colorspace and its inverse
#[inline]
pub fn from_linear_rgb(rgb: Rgb<f32>) -> Oklch {
let oklab = Oklab::from_linear_rgb(rgb);
Oklch::from_oklab(oklab)
}
/// Converts [Oklch] into linear [Rgb]
#[inline]
pub fn to_linear_rgb(&self) -> Rgb<f32> {
let oklab = self.to_oklab();
oklab.to_linear_rgb()
}
/// Converts *Oklab* to *Oklch*
#[inline]
pub fn from_oklab(oklab: Oklab) -> Oklch {
let chroma = f_hypotf(oklab.b, oklab.a);
let hue = f_atan2f(oklab.b, oklab.a);
Oklch::new(oklab.l, chroma, hue)
}
/// Converts *Oklch* to *Oklab*
#[inline]
pub fn to_oklab(&self) -> Oklab {
let l = self.l;
let sincos = f_sincosf(self.h);
let a = self.c * sincos.1;
let b = self.c * sincos.0;
Oklab::new(l, a, b)
}
}
impl Oklch {
#[inline]
pub fn euclidean_distance(&self, other: Self) -> f32 {
let dl = self.l - other.l;
let dc = self.c - other.c;
let dh = self.h - other.h;
(dl * dl + dc * dc + dh * dh).sqrt()
}
}
impl Oklch {
#[inline]
pub fn taxicab_distance(&self, other: Self) -> f32 {
let dl = self.l - other.l;
let dc = self.c - other.c;
let dh = self.h - other.h;
dl.abs() + dc.abs() + dh.abs()
}
}
impl Add<Oklch> for Oklch {
type Output = Oklch;
#[inline]
fn add(self, rhs: Self) -> Oklch {
Oklch::new(self.l + rhs.l, self.c + rhs.c, self.h + rhs.h)
}
}
impl Add<f32> for Oklch {
type Output = Oklch;
#[inline]
fn add(self, rhs: f32) -> Oklch {
Oklch::new(self.l + rhs, self.c + rhs, self.h + rhs)
}
}
impl AddAssign<Oklch> for Oklch {
#[inline]
fn add_assign(&mut self, rhs: Oklch) {
self.l += rhs.l;
self.c += rhs.c;
self.h += rhs.h;
}
}
impl AddAssign<f32> for Oklch {
#[inline]
fn add_assign(&mut self, rhs: f32) {
self.l += rhs;
self.c += rhs;
self.h += rhs;
}
}
impl Mul<f32> for Oklch {
type Output = Oklch;
#[inline]
fn mul(self, rhs: f32) -> Self::Output {
Oklch::new(self.l * rhs, self.c * rhs, self.h * rhs)
}
}
impl Mul<Oklch> for Oklch {
type Output = Oklch;
#[inline]
fn mul(self, rhs: Oklch) -> Self::Output {
Oklch::new(self.l * rhs.l, self.c * rhs.c, self.h * rhs.h)
}
}
impl MulAssign<f32> for Oklch {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
self.l *= rhs;
self.c *= rhs;
self.h *= rhs;
}
}
impl MulAssign<Oklch> for Oklch {
#[inline]
fn mul_assign(&mut self, rhs: Oklch) {
self.l *= rhs.l;
self.c *= rhs.c;
self.h *= rhs.h;
}
}
impl Sub<f32> for Oklch {
type Output = Oklch;
#[inline]
fn sub(self, rhs: f32) -> Self::Output {
Oklch::new(self.l - rhs, self.c - rhs, self.h - rhs)
}
}
impl Sub<Oklch> for Oklch {
type Output = Oklch;
#[inline]
fn sub(self, rhs: Oklch) -> Self::Output {
Oklch::new(self.l - rhs.l, self.c - rhs.c, self.h - rhs.h)
}
}
impl SubAssign<f32> for Oklch {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
self.l -= rhs;
self.c -= rhs;
self.h -= rhs;
}
}
impl SubAssign<Oklch> for Oklch {
#[inline]
fn sub_assign(&mut self, rhs: Oklch) {
self.l -= rhs.l;
self.c -= rhs.c;
self.h -= rhs.h;
}
}
impl Div<f32> for Oklch {
type Output = Oklch;
#[inline]
fn div(self, rhs: f32) -> Self::Output {
Oklch::new(self.l / rhs, self.c / rhs, self.h / rhs)
}
}
impl Div<Oklch> for Oklch {
type Output = Oklch;
#[inline]
fn div(self, rhs: Oklch) -> Self::Output {
Oklch::new(self.l / rhs.l, self.c / rhs.c, self.h / rhs.h)
}
}
impl DivAssign<f32> for Oklch {
#[inline]
fn div_assign(&mut self, rhs: f32) {
self.l /= rhs;
self.c /= rhs;
self.h /= rhs;
}
}
impl DivAssign<Oklch> for Oklch {
#[inline]
fn div_assign(&mut self, rhs: Oklch) {
self.l /= rhs.l;
self.c /= rhs.c;
self.h /= rhs.h;
}
}
impl Neg for Oklch {
type Output = Oklch;
#[inline]
fn neg(self) -> Self::Output {
Oklch::new(-self.l, -self.c, -self.h)
}
}
impl Pow<f32> for Oklch {
type Output = Oklch;
#[inline]
fn pow(self, rhs: f32) -> Self::Output {
Oklch::new(
f_powf(self.l, rhs),
f_powf(self.c, rhs),
f_powf(self.h, rhs),
)
}
}
impl Pow<Oklch> for Oklch {
type Output = Oklch;
#[inline]
fn pow(self, rhs: Oklch) -> Self::Output {
Oklch::new(
f_powf(self.l, rhs.l),
f_powf(self.c, rhs.c),
f_powf(self.h, rhs.h),
)
}
}
impl Oklch {
#[inline]
pub fn sqrt(&self) -> Oklch {
Oklch::new(self.l.sqrt(), self.c.sqrt(), self.h.sqrt())
}
#[inline]
pub fn cbrt(&self) -> Oklch {
Oklch::new(f_cbrtf(self.l), f_cbrtf(self.c), f_cbrtf(self.h))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let xyz = Rgb::new(0.1, 0.2, 0.3);
let lab = Oklch::from_linear_rgb(xyz);
let rolled_back = lab.to_linear_rgb();
let dx = (xyz.r - rolled_back.r).abs();
let dy = (xyz.g - rolled_back.g).abs();
let dz = (xyz.b - rolled_back.b).abs();
assert!(dx < 1e-5);
assert!(dy < 1e-5);
assert!(dz < 1e-5);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,955 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::err::try_vec;
use crate::helpers::{read_matrix_3d, read_vector_3d};
use crate::profile::LutDataType;
use crate::safe_math::{SafeAdd, SafeMul, SafePowi};
use crate::tag::{TAG_SIZE, TagTypeDefinition};
use crate::{
CicpColorPrimaries, CicpProfile, CmsError, ColorDateTime, ColorProfile, DescriptionString,
LocalizableString, LutMultidimensionalType, LutStore, LutType, LutWarehouse, Matrix3d,
Matrix3f, MatrixCoefficients, Measurement, MeasurementGeometry, ParsingOptions, ProfileText,
StandardIlluminant, StandardObserver, TechnologySignatures, ToneReprCurve,
TransferCharacteristics, Vector3d, ViewingConditions, Xyz, Xyzd,
};
/// Produces the nearest float to `a` with a maximum error of 1/1024 which
/// happens for large values like 0x40000040.
#[inline]
pub(crate) const fn s15_fixed16_number_to_float(a: i32) -> f32 {
a as f32 / 65536.
}
#[inline]
pub(crate) const fn s15_fixed16_number_to_double(a: i32) -> f64 {
a as f64 / 65536.
}
#[inline]
pub(crate) const fn uint16_number_to_float(a: u32) -> f32 {
a as f32 / 65536.
}
#[inline]
pub(crate) const fn uint16_number_to_float_fast(a: u32) -> f32 {
a as f32 * (1. / 65536.)
}
// #[inline]
// pub(crate) fn uint8_number_to_float(a: u8) -> f32 {
// a as f32 / 255.0
// }
#[inline]
pub(crate) fn uint8_number_to_float_fast(a: u8) -> f32 {
a as f32 * (1. / 255.0)
}
fn utf16be_to_utf16(slice: &[u8]) -> Result<Vec<u16>, CmsError> {
let mut vec = try_vec![0u16; slice.len() / 2];
for (dst, chunk) in vec.iter_mut().zip(slice.chunks_exact(2)) {
*dst = u16::from_be_bytes([chunk[0], chunk[1]]);
}
Ok(vec)
}
impl ColorProfile {
#[inline]
pub(crate) fn read_lut_type(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<LutType, CmsError> {
let tag_size = if tag_size == 0 { TAG_SIZE } else { tag_size };
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..last_tag_offset];
if tag.len() < 48 {
return Err(CmsError::InvalidProfile);
}
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
LutType::try_from(tag_type)
}
#[inline]
pub(crate) fn read_viewing_conditions(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Option<ViewingConditions>, CmsError> {
if tag_size < 36 {
return Ok(None);
}
if slice.len() < entry.safe_add(36)? {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..entry.safe_add(36)?];
let tag_type =
TagTypeDefinition::from(u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]));
// Ignore unknown
if tag_type != TagTypeDefinition::DefViewingConditions {
return Ok(None);
}
let illuminant_x = i32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]);
let illuminant_y = i32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]);
let illuminant_z = i32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]);
let surround_x = i32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]);
let surround_y = i32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]);
let surround_z = i32::from_be_bytes([tag[28], tag[29], tag[30], tag[31]]);
let illuminant_type = u32::from_be_bytes([tag[32], tag[33], tag[34], tag[35]]);
let illuminant = Xyz::new(
s15_fixed16_number_to_float(illuminant_x),
s15_fixed16_number_to_float(illuminant_y),
s15_fixed16_number_to_float(illuminant_z),
);
let surround = Xyz::new(
s15_fixed16_number_to_float(surround_x),
s15_fixed16_number_to_float(surround_y),
s15_fixed16_number_to_float(surround_z),
);
let observer = StandardObserver::from(illuminant_type);
Ok(Some(ViewingConditions {
illuminant,
surround,
observer,
}))
}
pub(crate) fn read_string_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Option<ProfileText>, CmsError> {
let tag_size = if tag_size == 0 { TAG_SIZE } else { tag_size };
if tag_size < 4 {
return Ok(None);
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..last_tag_offset];
if tag.len() < 8 {
return Ok(None);
}
let tag_type =
TagTypeDefinition::from(u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]));
// Ignore unknown
if tag_type == TagTypeDefinition::Text {
let sliced_from_to_end = &tag[8..tag.len()];
let str = String::from_utf8_lossy(sliced_from_to_end);
return Ok(Some(ProfileText::PlainString(str.to_string())));
} else if tag_type == TagTypeDefinition::MultiLocalizedUnicode {
if tag.len() < 28 {
return Err(CmsError::InvalidProfile);
}
// let record_size = u32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]) as usize;
// // Record size is reserved to be 12.
// if record_size != 12 {
// return Err(CmsError::InvalidIcc);
// }
let records_count = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
let primary_language_code = String::from_utf8_lossy(&[tag[16], tag[17]]).to_string();
let primary_country_code = String::from_utf8_lossy(&[tag[18], tag[19]]).to_string();
let first_string_record_length =
u32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]) as usize;
let first_record_offset =
u32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]) as usize;
if tag.len() < first_record_offset.safe_add(first_string_record_length)? {
return Ok(None);
}
let resliced =
&tag[first_record_offset..first_record_offset + first_string_record_length];
let cvt = utf16be_to_utf16(resliced)?;
let string_record = String::from_utf16_lossy(&cvt);
let mut records = vec![LocalizableString {
language: primary_language_code,
country: primary_country_code,
value: string_record,
}];
for record in 1..records_count {
// Localizable header must be at least 12 bytes
let localizable_header_offset = if record == 1 {
28
} else {
28 + 12 * (record - 1)
};
if tag.len() < localizable_header_offset + 12 {
return Err(CmsError::InvalidProfile);
}
let choked = &tag[localizable_header_offset..localizable_header_offset + 12];
let language_code = String::from_utf8_lossy(&[choked[0], choked[1]]).to_string();
let country_code = String::from_utf8_lossy(&[choked[2], choked[3]]).to_string();
let record_length =
u32::from_be_bytes([choked[4], choked[5], choked[6], choked[7]]) as usize;
let string_offset =
u32::from_be_bytes([choked[8], choked[9], choked[10], choked[11]]) as usize;
if tag.len() < string_offset.safe_add(record_length)? {
return Ok(None);
}
let resliced = &tag[string_offset..string_offset + record_length];
let cvt = utf16be_to_utf16(resliced)?;
let string_record = String::from_utf16_lossy(&cvt);
records.push(LocalizableString {
country: country_code,
language: language_code,
value: string_record,
});
}
return Ok(Some(ProfileText::Localizable(records)));
} else if tag_type == TagTypeDefinition::Description {
if tag.len() < 12 {
return Err(CmsError::InvalidProfile);
}
let ascii_length = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
if tag.len() < 12.safe_add(ascii_length)? {
return Err(CmsError::InvalidProfile);
}
let sliced = &tag[12..12 + ascii_length];
let ascii_string = String::from_utf8_lossy(sliced).to_string();
let mut last_position = 12 + ascii_length;
if tag.len() < last_position + 8 {
return Err(CmsError::InvalidProfile);
}
let uc = &tag[last_position..last_position + 8];
let unicode_code = u32::from_be_bytes([uc[0], uc[1], uc[2], uc[3]]);
let unicode_length = u32::from_be_bytes([uc[4], uc[5], uc[6], uc[7]]) as usize * 2;
if tag.len() < unicode_length.safe_add(8)?.safe_add(last_position)? {
return Ok(None);
}
last_position += 8;
let uc = &tag[last_position..last_position + unicode_length];
let wc = utf16be_to_utf16(uc)?;
let unicode_string = String::from_utf16_lossy(&wc).to_string();
// last_position += unicode_length;
//
// if tag.len() < last_position + 2 {
// return Err(CmsError::InvalidIcc);
// }
// let uc = &tag[last_position..last_position + 2];
// let script_code = uc[0];
// let mac_length = uc[1] as usize;
// last_position += 2;
// if tag.len() < last_position + mac_length {
// return Err(CmsError::InvalidIcc);
// }
//
// let uc = &tag[last_position..last_position + unicode_length];
// let wc = utf16be_to_utf16(uc);
// let mac_string = String::from_utf16_lossy(&wc).to_string();
return Ok(Some(ProfileText::Description(DescriptionString {
ascii_string,
unicode_language_code: unicode_code,
unicode_string,
mac_string: "".to_string(),
script_code_code: -1,
})));
}
Ok(None)
}
#[inline]
fn read_lut_table_f32(table: &[u8], lut_type: LutType) -> Result<LutStore, CmsError> {
if lut_type == LutType::Lut16 {
let mut clut = try_vec![0u16; table.len() / 2];
for (src, dst) in table.chunks_exact(2).zip(clut.iter_mut()) {
*dst = u16::from_be_bytes([src[0], src[1]]);
}
Ok(LutStore::Store16(clut))
} else if lut_type == LutType::Lut8 {
let mut clut = try_vec![0u8; table.len()];
for (&src, dst) in table.iter().zip(clut.iter_mut()) {
*dst = src;
}
Ok(LutStore::Store8(clut))
} else {
unreachable!("This should never happen, report to https://github.com/awxkee/moxcms")
}
}
#[inline]
fn read_nested_tone_curves(
slice: &[u8],
offset: usize,
length: usize,
options: &ParsingOptions,
) -> Result<Option<Vec<ToneReprCurve>>, CmsError> {
let mut curve_offset: usize = offset;
let mut curves = Vec::new();
for _ in 0..length {
if slice.len() < curve_offset.safe_add(12)? {
return Err(CmsError::InvalidProfile);
}
let mut tag_size = 0usize;
let new_curve = Self::read_trc_tag(slice, curve_offset, 0, &mut tag_size, options)?;
match new_curve {
None => return Err(CmsError::InvalidProfile),
Some(curve) => curves.push(curve),
}
curve_offset += tag_size;
// 4 byte aligned
if curve_offset % 4 != 0 {
curve_offset += 4 - curve_offset % 4;
}
}
Ok(Some(curves))
}
#[inline]
pub(crate) fn read_lut_abm_type(
slice: &[u8],
entry: usize,
tag_size: usize,
to_pcs: bool,
options: &ParsingOptions,
) -> Result<Option<LutWarehouse>, CmsError> {
if tag_size < 48 {
return Ok(None);
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..last_tag_offset];
if tag.len() < 48 {
return Err(CmsError::InvalidProfile);
}
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
let tag_type_definition = TagTypeDefinition::from(tag_type);
if tag_type_definition != TagTypeDefinition::MabLut
&& tag_type_definition != TagTypeDefinition::MbaLut
{
return Ok(None);
}
let in_channels = tag[8];
let out_channels = tag[9];
if in_channels > 4 && out_channels > 4 {
return Ok(None);
}
let a_curve_offset = u32::from_be_bytes([tag[28], tag[29], tag[30], tag[31]]) as usize;
let clut_offset = u32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]) as usize;
let m_curve_offset = u32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]) as usize;
let matrix_offset = u32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]) as usize;
let b_curve_offset = u32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]) as usize;
let transform: Matrix3d;
let bias: Vector3d;
if matrix_offset != 0 {
let matrix_end = matrix_offset.safe_add(12 * 4)?;
if tag.len() < matrix_end {
return Err(CmsError::InvalidProfile);
}
let m_tag = &tag[matrix_offset..matrix_end];
bias = read_vector_3d(&m_tag[36..48])?;
transform = read_matrix_3d(m_tag)?;
} else {
transform = Matrix3d::IDENTITY;
bias = Vector3d::default();
}
let mut grid_points: [u8; 16] = [0; 16];
let clut_table: Option<LutStore> = if clut_offset != 0 {
// Check if CLUT formed correctly
if clut_offset.safe_add(20)? > tag.len() {
return Err(CmsError::InvalidProfile);
}
let clut_sizes_slice = &tag[clut_offset..clut_offset.safe_add(16)?];
for (&s, v) in clut_sizes_slice.iter().zip(grid_points.iter_mut()) {
*v = s;
}
let mut clut_size = 1u32;
for &i in grid_points.iter().take(in_channels as usize) {
clut_size *= i as u32;
}
clut_size *= out_channels as u32;
if clut_size == 0 {
return Err(CmsError::InvalidProfile);
}
if clut_size > 10_000_000 {
return Err(CmsError::InvalidProfile);
}
let clut_offset20 = clut_offset.safe_add(20)?;
let clut_header = &tag[clut_offset..clut_offset20];
let entry_size = clut_header[16];
if entry_size != 1 && entry_size != 2 {
return Err(CmsError::InvalidProfile);
}
let clut_end =
clut_offset20.safe_add(clut_size.safe_mul(entry_size as u32)? as usize)?;
if tag.len() < clut_end {
return Err(CmsError::InvalidProfile);
}
let shaped_clut_table = &tag[clut_offset20..clut_end];
Some(Self::read_lut_table_f32(
shaped_clut_table,
if entry_size == 1 {
LutType::Lut8
} else {
LutType::Lut16
},
)?)
} else {
None
};
let a_curves = if a_curve_offset == 0 {
Vec::new()
} else {
Self::read_nested_tone_curves(
tag,
a_curve_offset,
if to_pcs {
in_channels as usize
} else {
out_channels as usize
},
options,
)?
.ok_or(CmsError::InvalidProfile)?
};
let m_curves = if m_curve_offset == 0 {
Vec::new()
} else {
Self::read_nested_tone_curves(
tag,
m_curve_offset,
if to_pcs {
out_channels as usize
} else {
in_channels as usize
},
options,
)?
.ok_or(CmsError::InvalidProfile)?
};
let b_curves = if b_curve_offset == 0 {
Vec::new()
} else {
Self::read_nested_tone_curves(
tag,
b_curve_offset,
if to_pcs {
out_channels as usize
} else {
in_channels as usize
},
options,
)?
.ok_or(CmsError::InvalidProfile)?
};
let wh = LutWarehouse::Multidimensional(LutMultidimensionalType {
num_input_channels: in_channels,
num_output_channels: out_channels,
matrix: transform,
clut: clut_table,
a_curves,
b_curves,
m_curves,
grid_points,
bias,
});
Ok(Some(wh))
}
#[inline]
pub(crate) fn read_lut_a_to_b_type(
slice: &[u8],
entry: usize,
tag_size: usize,
parsing_options: &ParsingOptions,
) -> Result<Option<LutWarehouse>, CmsError> {
if tag_size < 48 {
return Ok(None);
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..last_tag_offset];
if tag.len() < 48 {
return Err(CmsError::InvalidProfile);
}
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
let lut_type = LutType::try_from(tag_type)?;
assert!(lut_type == LutType::Lut8 || lut_type == LutType::Lut16);
if lut_type == LutType::Lut16 && tag.len() < 52 {
return Err(CmsError::InvalidProfile);
}
let num_input_table_entries: u16 = match lut_type {
LutType::Lut8 => 256,
LutType::Lut16 => u16::from_be_bytes([tag[48], tag[49]]),
_ => unreachable!(),
};
let num_output_table_entries: u16 = match lut_type {
LutType::Lut8 => 256,
LutType::Lut16 => u16::from_be_bytes([tag[50], tag[51]]),
_ => unreachable!(),
};
if !(2..=4096).contains(&num_input_table_entries)
|| !(2..=4096).contains(&num_output_table_entries)
{
return Err(CmsError::InvalidProfile);
}
let input_offset: usize = match lut_type {
LutType::Lut8 => 48,
LutType::Lut16 => 52,
_ => unreachable!(),
};
let entry_size: usize = match lut_type {
LutType::Lut8 => 1,
LutType::Lut16 => 2,
_ => unreachable!(),
};
let in_chan = tag[8];
let out_chan = tag[9];
let is_3_to_4 = in_chan == 3 || out_chan == 4;
let is_4_to_3 = in_chan == 4 || out_chan == 3;
if !is_3_to_4 && !is_4_to_3 {
return Err(CmsError::InvalidProfile);
}
let grid_points = tag[10];
let clut_size = (grid_points as u32).safe_powi(in_chan as u32)? as usize;
if !(1..=parsing_options.max_allowed_clut_size).contains(&clut_size) {
return Err(CmsError::InvalidProfile);
}
assert!(tag.len() >= 48);
let transform = read_matrix_3d(&tag[12..48])?;
let lut_input_size = num_input_table_entries.safe_mul(in_chan as u16)? as usize;
let linearization_table_end = lut_input_size
.safe_mul(entry_size)?
.safe_add(input_offset)?;
if tag.len() < linearization_table_end {
return Err(CmsError::InvalidProfile);
}
let shaped_input_table = &tag[input_offset..linearization_table_end];
let linearization_table = Self::read_lut_table_f32(shaped_input_table, lut_type)?;
let clut_offset = linearization_table_end;
let clut_data_size = clut_size
.safe_mul(out_chan as usize)?
.safe_mul(entry_size)?;
if tag.len() < clut_offset.safe_add(clut_data_size)? {
return Err(CmsError::InvalidProfile);
}
let shaped_clut_table = &tag[clut_offset..clut_offset + clut_data_size];
let clut_table = Self::read_lut_table_f32(shaped_clut_table, lut_type)?;
let output_offset = clut_offset.safe_add(clut_data_size)?;
let output_size = (num_output_table_entries as usize).safe_mul(out_chan as usize)?;
let shaped_output_table =
&tag[output_offset..output_offset.safe_add(output_size.safe_mul(entry_size)?)?];
let gamma_table = Self::read_lut_table_f32(shaped_output_table, lut_type)?;
let wh = LutWarehouse::Lut(LutDataType {
num_input_table_entries,
num_output_table_entries,
num_input_channels: in_chan,
num_output_channels: out_chan,
num_clut_grid_points: grid_points,
matrix: transform,
input_table: linearization_table,
clut_table,
output_table: gamma_table,
lut_type,
});
Ok(Some(wh))
}
pub(crate) fn read_lut_tag(
slice: &[u8],
tag_entry: u32,
tag_size: usize,
parsing_options: &ParsingOptions,
) -> Result<Option<LutWarehouse>, CmsError> {
let lut_type = Self::read_lut_type(slice, tag_entry as usize, tag_size)?;
Ok(if lut_type == LutType::Lut8 || lut_type == LutType::Lut16 {
Self::read_lut_a_to_b_type(slice, tag_entry as usize, tag_size, parsing_options)?
} else if lut_type == LutType::LutMba || lut_type == LutType::LutMab {
Self::read_lut_abm_type(
slice,
tag_entry as usize,
tag_size,
lut_type == LutType::LutMab,
parsing_options,
)?
} else {
None
})
}
pub(crate) fn read_trc_tag_s(
slice: &[u8],
entry: usize,
tag_size: usize,
options: &ParsingOptions,
) -> Result<Option<ToneReprCurve>, CmsError> {
let mut _empty = 0usize;
Self::read_trc_tag(slice, entry, tag_size, &mut _empty, options)
}
pub(crate) fn read_trc_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
read_size: &mut usize,
options: &ParsingOptions,
) -> Result<Option<ToneReprCurve>, CmsError> {
if slice.len() < entry.safe_add(4)? {
return Ok(None);
}
let small_tag = &slice[entry..entry + 4];
// We require always recognize tone curves.
let curve_type = TagTypeDefinition::from(u32::from_be_bytes([
small_tag[0],
small_tag[1],
small_tag[2],
small_tag[3],
]));
if tag_size != 0 && tag_size < TAG_SIZE {
return Ok(None);
}
let last_tag_offset = if tag_size != 0 {
tag_size + entry
} else {
slice.len()
};
if last_tag_offset > slice.len() {
return Err(CmsError::MalformedTrcCurve("Data exhausted".to_string()));
}
let tag = &slice[entry..last_tag_offset];
if tag.len() < TAG_SIZE {
return Err(CmsError::MalformedTrcCurve("Data exhausted".to_string()));
}
if curve_type == TagTypeDefinition::LutToneCurve {
let entry_count = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
if entry_count == 0 {
return Ok(Some(ToneReprCurve::Lut(vec![])));
}
if entry_count > options.max_allowed_trc_size {
return Err(CmsError::CurveLutIsTooLarge);
}
let curve_end = entry_count.safe_mul(size_of::<u16>())?.safe_add(12)?;
if tag.len() < curve_end {
return Err(CmsError::MalformedTrcCurve(
"Curve end ends to early".to_string(),
));
}
let curve_sliced = &tag[12..curve_end];
let mut curve_values = try_vec![0u16; entry_count];
for (value, curve_value) in curve_sliced.chunks_exact(2).zip(curve_values.iter_mut()) {
let gamma_s15 = u16::from_be_bytes([value[0], value[1]]);
*curve_value = gamma_s15;
}
*read_size = curve_end;
Ok(Some(ToneReprCurve::Lut(curve_values)))
} else if curve_type == TagTypeDefinition::ParametricToneCurve {
let entry_count = u16::from_be_bytes([tag[8], tag[9]]) as usize;
if entry_count > 4 {
return Err(CmsError::MalformedTrcCurve(
"Parametric curve has unknown entries count".to_string(),
));
}
const COUNT_TO_LENGTH: [usize; 5] = [1, 3, 4, 5, 7]; //PARAMETRIC_CURVE_TYPE
if tag.len() < 12 + COUNT_TO_LENGTH[entry_count] * size_of::<u32>() {
return Err(CmsError::MalformedTrcCurve(
"Parametric curve has unknown entries count exhaust data too early".to_string(),
));
}
let curve_sliced = &tag[12..12 + COUNT_TO_LENGTH[entry_count] * size_of::<u32>()];
let mut params = try_vec![0f32; COUNT_TO_LENGTH[entry_count]];
for (value, param_value) in curve_sliced.chunks_exact(4).zip(params.iter_mut()) {
let parametric_value = i32::from_be_bytes([value[0], value[1], value[2], value[3]]);
*param_value = s15_fixed16_number_to_float(parametric_value);
}
if entry_count == 1 || entry_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 {
return Err(CmsError::ParametricCurveZeroDivision);
}
}
*read_size = 12 + COUNT_TO_LENGTH[entry_count] * 4;
Ok(Some(ToneReprCurve::Parametric(params)))
} else {
Err(CmsError::MalformedTrcCurve(
"Unknown parametric curve tag".to_string(),
))
}
}
#[inline]
pub(crate) fn read_chad_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Option<Matrix3d>, CmsError> {
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
if slice[entry..].len() < 8 {
return Err(CmsError::InvalidProfile);
}
if tag_size < 8 {
return Ok(None);
}
if (tag_size - 8) / 4 != 9 {
return Ok(None);
}
let tag0 = &slice[entry..entry.safe_add(8)?];
let c_type =
TagTypeDefinition::from(u32::from_be_bytes([tag0[0], tag0[1], tag0[2], tag0[3]]));
if c_type != TagTypeDefinition::S15Fixed16Array {
return Err(CmsError::InvalidProfile);
}
if slice.len() < 9 * size_of::<u32>() + 8 {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry + 8..last_tag_offset];
if tag.len() != size_of::<Matrix3f>() {
return Err(CmsError::InvalidProfile);
}
let matrix = read_matrix_3d(tag)?;
Ok(Some(matrix))
}
#[inline]
pub(crate) fn read_tech_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Option<TechnologySignatures>, CmsError> {
if tag_size < TAG_SIZE {
return Err(CmsError::InvalidProfile);
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..entry.safe_add(12)?];
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
let def = TagTypeDefinition::from(tag_type);
if def == TagTypeDefinition::Signature {
let sig = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]);
let tech_sig = TechnologySignatures::from(sig);
return Ok(Some(tech_sig));
}
Ok(None)
}
#[inline]
pub(crate) fn read_date_time_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Option<ColorDateTime>, CmsError> {
if tag_size < 20 {
return Ok(None);
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..entry.safe_add(20)?];
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
let def = TagTypeDefinition::from(tag_type);
if def == TagTypeDefinition::DateTime {
let tag_value = &slice[8..20];
let time = ColorDateTime::new_from_slice(tag_value)?;
return Ok(Some(time));
}
Ok(None)
}
#[inline]
pub(crate) fn read_meas_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Option<Measurement>, CmsError> {
if tag_size < TAG_SIZE {
return Ok(None);
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..entry + 12];
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
let def = TagTypeDefinition::from(tag_type);
if def != TagTypeDefinition::Measurement {
return Ok(None);
}
if 36 > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..entry + 36];
let observer =
StandardObserver::from(u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]));
let q15_16_x = i32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]);
let q15_16_y = i32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]);
let q15_16_z = i32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]);
let x = s15_fixed16_number_to_float(q15_16_x);
let y = s15_fixed16_number_to_float(q15_16_y);
let z = s15_fixed16_number_to_float(q15_16_z);
let xyz = Xyz::new(x, y, z);
let geometry =
MeasurementGeometry::from(u32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]));
let flare =
uint16_number_to_float(u32::from_be_bytes([tag[28], tag[29], tag[30], tag[31]]));
let illuminant =
StandardIlluminant::from(u32::from_be_bytes([tag[32], tag[33], tag[34], tag[35]]));
Ok(Some(Measurement {
flare,
illuminant,
geometry,
observer,
backing: xyz,
}))
}
#[inline]
pub(crate) fn read_xyz_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Xyzd, CmsError> {
if tag_size < TAG_SIZE {
return Ok(Xyzd::default());
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..entry + 12];
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
let def = TagTypeDefinition::from(tag_type);
if def != TagTypeDefinition::Xyz {
return Ok(Xyzd::default());
}
let tag = &slice[entry..last_tag_offset];
if tag.len() < 20 {
return Err(CmsError::InvalidProfile);
}
let q15_16_x = i32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]);
let q15_16_y = i32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]);
let q15_16_z = i32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]);
let x = s15_fixed16_number_to_double(q15_16_x);
let y = s15_fixed16_number_to_double(q15_16_y);
let z = s15_fixed16_number_to_double(q15_16_z);
Ok(Xyzd { x, y, z })
}
#[inline]
pub(crate) fn read_cicp_tag(
slice: &[u8],
entry: usize,
tag_size: usize,
) -> Result<Option<CicpProfile>, CmsError> {
if tag_size < TAG_SIZE {
return Ok(None);
}
let last_tag_offset = tag_size.safe_add(entry)?;
if last_tag_offset > slice.len() {
return Err(CmsError::InvalidProfile);
}
let tag = &slice[entry..last_tag_offset];
if tag.len() < 12 {
return Err(CmsError::InvalidProfile);
}
let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
let def = TagTypeDefinition::from(tag_type);
if def != TagTypeDefinition::Cicp {
return Ok(None);
}
let primaries = CicpColorPrimaries::try_from(tag[8])?;
let transfer_characteristics = TransferCharacteristics::try_from(tag[9])?;
let matrix_coefficients = MatrixCoefficients::try_from(tag[10])?;
let full_range = tag[11] == 1;
Ok(Some(CicpProfile {
color_primaries: primaries,
transfer_characteristics,
matrix_coefficients,
full_range,
}))
}
}

723
deps/moxcms/src/rgb.rs vendored
View File

@@ -1,723 +0,0 @@
/*
* // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
* //
* // Use of this source code is governed by a BSD-style
* // license that can be found in the LICENSE file.
*/
use crate::math::{FusedMultiplyAdd, m_clamp, m_max, m_min};
use crate::mlaf::mlaf;
use crate::{Matrix3f, Vector3, Xyz};
use num_traits::{AsPrimitive, Bounded, Float, Num, Pow, Signed};
use pxfm::{
f_exp, f_exp2, f_exp2f, f_exp10, f_exp10f, f_expf, f_log, f_log2, f_log2f, f_log10, f_log10f,
f_logf, f_pow, f_powf,
};
use std::cmp::Ordering;
use std::ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
#[repr(C)]
#[derive(Debug, PartialOrd, PartialEq, Clone, Copy, Default)]
/// Represents any RGB values
pub struct Rgb<T> {
/// Red component
pub r: T,
/// Green component
pub g: T,
/// Blue component
pub b: T,
}
impl<T> Rgb<T> {
pub fn new(r: T, g: T, b: T) -> Rgb<T> {
Rgb { r, g, b }
}
}
impl<T> Rgb<T>
where
T: Copy,
{
pub fn dup(v: T) -> Rgb<T> {
Rgb { r: v, g: v, b: v }
}
#[inline]
pub const fn to_vector(self) -> Vector3<T> {
Vector3 {
v: [self.r, self.g, self.b],
}
}
}
impl Rgb<f32> {
#[inline(always)]
pub fn apply(&self, matrix: Matrix3f) -> Rgb<f32> {
let new_r = mlaf(
mlaf(self.r * matrix.v[0][0], self.g, matrix.v[0][1]),
self.b,
matrix.v[0][2],
);
let new_g = mlaf(
mlaf(self.r * matrix.v[1][0], self.g, matrix.v[1][1]),
self.b,
matrix.v[1][2],
);
let new_b = mlaf(
mlaf(self.r * matrix.v[2][0], self.g, matrix.v[2][1]),
self.b,
matrix.v[2][2],
);
Rgb {
r: new_r,
g: new_g,
b: new_b,
}
}
#[inline(always)]
pub fn to_xyz(&self, matrix: Matrix3f) -> Xyz {
let new_self = self.apply(matrix);
Xyz {
x: new_self.r,
y: new_self.g,
z: new_self.b,
}
}
#[inline(always)]
pub fn is_out_of_gamut(&self) -> bool {
!(0.0..=1.0).contains(&self.r)
|| !(0.0..=1.0).contains(&self.g)
|| !(0.0..=1.0).contains(&self.b)
}
}
impl<T> Index<usize> for Rgb<T> {
type Output = T;
fn index(&self, index: usize) -> &T {
match index {
0 => &self.r,
1 => &self.g,
2 => &self.b,
_ => panic!("Index out of bounds for Rgb"),
}
}
}
impl<T> IndexMut<usize> for Rgb<T> {
fn index_mut(&mut self, index: usize) -> &mut T {
match index {
0 => &mut self.r,
1 => &mut self.g,
2 => &mut self.b,
_ => panic!("Index out of bounds for RGB"),
}
}
}
macro_rules! generated_float_definition_rgb {
($T: ty) => {
impl Rgb<$T> {
#[inline]
pub fn zeroed() -> Rgb<$T> {
Rgb::<$T>::new(0., 0., 0.)
}
#[inline]
pub fn ones() -> Rgb<$T> {
Rgb::<$T>::new(1., 1., 1.)
}
#[inline]
pub fn white() -> Rgb<$T> {
Rgb::<$T>::ones()
}
#[inline]
pub fn black() -> Rgb<$T> {
Rgb::<$T>::zeroed()
}
}
};
}
generated_float_definition_rgb!(f32);
generated_float_definition_rgb!(f64);
macro_rules! generated_integral_definition_rgb {
($T: ty) => {
impl Rgb<$T> {
#[inline]
pub fn zeroed() -> Rgb<$T> {
Rgb::<$T>::new(0, 0, 0)
}
#[inline]
pub fn capped() -> Rgb<$T> {
Rgb::<$T>::new(<$T>::MAX, <$T>::MAX, <$T>::MAX)
}
#[inline]
pub fn white() -> Rgb<$T> {
Rgb::<$T>::capped()
}
#[inline]
pub fn black() -> Rgb<$T> {
Rgb::<$T>::new(0, 0, 0)
}
}
};
}
generated_integral_definition_rgb!(u8);
generated_integral_definition_rgb!(u16);
generated_integral_definition_rgb!(i8);
generated_integral_definition_rgb!(i16);
generated_integral_definition_rgb!(i32);
generated_integral_definition_rgb!(u32);
pub trait FusedPow<T> {
fn f_pow(&self, power: T) -> Self;
}
pub trait FusedLog2<T> {
fn f_log2(&self) -> Self;
}
pub trait FusedLog10<T> {
fn f_log10(&self) -> Self;
}
pub trait FusedLog<T> {
fn f_log(&self) -> Self;
}
pub trait FusedExp<T> {
fn f_exp(&self) -> Self;
}
pub trait FusedExp2<T> {
fn f_exp2(&self) -> Self;
}
pub trait FusedExp10<T> {
fn f_exp10(&self) -> Self;
}
impl FusedPow<Rgb<f32>> for Rgb<f32> {
fn f_pow(&self, power: Rgb<f32>) -> Rgb<f32> {
Rgb::new(
f_powf(self.r, power.r),
f_powf(self.g, power.g),
f_powf(self.b, power.b),
)
}
}
impl FusedPow<Rgb<f64>> for Rgb<f64> {
fn f_pow(&self, power: Rgb<f64>) -> Rgb<f64> {
Rgb::new(
f_pow(self.r, power.r),
f_pow(self.g, power.g),
f_pow(self.b, power.b),
)
}
}
impl FusedLog2<Rgb<f32>> for Rgb<f32> {
#[inline]
fn f_log2(&self) -> Rgb<f32> {
Rgb::new(f_log2f(self.r), f_log2f(self.g), f_log2f(self.b))
}
}
impl FusedLog2<Rgb<f64>> for Rgb<f64> {
#[inline]
fn f_log2(&self) -> Rgb<f64> {
Rgb::new(f_log2(self.r), f_log2(self.g), f_log2(self.b))
}
}
impl FusedLog<Rgb<f32>> for Rgb<f32> {
#[inline]
fn f_log(&self) -> Rgb<f32> {
Rgb::new(f_logf(self.r), f_logf(self.g), f_logf(self.b))
}
}
impl FusedLog<Rgb<f64>> for Rgb<f64> {
#[inline]
fn f_log(&self) -> Rgb<f64> {
Rgb::new(f_log(self.r), f_log(self.g), f_log(self.b))
}
}
impl FusedLog10<Rgb<f32>> for Rgb<f32> {
#[inline]
fn f_log10(&self) -> Rgb<f32> {
Rgb::new(f_log10f(self.r), f_log10f(self.g), f_log10f(self.b))
}
}
impl FusedLog10<Rgb<f64>> for Rgb<f64> {
#[inline]
fn f_log10(&self) -> Rgb<f64> {
Rgb::new(f_log10(self.r), f_log10(self.g), f_log10(self.b))
}
}
impl FusedExp<Rgb<f32>> for Rgb<f32> {
#[inline]
fn f_exp(&self) -> Rgb<f32> {
Rgb::new(f_expf(self.r), f_expf(self.g), f_expf(self.b))
}
}
impl FusedExp<Rgb<f64>> for Rgb<f64> {
#[inline]
fn f_exp(&self) -> Rgb<f64> {
Rgb::new(f_exp(self.r), f_exp(self.g), f_exp(self.b))
}
}
impl FusedExp2<Rgb<f32>> for Rgb<f32> {
#[inline]
fn f_exp2(&self) -> Rgb<f32> {
Rgb::new(f_exp2f(self.r), f_exp2f(self.g), f_exp2f(self.b))
}
}
impl FusedExp2<Rgb<f64>> for Rgb<f64> {
#[inline]
fn f_exp2(&self) -> Rgb<f64> {
Rgb::new(f_exp2(self.r), f_exp2(self.g), f_exp2(self.b))
}
}
impl FusedExp10<Rgb<f32>> for Rgb<f32> {
#[inline]
fn f_exp10(&self) -> Rgb<f32> {
Rgb::new(f_exp10f(self.r), f_exp10f(self.g), f_exp10f(self.b))
}
}
impl FusedExp10<Rgb<f64>> for Rgb<f64> {
#[inline]
fn f_exp10(&self) -> Rgb<f64> {
Rgb::new(f_exp10(self.r), f_exp10(self.g), f_exp10(self.b))
}
}
impl<T> Rgb<T>
where
T: Copy + AsPrimitive<f32>,
{
pub fn euclidean_distance(&self, other: Rgb<T>) -> f32 {
let dr = self.r.as_() - other.r.as_();
let dg = self.g.as_() - other.g.as_();
let db = self.b.as_() - other.b.as_();
(dr * dr + dg * dg + db * db).sqrt()
}
}
impl<T> Rgb<T>
where
T: Copy + AsPrimitive<f32>,
{
pub fn taxicab_distance(&self, other: Self) -> f32 {
let dr = self.r.as_() - other.r.as_();
let dg = self.g.as_() - other.g.as_();
let db = self.b.as_() - other.b.as_();
dr.abs() + dg.abs() + db.abs()
}
}
impl<T> Add for Rgb<T>
where
T: Add<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
Rgb::new(self.r + rhs.r, self.g + rhs.g, self.b + rhs.b)
}
}
impl<T> Sub for Rgb<T>
where
T: Sub<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
Rgb::new(self.r - rhs.r, self.g - rhs.g, self.b - rhs.b)
}
}
impl<T: Copy + Clone> Sub<T> for Rgb<T>
where
T: Sub<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn sub(self, rhs: T) -> Self::Output {
Rgb::new(self.r - rhs, self.g - rhs, self.b - rhs)
}
}
impl<T: Copy + Clone> Add<T> for Rgb<T>
where
T: Add<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn add(self, rhs: T) -> Self::Output {
Rgb::new(self.r + rhs, self.g + rhs, self.b + rhs)
}
}
impl<T: Copy + Clone> Rgb<T>
where
T: Signed,
{
#[inline]
pub fn abs(self) -> Self {
Rgb::new(self.r.abs(), self.g.abs(), self.b.abs())
}
}
impl<T> Div for Rgb<T>
where
T: Div<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn div(self, rhs: Self) -> Self::Output {
Rgb::new(self.r / rhs.r, self.g / rhs.g, self.b / rhs.b)
}
}
impl<T: Clone + Copy> Div<T> for Rgb<T>
where
T: Div<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn div(self, rhs: T) -> Self::Output {
Rgb::new(self.r / rhs, self.g / rhs, self.b / rhs)
}
}
impl<T> Mul for Rgb<T>
where
T: Mul<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn mul(self, rhs: Self) -> Self::Output {
Rgb::new(self.r * rhs.r, self.g * rhs.g, self.b * rhs.b)
}
}
impl<T: Clone + Copy> Mul<T> for Rgb<T>
where
T: Mul<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn mul(self, rhs: T) -> Self::Output {
Rgb::new(self.r * rhs, self.g * rhs, self.b * rhs)
}
}
impl<T> MulAssign for Rgb<T>
where
T: MulAssign<T>,
{
#[inline]
fn mul_assign(&mut self, rhs: Self) {
self.r *= rhs.r;
self.g *= rhs.g;
self.b *= rhs.b;
}
}
macro_rules! generated_mul_assign_definition_rgb {
($T: ty) => {
impl<T> MulAssign<$T> for Rgb<T>
where
T: MulAssign<$T>,
{
#[inline]
fn mul_assign(&mut self, rhs: $T) {
self.r *= rhs;
self.g *= rhs;
self.b *= rhs;
}
}
};
}
generated_mul_assign_definition_rgb!(i8);
generated_mul_assign_definition_rgb!(u8);
generated_mul_assign_definition_rgb!(u16);
generated_mul_assign_definition_rgb!(i16);
generated_mul_assign_definition_rgb!(u32);
generated_mul_assign_definition_rgb!(i32);
generated_mul_assign_definition_rgb!(f32);
generated_mul_assign_definition_rgb!(f64);
impl<T> AddAssign for Rgb<T>
where
T: AddAssign<T>,
{
#[inline]
fn add_assign(&mut self, rhs: Self) {
self.r += rhs.r;
self.g += rhs.g;
self.b += rhs.b;
}
}
macro_rules! generated_add_assign_definition_rgb {
($T: ty) => {
impl<T: Copy> AddAssign<$T> for Rgb<T>
where
T: AddAssign<$T>,
{
#[inline]
fn add_assign(&mut self, rhs: $T) {
self.r += rhs;
self.g += rhs;
self.b += rhs;
}
}
};
}
generated_add_assign_definition_rgb!(i8);
generated_add_assign_definition_rgb!(u8);
generated_add_assign_definition_rgb!(u16);
generated_add_assign_definition_rgb!(i16);
generated_add_assign_definition_rgb!(u32);
generated_add_assign_definition_rgb!(i32);
generated_add_assign_definition_rgb!(f32);
generated_add_assign_definition_rgb!(f64);
impl<T> DivAssign for Rgb<T>
where
T: DivAssign<T>,
{
#[inline]
fn div_assign(&mut self, rhs: Self) {
self.r /= rhs.r;
self.g /= rhs.g;
self.b /= rhs.b;
}
}
macro_rules! generated_div_assign_definition_rgb {
($T: ty) => {
impl<T: Copy> DivAssign<$T> for Rgb<T>
where
T: DivAssign<$T>,
{
#[inline]
fn div_assign(&mut self, rhs: $T) {
self.r /= rhs;
self.g /= rhs;
self.b /= rhs;
}
}
};
}
generated_div_assign_definition_rgb!(u8);
generated_div_assign_definition_rgb!(i8);
generated_div_assign_definition_rgb!(u16);
generated_div_assign_definition_rgb!(i16);
generated_div_assign_definition_rgb!(u32);
generated_div_assign_definition_rgb!(i32);
generated_div_assign_definition_rgb!(f32);
generated_div_assign_definition_rgb!(f64);
impl<T> Neg for Rgb<T>
where
T: Neg<Output = T>,
{
type Output = Rgb<T>;
#[inline]
fn neg(self) -> Self::Output {
Rgb::new(-self.r, -self.g, -self.b)
}
}
impl<T> Rgb<T>
where
T: FusedMultiplyAdd<T>,
{
pub fn mla(&self, b: Rgb<T>, c: Rgb<T>) -> Rgb<T> {
Rgb::new(
self.r.mla(b.r, c.r),
self.g.mla(b.g, c.g),
self.b.mla(b.b, c.b),
)
}
}
impl<T> Rgb<T>
where
T: Num + PartialOrd + Copy + Bounded,
{
/// Clamp function to clamp each channel within a given range
#[inline]
#[allow(clippy::manual_clamp)]
pub fn clamp(&self, min_value: T, max_value: T) -> Rgb<T> {
Rgb::new(
m_clamp(self.r, min_value, max_value),
m_clamp(self.g, min_value, max_value),
m_clamp(self.b, min_value, max_value),
)
}
/// Min function to define min
#[inline]
pub fn min(&self, other_min: T) -> Rgb<T> {
Rgb::new(
m_min(self.r, other_min),
m_min(self.g, other_min),
m_min(self.b, other_min),
)
}
/// Max function to define max
#[inline]
pub fn max(&self, other_max: T) -> Rgb<T> {
Rgb::new(
m_max(self.r, other_max),
m_max(self.g, other_max),
m_max(self.b, other_max),
)
}
/// Clamp function to clamp each channel within a given range
#[inline]
#[allow(clippy::manual_clamp)]
pub fn clamp_p(&self, min_value: Rgb<T>, max_value: Rgb<T>) -> Rgb<T> {
Rgb::new(
m_clamp(self.r, max_value.r, min_value.r),
m_clamp(self.g, max_value.g, min_value.g),
m_clamp(self.b, max_value.b, min_value.b),
)
}
/// Min function to define min
#[inline]
pub fn min_p(&self, other_min: Rgb<T>) -> Rgb<T> {
Rgb::new(
m_min(self.r, other_min.r),
m_min(self.g, other_min.g),
m_min(self.b, other_min.b),
)
}
/// Max function to define max
#[inline]
pub fn max_p(&self, other_max: Rgb<T>) -> Rgb<T> {
Rgb::new(
m_max(self.r, other_max.r),
m_max(self.g, other_max.g),
m_max(self.b, other_max.b),
)
}
}
impl<T> Rgb<T>
where
T: Float + 'static,
f32: AsPrimitive<T>,
{
#[inline]
pub fn sqrt(&self) -> Rgb<T> {
let zeros = 0f32.as_();
Rgb::new(
if self.r.partial_cmp(&zeros).unwrap_or(Ordering::Less) == Ordering::Less {
0f32.as_()
} else {
self.r.sqrt()
},
if self.g.partial_cmp(&zeros).unwrap_or(Ordering::Less) == Ordering::Less {
0f32.as_()
} else {
self.g.sqrt()
},
if self.b.partial_cmp(&zeros).unwrap_or(Ordering::Less) == Ordering::Less {
0f32.as_()
} else {
self.b.sqrt()
},
)
}
#[inline]
pub fn cbrt(&self) -> Rgb<T> {
Rgb::new(self.r.cbrt(), self.g.cbrt(), self.b.cbrt())
}
}
impl<T> Pow<T> for Rgb<T>
where
T: Float,
{
type Output = Rgb<T>;
#[inline]
fn pow(self, rhs: T) -> Self::Output {
Rgb::<T>::new(self.r.powf(rhs), self.g.powf(rhs), self.b.powf(rhs))
}
}
impl<T> Pow<Rgb<T>> for Rgb<T>
where
T: Float,
{
type Output = Rgb<T>;
#[inline]
fn pow(self, rhs: Rgb<T>) -> Self::Output {
Rgb::<T>::new(self.r.powf(rhs.r), self.g.powf(rhs.g), self.b.powf(rhs.b))
}
}
impl<T> Rgb<T> {
pub fn cast<V>(self) -> Rgb<V>
where
T: AsPrimitive<V>,
V: Copy + 'static,
{
Rgb::new(self.r.as_(), self.g.as_(), self.b.as_())
}
}
impl<T> Rgb<T>
where
T: Float + 'static,
{
pub fn round(self) -> Rgb<T> {
Rgb::new(self.r.round(), self.g.round(), self.b.round())
}
}

View File

@@ -1,103 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::CmsError;
use std::ops::Add;
pub(crate) trait SafeAdd<T: Copy + Add<T, Output = T>> {
fn safe_add(&self, other: T) -> Result<T, CmsError>;
}
pub(crate) trait SafeMul<T: Copy + Add<T, Output = T>> {
fn safe_mul(&self, other: T) -> Result<T, CmsError>;
}
pub(crate) trait SafePowi<T: Copy + Add<T, Output = T>> {
fn safe_powi(&self, power: u32) -> Result<T, CmsError>;
}
macro_rules! safe_add_impl {
($type_name: ident) => {
impl SafeAdd<$type_name> for $type_name {
#[inline(always)]
fn safe_add(&self, other: $type_name) -> Result<$type_name, CmsError> {
if let Some(result) = self.checked_add(other) {
return Ok(result);
}
Err(CmsError::OverflowingError)
}
}
};
}
safe_add_impl!(u16);
safe_add_impl!(u32);
safe_add_impl!(i32);
safe_add_impl!(usize);
safe_add_impl!(isize);
macro_rules! safe_mul_impl {
($type_name: ident) => {
impl SafeMul<$type_name> for $type_name {
#[inline(always)]
fn safe_mul(&self, other: $type_name) -> Result<$type_name, CmsError> {
if let Some(result) = self.checked_mul(other) {
return Ok(result);
}
Err(CmsError::OverflowingError)
}
}
};
}
safe_mul_impl!(u16);
safe_mul_impl!(u32);
safe_mul_impl!(i32);
safe_mul_impl!(usize);
safe_mul_impl!(isize);
macro_rules! safe_powi_impl {
($type_name: ident) => {
impl SafePowi<$type_name> for $type_name {
#[inline(always)]
fn safe_powi(&self, power: u32) -> Result<$type_name, CmsError> {
if let Some(result) = self.checked_pow(power) {
return Ok(result);
}
Err(CmsError::OverflowingError)
}
}
};
}
safe_powi_impl!(u8);
safe_powi_impl!(u16);
safe_powi_impl!(u32);
safe_powi_impl!(i32);
safe_powi_impl!(usize);
safe_powi_impl!(isize);

View File

@@ -1,102 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::Xyz;
use crate::mlaf::mlaf;
use pxfm::f_cbrtf;
#[inline]
fn srlab2_gamma(x: f32) -> f32 {
if x <= 216. / 24389. {
x * (24389. / 2700.)
} else {
1.16 * f_cbrtf(x) - 0.16
}
}
#[inline]
fn srlab2_linearize(x: f32) -> f32 {
if x <= 0.08 {
x * (2700.0 / 24389.0)
} else {
let zx = (x + 0.16) / 1.16;
zx * zx * zx
}
}
#[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)]
pub struct Srlab2 {
pub l: f32,
pub a: f32,
pub b: f32,
}
impl Srlab2 {
#[inline]
pub const fn new(l: f32, a: f32, b: f32) -> Srlab2 {
Srlab2 { l, a, b }
}
#[inline]
pub fn from_xyz(xyz: Xyz) -> Srlab2 {
let lx = srlab2_gamma(xyz.x);
let ly = srlab2_gamma(xyz.y);
let lz = srlab2_gamma(xyz.z);
let l = mlaf(mlaf(0.629054 * ly, -0.000008, lz), 0.37095, lx);
let a = mlaf(mlaf(6.634684 * lx, -7.505078, ly), 0.870328, lz);
let b = mlaf(mlaf(0.639569 * lx, 1.084576, ly), -1.724152, lz);
Srlab2 { l, a, b }
}
#[inline]
pub fn to_xyz(&self) -> Xyz {
let x = mlaf(mlaf(self.l, 0.09041272, self.a), 0.045634452, self.b);
let y = mlaf(mlaf(self.l, -0.05331593, self.a), -0.026917785, self.b);
let z = mlaf(self.l, -0.58, self.b);
let lx = srlab2_linearize(x);
let ly = srlab2_linearize(y);
let lz = srlab2_linearize(z);
Xyz::new(lx, ly, lz)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_srlab2() {
let xyz = Xyz::new(0.3, 0.65, 0.66);
let srlab2 = Srlab2::from_xyz(xyz);
let r_xyz = srlab2.to_xyz();
assert!((r_xyz.x - xyz.x).abs() < 1e-5);
assert!((r_xyz.y - xyz.y).abs() < 1e-5);
assert!((r_xyz.z - xyz.z).abs() < 1e-5);
}
}

271
deps/moxcms/src/tag.rs vendored
View File

@@ -1,271 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::CmsError;
pub(crate) const TAG_SIZE: usize = 12;
#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
pub(crate) enum Tag {
RedXyz,
GreenXyz,
BlueXyz,
RedToneReproduction,
GreenToneReproduction,
BlueToneReproduction,
GreyToneReproduction,
MediaWhitePoint,
CodeIndependentPoints,
ChromaticAdaptation,
BlackPoint,
DeviceToPcsLutPerceptual,
DeviceToPcsLutColorimetric,
DeviceToPcsLutSaturation,
PcsToDeviceLutPerceptual,
PcsToDeviceLutColorimetric,
PcsToDeviceLutSaturation,
ProfileDescription,
Copyright,
ViewingConditionsDescription,
DeviceManufacturer,
DeviceModel,
Gamut,
Luminance,
Measurement,
Chromaticity,
ObserverConditions,
CharTarget,
Technology,
CalibrationDateTime,
}
impl TryFrom<u32> for Tag {
type Error = CmsError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
if value == u32::from_ne_bytes(*b"rXYZ").to_be() {
return Ok(Self::RedXyz);
} else if value == u32::from_ne_bytes(*b"gXYZ").to_be() {
return Ok(Self::GreenXyz);
} else if value == u32::from_ne_bytes(*b"bXYZ").to_be() {
return Ok(Self::BlueXyz);
} else if value == u32::from_ne_bytes(*b"rTRC").to_be() {
return Ok(Self::RedToneReproduction);
} else if value == u32::from_ne_bytes(*b"gTRC").to_be() {
return Ok(Self::GreenToneReproduction);
} else if value == u32::from_ne_bytes(*b"bTRC").to_be() {
return Ok(Self::BlueToneReproduction);
} else if value == u32::from_ne_bytes(*b"kTRC").to_be() {
return Ok(Self::GreyToneReproduction);
} else if value == u32::from_ne_bytes(*b"wtpt").to_be() {
return Ok(Self::MediaWhitePoint);
} else if value == u32::from_ne_bytes(*b"cicp").to_be() {
return Ok(Self::CodeIndependentPoints);
} else if value == u32::from_ne_bytes(*b"chad").to_be() {
return Ok(Self::ChromaticAdaptation);
} else if value == u32::from_ne_bytes(*b"bkpt").to_be() {
return Ok(Self::BlackPoint);
} else if value == u32::from_ne_bytes(*b"A2B0").to_be() {
return Ok(Self::DeviceToPcsLutPerceptual);
} else if value == u32::from_ne_bytes(*b"A2B1").to_be() {
return Ok(Self::DeviceToPcsLutColorimetric);
} else if value == u32::from_ne_bytes(*b"A2B2").to_be() {
return Ok(Self::DeviceToPcsLutSaturation);
} else if value == u32::from_ne_bytes(*b"B2A0").to_be() {
return Ok(Self::PcsToDeviceLutPerceptual);
} else if value == u32::from_ne_bytes(*b"B2A1").to_be() {
return Ok(Self::PcsToDeviceLutColorimetric);
} else if value == u32::from_ne_bytes(*b"B2A2").to_be() {
return Ok(Self::PcsToDeviceLutSaturation);
} else if value == u32::from_ne_bytes(*b"desc").to_be() {
return Ok(Self::ProfileDescription);
} else if value == u32::from_ne_bytes(*b"cprt").to_be() {
return Ok(Self::Copyright);
} else if value == u32::from_ne_bytes(*b"vued").to_be() {
return Ok(Self::ViewingConditionsDescription);
} else if value == u32::from_ne_bytes(*b"dmnd").to_be() {
return Ok(Self::DeviceManufacturer);
} else if value == u32::from_ne_bytes(*b"dmdd").to_be() {
return Ok(Self::DeviceModel);
} else if value == u32::from_ne_bytes(*b"gamt").to_be() {
return Ok(Self::Gamut);
} else if value == u32::from_ne_bytes(*b"lumi").to_be() {
return Ok(Self::Luminance);
} else if value == u32::from_ne_bytes(*b"meas").to_be() {
return Ok(Self::Measurement);
} else if value == u32::from_ne_bytes(*b"chrm").to_be() {
return Ok(Self::Chromaticity);
} else if value == u32::from_ne_bytes(*b"view").to_be() {
return Ok(Self::ObserverConditions);
} else if value == u32::from_ne_bytes(*b"targ").to_be() {
return Ok(Self::CharTarget);
} else if value == u32::from_ne_bytes(*b"tech").to_be() {
return Ok(Self::Technology);
} else if value == u32::from_ne_bytes(*b"calt").to_be() {
return Ok(Self::CalibrationDateTime);
}
Err(CmsError::UnknownTag(value))
}
}
impl From<Tag> for u32 {
fn from(value: Tag) -> Self {
match value {
Tag::RedXyz => u32::from_ne_bytes(*b"rXYZ").to_be(),
Tag::GreenXyz => u32::from_ne_bytes(*b"gXYZ").to_be(),
Tag::BlueXyz => u32::from_ne_bytes(*b"bXYZ").to_be(),
Tag::RedToneReproduction => u32::from_ne_bytes(*b"rTRC").to_be(),
Tag::GreenToneReproduction => u32::from_ne_bytes(*b"gTRC").to_be(),
Tag::BlueToneReproduction => u32::from_ne_bytes(*b"bTRC").to_be(),
Tag::GreyToneReproduction => u32::from_ne_bytes(*b"kTRC").to_be(),
Tag::MediaWhitePoint => u32::from_ne_bytes(*b"wtpt").to_be(),
Tag::CodeIndependentPoints => u32::from_ne_bytes(*b"cicp").to_be(),
Tag::ChromaticAdaptation => u32::from_ne_bytes(*b"chad").to_be(),
Tag::BlackPoint => u32::from_ne_bytes(*b"bkpt").to_be(),
Tag::DeviceToPcsLutPerceptual => u32::from_ne_bytes(*b"A2B0").to_be(),
Tag::DeviceToPcsLutColorimetric => u32::from_ne_bytes(*b"A2B1").to_be(),
Tag::DeviceToPcsLutSaturation => u32::from_ne_bytes(*b"A2B2").to_be(),
Tag::PcsToDeviceLutPerceptual => u32::from_ne_bytes(*b"B2A0").to_be(),
Tag::PcsToDeviceLutColorimetric => u32::from_ne_bytes(*b"B2A1").to_be(),
Tag::PcsToDeviceLutSaturation => u32::from_ne_bytes(*b"B2A2").to_be(),
Tag::ProfileDescription => u32::from_ne_bytes(*b"desc").to_be(),
Tag::Copyright => u32::from_ne_bytes(*b"cprt").to_be(),
Tag::ViewingConditionsDescription => u32::from_ne_bytes(*b"vued").to_be(),
Tag::DeviceManufacturer => u32::from_ne_bytes(*b"dmnd").to_be(),
Tag::DeviceModel => u32::from_ne_bytes(*b"dmdd").to_be(),
Tag::Gamut => u32::from_ne_bytes(*b"gamt").to_be(),
Tag::Luminance => u32::from_ne_bytes(*b"lumi").to_be(),
Tag::Measurement => u32::from_ne_bytes(*b"meas").to_be(),
Tag::Chromaticity => u32::from_ne_bytes(*b"chrm").to_be(),
Tag::ObserverConditions => u32::from_ne_bytes(*b"view").to_be(),
Tag::CharTarget => u32::from_ne_bytes(*b"targ").to_be(),
Tag::Technology => u32::from_ne_bytes(*b"tech").to_be(),
Tag::CalibrationDateTime => u32::from_ne_bytes(*b"calt").to_be(),
}
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub(crate) enum TagTypeDefinition {
Text,
MultiLocalizedUnicode,
Description,
MabLut,
MbaLut,
ParametricToneCurve,
LutToneCurve,
Xyz,
MultiProcessElement,
DefViewingConditions,
Signature,
Cicp,
DateTime,
S15Fixed16Array,
U8Array,
U16Fixed16Array,
U16Array,
U32Array,
U64Array,
Measurement,
NotAllowed,
}
impl From<u32> for TagTypeDefinition {
fn from(value: u32) -> Self {
if value == u32::from_ne_bytes(*b"mluc").to_be() {
return TagTypeDefinition::MultiLocalizedUnicode;
} else if value == u32::from_ne_bytes(*b"desc").to_be() {
return TagTypeDefinition::Description;
} else if value == u32::from_ne_bytes(*b"text").to_be() {
return TagTypeDefinition::Text;
} else if value == u32::from_ne_bytes(*b"mAB ").to_be() {
return TagTypeDefinition::MabLut;
} else if value == u32::from_ne_bytes(*b"mBA ").to_be() {
return TagTypeDefinition::MbaLut;
} else if value == u32::from_ne_bytes(*b"para").to_be() {
return TagTypeDefinition::ParametricToneCurve;
} else if value == u32::from_ne_bytes(*b"curv").to_be() {
return TagTypeDefinition::LutToneCurve;
} else if value == u32::from_ne_bytes(*b"XYZ ").to_be() {
return TagTypeDefinition::Xyz;
} else if value == u32::from_ne_bytes(*b"mpet").to_be() {
return TagTypeDefinition::MultiProcessElement;
} else if value == u32::from_ne_bytes(*b"view").to_be() {
return TagTypeDefinition::DefViewingConditions;
} else if value == u32::from_ne_bytes(*b"sig ").to_be() {
return TagTypeDefinition::Signature;
} else if value == u32::from_ne_bytes(*b"cicp").to_be() {
return TagTypeDefinition::Cicp;
} else if value == u32::from_ne_bytes(*b"dtim").to_be() {
return TagTypeDefinition::DateTime;
} else if value == u32::from_ne_bytes(*b"meas").to_be() {
return TagTypeDefinition::Measurement;
} else if value == u32::from_ne_bytes(*b"sf32").to_be() {
return TagTypeDefinition::S15Fixed16Array;
} else if value == u32::from_ne_bytes(*b"uf32").to_be() {
return TagTypeDefinition::U16Fixed16Array;
} else if value == u32::from_ne_bytes(*b"ui16").to_be() {
return TagTypeDefinition::U16Array;
} else if value == u32::from_ne_bytes(*b"ui32").to_be() {
return TagTypeDefinition::U32Array;
} else if value == u32::from_ne_bytes(*b"ui64").to_be() {
return TagTypeDefinition::U64Array;
} else if value == u32::from_ne_bytes(*b"ui08").to_be() {
return TagTypeDefinition::U8Array;
}
TagTypeDefinition::NotAllowed
}
}
impl From<TagTypeDefinition> for u32 {
fn from(value: TagTypeDefinition) -> Self {
match value {
TagTypeDefinition::MultiLocalizedUnicode => u32::from_ne_bytes(*b"mluc").to_be(),
TagTypeDefinition::Description => u32::from_ne_bytes(*b"desc").to_be(),
TagTypeDefinition::Text => u32::from_ne_bytes(*b"text").to_be(),
TagTypeDefinition::MabLut => u32::from_ne_bytes(*b"mAB ").to_be(),
TagTypeDefinition::MbaLut => u32::from_ne_bytes(*b"mBA ").to_be(),
TagTypeDefinition::ParametricToneCurve => u32::from_ne_bytes(*b"para").to_be(),
TagTypeDefinition::LutToneCurve => u32::from_ne_bytes(*b"curv").to_be(),
TagTypeDefinition::Xyz => u32::from_ne_bytes(*b"XYZ ").to_be(),
TagTypeDefinition::MultiProcessElement => u32::from_ne_bytes(*b"mpet").to_be(),
TagTypeDefinition::DefViewingConditions => u32::from_ne_bytes(*b"view").to_be(),
TagTypeDefinition::Signature => u32::from_ne_bytes(*b"sig ").to_be(),
TagTypeDefinition::Cicp => u32::from_ne_bytes(*b"cicp").to_be(),
TagTypeDefinition::DateTime => u32::from_ne_bytes(*b"dtim").to_be(),
TagTypeDefinition::S15Fixed16Array => u32::from_ne_bytes(*b"sf32").to_be(),
TagTypeDefinition::U16Fixed16Array => u32::from_ne_bytes(*b"uf32").to_be(),
TagTypeDefinition::U8Array => u32::from_ne_bytes(*b"ui08").to_be(),
TagTypeDefinition::U16Array => u32::from_ne_bytes(*b"ui16").to_be(),
TagTypeDefinition::U32Array => u32::from_ne_bytes(*b"ui32").to_be(),
TagTypeDefinition::U64Array => u32::from_ne_bytes(*b"ui64").to_be(),
TagTypeDefinition::Measurement => u32::from_ne_bytes(*b"meas").to_be(),
TagTypeDefinition::NotAllowed => 0,
}
}
}

File diff suppressed because it is too large Load Diff

1583
deps/moxcms/src/trc.rs vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,889 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::profile::{LutDataType, ProfileHeader};
use crate::tag::{TAG_SIZE, Tag, TagTypeDefinition};
use crate::trc::ToneReprCurve;
use crate::{
CicpProfile, CmsError, ColorDateTime, ColorProfile, DataColorSpace, LocalizableString,
LutMultidimensionalType, LutStore, LutType, LutWarehouse, Matrix3d, ProfileClass,
ProfileSignature, ProfileText, ProfileVersion, Vector3d, Xyzd,
};
pub(crate) trait FloatToFixedS15Fixed16 {
fn to_s15_fixed16(self) -> i32;
}
pub(crate) trait FloatToFixedU8Fixed8 {
fn to_u8_fixed8(self) -> u16;
}
// pub(crate) trait FloatToFixedU16 {
// fn to_fixed_u16(self) -> u16;
// }
// impl FloatToFixedU16 for f32 {
// #[inline]
// fn to_fixed_u16(self) -> u16 {
// const SCALE: f64 = (1 << 16) as f64;
// (self as f64 * SCALE + 0.5)
// .floor()
// .clamp(u16::MIN as f64, u16::MAX as f64) as u16
// }
// }
impl FloatToFixedS15Fixed16 for f32 {
#[inline]
fn to_s15_fixed16(self) -> i32 {
const SCALE: f64 = (1 << 16) as f64;
(self as f64 * SCALE + 0.5)
.floor()
.clamp(i32::MIN as f64, i32::MAX as f64) as i32
}
}
impl FloatToFixedS15Fixed16 for f64 {
#[inline]
fn to_s15_fixed16(self) -> i32 {
const SCALE: f64 = (1 << 16) as f64;
(self * SCALE + 0.5)
.floor()
.clamp(i32::MIN as f64, i32::MAX as f64) as i32
}
}
#[inline]
fn write_u32_be(into: &mut Vec<u8>, value: u32) {
let bytes = value.to_be_bytes();
into.push(bytes[0]);
into.push(bytes[1]);
into.push(bytes[2]);
into.push(bytes[3]);
}
#[inline]
pub(crate) fn write_u16_be(into: &mut Vec<u8>, value: u16) {
let bytes = value.to_be_bytes();
into.push(bytes[0]);
into.push(bytes[1]);
}
#[inline]
fn write_i32_be(into: &mut Vec<u8>, value: i32) {
let bytes = value.to_be_bytes();
into.push(bytes[0]);
into.push(bytes[1]);
into.push(bytes[2]);
into.push(bytes[3]);
}
fn first_two_ascii_bytes(s: &String) -> [u8; 2] {
let bytes = s.as_bytes();
if bytes.len() >= 2 {
bytes[0..2].try_into().unwrap()
} else if bytes.len() == 1 {
let vec = vec![bytes[0], 0u8];
vec.try_into().unwrap()
} else {
let vec = vec![0u8, 0u8];
vec.try_into().unwrap()
}
}
/// Writes Multi Localized Unicode
#[inline]
fn write_mluc(into: &mut Vec<u8>, strings: &[LocalizableString]) -> usize {
assert!(!strings.is_empty());
let start = into.len();
let tag_def: u32 = TagTypeDefinition::MultiLocalizedUnicode.into();
write_u32_be(into, tag_def);
write_u32_be(into, 0);
let number_of_records = strings.len();
write_u32_be(into, number_of_records as u32);
write_u32_be(into, 12); // Record size, must be 12
let lang = first_two_ascii_bytes(&strings[0].language);
into.extend_from_slice(&lang);
let country = first_two_ascii_bytes(&strings[0].country);
into.extend_from_slice(&country);
let first_string_len = strings[0].value.len() * 2;
write_u32_be(into, first_string_len as u32);
let mut first_string_offset = 16 + 12 * strings.len();
write_u32_be(into, first_string_offset as u32);
first_string_offset += first_string_len;
for record in strings.iter().skip(1) {
let lang = first_two_ascii_bytes(&record.language);
into.extend_from_slice(&lang);
let country = first_two_ascii_bytes(&record.country);
into.extend_from_slice(&country);
let first_string_len = record.value.len() * 2;
write_u32_be(into, first_string_len as u32);
write_u32_be(into, first_string_offset as u32);
first_string_offset += first_string_len;
}
for record in strings.iter() {
for chunk in record.value.encode_utf16() {
write_u16_be(into, chunk);
}
}
let end = into.len();
end - start
}
#[inline]
fn write_string_value(into: &mut Vec<u8>, text: &ProfileText) -> usize {
match text {
ProfileText::PlainString(text) => {
let vec = vec![LocalizableString {
language: "en".to_string(),
country: "US".to_string(),
value: text.clone(),
}];
write_mluc(into, &vec)
}
ProfileText::Localizable(localizable) => {
if localizable.is_empty() {
return 0;
}
write_mluc(into, localizable)
}
ProfileText::Description(description) => {
let vec = vec![LocalizableString {
language: "en".to_string(),
country: "US".to_string(),
value: description.unicode_string.clone(),
}];
write_mluc(into, &vec)
}
}
}
#[inline]
fn write_xyz_tag_value(into: &mut Vec<u8>, xyz: Xyzd) {
let tag_definition: u32 = TagTypeDefinition::Xyz.into();
write_u32_be(into, tag_definition);
write_u32_be(into, 0);
let x_fixed = xyz.x.to_s15_fixed16();
write_i32_be(into, x_fixed);
let y_fixed = xyz.y.to_s15_fixed16();
write_i32_be(into, y_fixed);
let z_fixed = xyz.z.to_s15_fixed16();
write_i32_be(into, z_fixed);
}
#[inline]
fn write_tag_entry(into: &mut Vec<u8>, tag: Tag, tag_entry: usize, tag_size: usize) {
let tag_value: u32 = tag.into();
write_u32_be(into, tag_value);
write_u32_be(into, tag_entry as u32);
write_u32_be(into, tag_size as u32);
}
fn write_trc_entry(into: &mut Vec<u8>, trc: &ToneReprCurve) -> Result<usize, CmsError> {
match trc {
ToneReprCurve::Lut(lut) => {
let curv: u32 = TagTypeDefinition::LutToneCurve.into();
write_u32_be(into, curv);
write_u32_be(into, 0);
write_u32_be(into, lut.len() as u32);
for item in lut.iter() {
write_u16_be(into, *item);
}
Ok(12 + lut.len() * 2)
}
ToneReprCurve::Parametric(parametric_curve) => {
if parametric_curve.len() > 7
|| parametric_curve.len() == 6
|| parametric_curve.len() == 2
{
return Err(CmsError::InvalidProfile);
}
let para: u32 = TagTypeDefinition::ParametricToneCurve.into();
write_u32_be(into, para);
write_u32_be(into, 0);
if parametric_curve.len() == 1 {
write_u16_be(into, 0);
} else if parametric_curve.len() == 3 {
write_u16_be(into, 1);
} else if parametric_curve.len() == 4 {
write_u16_be(into, 2);
} else if parametric_curve.len() == 5 {
write_u16_be(into, 3);
} else if parametric_curve.len() == 7 {
write_u16_be(into, 4);
}
write_u16_be(into, 0);
for item in parametric_curve.iter() {
write_i32_be(into, item.to_s15_fixed16());
}
Ok(12 + 4 * parametric_curve.len())
}
}
}
#[inline]
fn write_cicp_entry(into: &mut Vec<u8>, cicp: &CicpProfile) {
let cicp_tag: u32 = TagTypeDefinition::Cicp.into();
write_u32_be(into, cicp_tag);
write_u32_be(into, 0);
into.push(cicp.color_primaries as u8);
into.push(cicp.transfer_characteristics as u8);
into.push(cicp.matrix_coefficients as u8);
into.push(if cicp.full_range { 1 } else { 0 });
}
fn write_chad(into: &mut Vec<u8>, matrix: Matrix3d) {
let arr_type: u32 = TagTypeDefinition::S15Fixed16Array.into();
write_u32_be(into, arr_type);
write_u32_be(into, 0);
write_matrix3d(into, matrix);
}
#[inline]
fn write_matrix3d(into: &mut Vec<u8>, v: Matrix3d) {
write_i32_be(into, v.v[0][0].to_s15_fixed16());
write_i32_be(into, v.v[0][1].to_s15_fixed16());
write_i32_be(into, v.v[0][2].to_s15_fixed16());
write_i32_be(into, v.v[1][0].to_s15_fixed16());
write_i32_be(into, v.v[1][1].to_s15_fixed16());
write_i32_be(into, v.v[1][2].to_s15_fixed16());
write_i32_be(into, v.v[2][0].to_s15_fixed16());
write_i32_be(into, v.v[2][1].to_s15_fixed16());
write_i32_be(into, v.v[2][2].to_s15_fixed16());
}
#[inline]
fn write_vector3d(into: &mut Vec<u8>, v: Vector3d) {
write_i32_be(into, v.v[0].to_s15_fixed16());
write_i32_be(into, v.v[1].to_s15_fixed16());
write_i32_be(into, v.v[2].to_s15_fixed16());
}
#[inline]
fn write_lut_entry(into: &mut Vec<u8>, lut: &LutDataType) -> Result<usize, CmsError> {
if !lut.has_same_kind() {
return Err(CmsError::InvalidProfile);
}
let start = into.len();
let lut16_tag: u32 = match &lut.input_table {
LutStore::Store8(_) => LutType::Lut8.into(),
LutStore::Store16(_) => LutType::Lut16.into(),
};
write_u32_be(into, lut16_tag);
write_u32_be(into, 0);
into.push(lut.num_input_channels);
into.push(lut.num_output_channels);
into.push(lut.num_clut_grid_points);
into.push(0);
write_matrix3d(into, lut.matrix);
write_u16_be(into, lut.num_input_table_entries);
write_u16_be(into, lut.num_output_table_entries);
match &lut.input_table {
LutStore::Store8(input_table) => {
for &item in input_table.iter() {
into.push(item);
}
}
LutStore::Store16(input_table) => {
for &item in input_table.iter() {
write_u16_be(into, item);
}
}
}
match &lut.clut_table {
LutStore::Store8(input_table) => {
for &item in input_table.iter() {
into.push(item);
}
}
LutStore::Store16(input_table) => {
for &item in input_table.iter() {
write_u16_be(into, item);
}
}
}
match &lut.output_table {
LutStore::Store8(input_table) => {
for &item in input_table.iter() {
into.push(item);
}
}
LutStore::Store16(input_table) => {
for &item in input_table.iter() {
write_u16_be(into, item);
}
}
}
let end = into.len();
Ok(end - start)
}
#[inline]
fn write_mab_entry(
into: &mut Vec<u8>,
lut: &LutMultidimensionalType,
is_a_to_b: bool,
) -> Result<usize, CmsError> {
let start = into.len();
let lut16_tag: u32 = if is_a_to_b {
LutType::LutMab.into()
} else {
LutType::LutMba.into()
};
write_u32_be(into, lut16_tag);
write_u32_be(into, 0);
into.push(lut.num_input_channels);
into.push(lut.num_output_channels);
write_u16_be(into, 0);
let mut working_offset = 32usize;
let mut data = Vec::new();
// Offset to "B curves"
if !lut.b_curves.is_empty() {
while working_offset % 4 != 0 {
data.push(0);
working_offset += 1;
}
write_u32_be(into, working_offset as u32);
for trc in lut.b_curves.iter() {
let curve_size = write_trc_entry(&mut data, trc)?;
working_offset += curve_size;
while working_offset % 4 != 0 {
data.push(0);
working_offset += 1;
}
}
} else {
write_u32_be(into, 0);
}
// Offset to matrix
if !lut.m_curves.is_empty() {
while working_offset % 4 != 0 {
data.push(0);
working_offset += 1;
}
write_u32_be(into, working_offset as u32);
write_matrix3d(&mut data, lut.matrix);
write_vector3d(&mut data, lut.bias);
working_offset += 9 * 4 + 3 * 4;
// Offset to "M curves"
write_u32_be(into, working_offset as u32);
for trc in lut.m_curves.iter() {
let curve_size = write_trc_entry(&mut data, trc)?;
working_offset += curve_size;
while working_offset % 4 != 0 {
data.push(0);
working_offset += 1;
}
}
} else {
// Offset to matrix
write_u32_be(into, 0);
// Offset to "M curves"
write_u32_be(into, 0);
}
let mut clut_start = data.len();
// Offset to CLUT
if let Some(clut) = &lut.clut {
while working_offset % 4 != 0 {
data.push(0);
working_offset += 1;
}
clut_start = data.len();
write_u32_be(into, working_offset as u32);
// Writing CLUT
for &pt in lut.grid_points.iter() {
data.push(pt);
}
data.push(match clut {
LutStore::Store8(_) => 1,
LutStore::Store16(_) => 2,
}); // Entry size
data.push(0);
data.push(0);
data.push(0);
match clut {
LutStore::Store8(store) => {
for &element in store.iter() {
data.push(element)
}
}
LutStore::Store16(store) => {
for &element in store.iter() {
write_u16_be(&mut data, element);
}
}
}
} else {
write_u32_be(into, 0);
}
let clut_size = data.len() - clut_start;
working_offset += clut_size;
// Offset to "A curves"
if !lut.a_curves.is_empty() {
while working_offset % 4 != 0 {
data.push(0);
working_offset += 1;
}
write_u32_be(into, working_offset as u32);
for trc in lut.a_curves.iter() {
let curve_size = write_trc_entry(&mut data, trc)?;
working_offset += curve_size;
while working_offset % 4 != 0 {
data.push(0);
working_offset += 1;
}
}
} else {
write_u32_be(into, 0);
}
into.extend(data);
let end = into.len();
Ok(end - start)
}
fn write_lut(into: &mut Vec<u8>, lut: &LutWarehouse, is_a_to_b: bool) -> Result<usize, CmsError> {
match lut {
LutWarehouse::Lut(lut) => Ok(write_lut_entry(into, lut)?),
LutWarehouse::Multidimensional(mab) => write_mab_entry(into, mab, is_a_to_b),
}
}
impl ProfileHeader {
fn encode(&self) -> Vec<u8> {
let mut encoder: Vec<u8> = Vec::with_capacity(size_of::<ProfileHeader>());
write_u32_be(&mut encoder, self.size); // Size
write_u32_be(&mut encoder, 0); // CMM Type
write_u32_be(&mut encoder, self.version.into()); // Version Number Type
write_u32_be(&mut encoder, self.profile_class.into()); // Profile class
write_u32_be(&mut encoder, self.data_color_space.into()); // Data color space
write_u32_be(&mut encoder, self.pcs.into()); // PCS
self.creation_date_time.encode(&mut encoder); // Date time
write_u32_be(&mut encoder, self.signature.into()); // Profile signature
write_u32_be(&mut encoder, self.platform);
write_u32_be(&mut encoder, self.flags);
write_u32_be(&mut encoder, self.device_manufacturer);
write_u32_be(&mut encoder, self.device_model);
for &i in self.device_attributes.iter() {
encoder.push(i);
}
write_u32_be(&mut encoder, self.rendering_intent.into());
write_i32_be(&mut encoder, self.illuminant.x.to_s15_fixed16());
write_i32_be(&mut encoder, self.illuminant.y.to_s15_fixed16());
write_i32_be(&mut encoder, self.illuminant.z.to_s15_fixed16());
write_u32_be(&mut encoder, self.creator);
for &i in self.profile_id.iter() {
encoder.push(i);
}
for &i in self.reserved.iter() {
encoder.push(i);
}
write_u32_be(&mut encoder, self.tag_count);
encoder
}
}
impl ColorProfile {
fn writable_tags_count(&self) -> usize {
let mut tags_count = 0usize;
if self.red_colorant != Xyzd::default() {
tags_count += 1;
}
if self.green_colorant != Xyzd::default() {
tags_count += 1;
}
if self.blue_colorant != Xyzd::default() {
tags_count += 1;
}
if self.red_trc.is_some() {
tags_count += 1;
}
if self.green_trc.is_some() {
tags_count += 1;
}
if self.blue_trc.is_some() {
tags_count += 1;
}
if self.gray_trc.is_some() {
tags_count += 1;
}
if self.cicp.is_some() {
tags_count += 1;
}
if self.media_white_point.is_some() {
tags_count += 1;
}
if self.gamut.is_some() {
tags_count += 1;
}
if self.chromatic_adaptation.is_some() {
tags_count += 1;
}
if self.lut_a_to_b_perceptual.is_some() {
tags_count += 1;
}
if self.lut_a_to_b_colorimetric.is_some() {
tags_count += 1;
}
if self.lut_a_to_b_saturation.is_some() {
tags_count += 1;
}
if self.lut_b_to_a_perceptual.is_some() {
tags_count += 1;
}
if self.lut_b_to_a_colorimetric.is_some() {
tags_count += 1;
}
if self.lut_b_to_a_saturation.is_some() {
tags_count += 1;
}
if self.luminance.is_some() {
tags_count += 1;
}
if let Some(description) = &self.description {
if description.has_values() {
tags_count += 1;
}
}
if let Some(copyright) = &self.copyright {
if copyright.has_values() {
tags_count += 1;
}
}
if let Some(vd) = &self.viewing_conditions_description {
if vd.has_values() {
tags_count += 1;
}
}
if let Some(vd) = &self.device_model {
if vd.has_values() {
tags_count += 1;
}
}
if let Some(vd) = &self.device_manufacturer {
if vd.has_values() {
tags_count += 1;
}
}
tags_count
}
/// Encodes profile
pub fn encode(&self) -> Result<Vec<u8>, CmsError> {
let mut entries = Vec::new();
let tags_count = self.writable_tags_count();
let mut tags = Vec::with_capacity(TAG_SIZE * tags_count);
let mut base_offset = size_of::<ProfileHeader>() + TAG_SIZE * tags_count;
if self.red_colorant != Xyzd::default() {
write_tag_entry(&mut tags, Tag::RedXyz, base_offset, 20);
write_xyz_tag_value(&mut entries, self.red_colorant);
base_offset += 20;
}
if self.green_colorant != Xyzd::default() {
write_tag_entry(&mut tags, Tag::GreenXyz, base_offset, 20);
write_xyz_tag_value(&mut entries, self.green_colorant);
base_offset += 20;
}
if self.blue_colorant != Xyzd::default() {
write_tag_entry(&mut tags, Tag::BlueXyz, base_offset, 20);
write_xyz_tag_value(&mut entries, self.blue_colorant);
base_offset += 20;
}
if let Some(chad) = self.chromatic_adaptation {
write_tag_entry(&mut tags, Tag::ChromaticAdaptation, base_offset, 8 + 9 * 4);
write_chad(&mut entries, chad);
base_offset += 8 + 9 * 4;
}
if let Some(trc) = &self.red_trc {
let entry_size = write_trc_entry(&mut entries, trc)?;
write_tag_entry(&mut tags, Tag::RedToneReproduction, base_offset, entry_size);
base_offset += entry_size;
}
if let Some(trc) = &self.green_trc {
let entry_size = write_trc_entry(&mut entries, trc)?;
write_tag_entry(
&mut tags,
Tag::GreenToneReproduction,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(trc) = &self.blue_trc {
let entry_size = write_trc_entry(&mut entries, trc)?;
write_tag_entry(
&mut tags,
Tag::BlueToneReproduction,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(trc) = &self.gray_trc {
let entry_size = write_trc_entry(&mut entries, trc)?;
write_tag_entry(
&mut tags,
Tag::GreyToneReproduction,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if self.white_point != Xyzd::default() {
write_tag_entry(&mut tags, Tag::MediaWhitePoint, base_offset, 20);
write_xyz_tag_value(&mut entries, self.white_point);
base_offset += 20;
}
let has_cicp = self.cicp.is_some();
// This tag may be present when the data colour space in the profile header is RGB, YCbCr, or XYZ, and the
// profile class in the profile header is Input or Display. The tag shall not be present for other data colour spaces
// or profile classes indicated in the profile header.
if let Some(cicp) = &self.cicp {
if (self.profile_class == ProfileClass::InputDevice
|| self.profile_class == ProfileClass::DisplayDevice)
&& (self.color_space == DataColorSpace::Rgb
|| self.color_space == DataColorSpace::YCbr
|| self.color_space == DataColorSpace::Xyz)
{
write_tag_entry(&mut tags, Tag::CodeIndependentPoints, base_offset, 12);
write_cicp_entry(&mut entries, cicp);
base_offset += 12;
}
}
if let Some(lut) = &self.lut_a_to_b_perceptual {
let entry_size = write_lut(&mut entries, lut, true)?;
write_tag_entry(
&mut tags,
Tag::DeviceToPcsLutPerceptual,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(lut) = &self.lut_a_to_b_colorimetric {
let entry_size = write_lut(&mut entries, lut, true)?;
write_tag_entry(
&mut tags,
Tag::DeviceToPcsLutColorimetric,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(lut) = &self.lut_a_to_b_saturation {
let entry_size = write_lut(&mut entries, lut, true)?;
write_tag_entry(
&mut tags,
Tag::DeviceToPcsLutSaturation,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(lut) = &self.lut_b_to_a_perceptual {
let entry_size = write_lut(&mut entries, lut, false)?;
write_tag_entry(
&mut tags,
Tag::PcsToDeviceLutPerceptual,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(lut) = &self.lut_b_to_a_colorimetric {
let entry_size = write_lut(&mut entries, lut, false)?;
write_tag_entry(
&mut tags,
Tag::PcsToDeviceLutColorimetric,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(lut) = &self.lut_b_to_a_saturation {
let entry_size = write_lut(&mut entries, lut, false)?;
write_tag_entry(
&mut tags,
Tag::PcsToDeviceLutSaturation,
base_offset,
entry_size,
);
base_offset += entry_size;
}
if let Some(lut) = &self.gamut {
let entry_size = write_lut(&mut entries, lut, false)?;
write_tag_entry(&mut tags, Tag::Gamut, base_offset, entry_size);
base_offset += entry_size;
}
if let Some(luminance) = self.luminance {
write_tag_entry(&mut tags, Tag::Luminance, base_offset, 20);
write_xyz_tag_value(&mut entries, luminance);
base_offset += 20;
}
if let Some(description) = &self.description {
if description.has_values() {
let entry_size = write_string_value(&mut entries, description);
write_tag_entry(&mut tags, Tag::ProfileDescription, base_offset, entry_size);
base_offset += entry_size;
}
}
if let Some(copyright) = &self.copyright {
if copyright.has_values() {
let entry_size = write_string_value(&mut entries, copyright);
write_tag_entry(&mut tags, Tag::Copyright, base_offset, entry_size);
base_offset += entry_size;
}
}
if let Some(vd) = &self.viewing_conditions_description {
if vd.has_values() {
let entry_size = write_string_value(&mut entries, vd);
write_tag_entry(
&mut tags,
Tag::ViewingConditionsDescription,
base_offset,
entry_size,
);
base_offset += entry_size;
}
}
if let Some(vd) = &self.device_model {
if vd.has_values() {
let entry_size = write_string_value(&mut entries, vd);
write_tag_entry(&mut tags, Tag::DeviceModel, base_offset, entry_size);
base_offset += entry_size;
}
}
if let Some(vd) = &self.device_manufacturer {
if vd.has_values() {
let entry_size = write_string_value(&mut entries, vd);
write_tag_entry(&mut tags, Tag::DeviceManufacturer, base_offset, entry_size);
// base_offset += entry_size;
}
}
tags.extend(entries);
let profile_header = ProfileHeader {
size: size_of::<ProfileHeader>() as u32 + tags.len() as u32,
pcs: self.pcs,
profile_class: self.profile_class,
rendering_intent: self.rendering_intent,
cmm_type: 0,
version: if has_cicp {
ProfileVersion::V4_3
} else {
ProfileVersion::V4_0
},
data_color_space: self.color_space,
creation_date_time: ColorDateTime::now(),
signature: ProfileSignature::Acsp,
platform: 0u32,
flags: 0u32,
device_manufacturer: 0u32,
device_model: 0u32,
device_attributes: [0u8; 8],
illuminant: self.white_point.to_xyz(),
creator: 0u32,
profile_id: [0u8; 16],
reserved: [0u8; 28],
tag_count: tags_count as u32,
};
let mut header = profile_header.encode();
header.extend(tags);
Ok(header)
}
}
impl FloatToFixedU8Fixed8 for f32 {
#[inline]
fn to_u8_fixed8(self) -> u16 {
if self > 255.0 + 255.0 / 256f32 {
0xffffu16
} else if self < 0.0 {
0u16
} else {
(self * 256.0 + 0.5).floor() as u16
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_u8_fixed8() {
assert_eq!(0, 0f32.to_u8_fixed8());
assert_eq!(0x0100, 1f32.to_u8_fixed8());
assert_eq!(u16::MAX, (255f32 + (255f32 / 256f32)).to_u8_fixed8());
}
#[test]
fn to_s15_fixed16() {
assert_eq!(0x80000000u32 as i32, (-32768f32).to_s15_fixed16());
assert_eq!(0, 0f32.to_s15_fixed16());
assert_eq!(0x10000, 1.0f32.to_s15_fixed16());
assert_eq!(
i32::MAX,
(32767f32 + (65535f32 / 65536f32)).to_s15_fixed16()
);
}
}

View File

@@ -1,98 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 8/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::{Xyz, Xyzd};
/// Holds CIE XyY representation
#[derive(Clone, Debug, Copy, Default)]
pub struct XyY {
pub x: f64,
pub y: f64,
pub yb: f64,
}
pub trait XyYRepresentable {
fn to_xyy(self) -> XyY;
}
impl XyYRepresentable for XyY {
#[inline]
fn to_xyy(self) -> XyY {
self
}
}
impl XyY {
#[inline]
pub const fn new(x: f64, y: f64, yb: f64) -> Self {
Self { x, y, yb }
}
#[inline]
pub const fn to_xyz(self) -> Xyz {
let reciprocal = if self.y != 0. {
1. / self.y * self.yb
} else {
0.
};
Xyz {
x: (self.x * reciprocal) as f32,
y: self.yb as f32,
z: ((1. - self.x - self.y) * reciprocal) as f32,
}
}
#[inline]
pub const fn to_xyzd(self) -> Xyzd {
let reciprocal = if self.y != 0. {
1. / self.y * self.yb
} else {
0.
};
Xyzd {
x: self.x * reciprocal,
y: self.yb,
z: (1. - self.x - self.y) * reciprocal,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xyzd_xyy() {
let xyy = XyY::new(0.2, 0.4, 0.5);
let xyy = xyy.to_xyzd();
let r_xyy = xyy.to_xyzd();
assert!((r_xyy.x - xyy.x).abs() < 1e-5);
assert!((r_xyy.y - xyy.y).abs() < 1e-5);
assert!((r_xyy.z - xyy.z).abs() < 1e-5);
}
}

177
deps/moxcms/src/yrg.rs vendored
View File

@@ -1,177 +0,0 @@
/*
* // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
* //
* // Redistribution and use in source and binary forms, with or without modification,
* // are permitted provided that the following conditions are met:
* //
* // 1. Redistributions of source code must retain the above copyright notice, this
* // list of conditions and the following disclaimer.
* //
* // 2. Redistributions in binary form must reproduce the above copyright notice,
* // this list of conditions and the following disclaimer in the documentation
* // and/or other materials provided with the distribution.
* //
* // 3. Neither the name of the copyright holder nor the names of its
* // contributors may be used to endorse or promote products derived from
* // this software without specific prior written permission.
* //
* // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::mlaf::mlaf;
use crate::{Matrix3f, Vector3f, Xyz};
use pxfm::{f_atan2f, f_hypotf, f_sincosf};
/// Structure for Yrg colorspace
///
/// Kirk Yrg 2021.
#[repr(C)]
#[derive(Default, Debug, PartialOrd, PartialEq, Copy, Clone)]
pub struct Yrg {
pub y: f32,
pub r: f32,
pub g: f32,
}
/// Structure for cone form of Yrg colorspace
#[repr(C)]
#[derive(Default, Debug, PartialOrd, PartialEq, Copy, Clone)]
pub struct Ych {
pub y: f32,
pub c: f32,
pub h: f32,
}
const LMS_TO_XYZ: Matrix3f = Matrix3f {
v: [
[1.8079466, -1.2997167, 0.34785876],
[0.61783963, 0.39595452, -0.041046873],
[-0.12546961, 0.20478038, 1.7427418],
],
};
const XYZ_TO_LMS: Matrix3f = Matrix3f {
v: [
[0.257085, 0.859943, -0.031061],
[-0.394427, 1.175800, 0.106423],
[0.064856, -0.076250, 0.559067],
],
};
impl Yrg {
#[inline]
pub const fn new(y: f32, r: f32, g: f32) -> Yrg {
Yrg { y, r, g }
}
/// Convert [Xyz] D65 to [Yrg]
///
/// Yrg defined in D65 white point. Ensure Xyz values is adapted.
/// Yrg use CIE XYZ 2006, adapt CIE XYZ 1931 by using [cie_y_1931_to_cie_y_2006] at first.
#[inline]
pub fn from_xyz(xyz: Xyz) -> Self {
let lms = XYZ_TO_LMS.f_mul_vector(Vector3f {
v: [xyz.x, xyz.y, xyz.z],
});
let y = mlaf(0.68990272 * lms.v[0], 0.34832189, lms.v[1]);
let a = lms.v[0] + lms.v[1] + lms.v[2];
let l = if a == 0. { 0. } else { lms.v[0] / a };
let m = if a == 0. { 0. } else { lms.v[1] / a };
let r = mlaf(mlaf(0.02062, -0.6873, m), 1.0671, l);
let g = mlaf(mlaf(-0.05155, -0.0362, l), 1.7182, m);
Yrg { y, r, g }
}
#[inline]
pub fn to_xyz(&self) -> Xyz {
let l = mlaf(0.95 * self.r, 0.38, self.g);
let m = mlaf(mlaf(0.03, 0.59, self.g), 0.02, self.r);
let den = mlaf(0.68990272 * l, 0.34832189, m);
let a = if den == 0. { 0. } else { self.y / den };
let l0 = l * a;
let m0 = m * a;
let s0 = (1f32 - l - m) * a;
let v = Vector3f { v: [l0, m0, s0] };
let x = LMS_TO_XYZ.f_mul_vector(v);
Xyz {
x: x.v[0],
y: x.v[1],
z: x.v[2],
}
}
}
impl Ych {
#[inline]
pub const fn new(y: f32, c: f32, h: f32) -> Self {
Ych { y, c, h }
}
#[inline]
pub fn from_yrg(yrg: Yrg) -> Self {
let y = yrg.y;
// Subtract white point. These are the r, g coordinates of
// sRGB (D50 adapted) (1, 1, 1) taken through
// XYZ D50 -> CAT16 D50->D65 adaptation -> LMS 2006
// -> grading RGB conversion.
let r = yrg.r - 0.21902143;
let g = yrg.g - 0.54371398;
let c = f_hypotf(g, r);
let h = f_atan2f(g, r);
Self { y, c, h }
}
#[inline]
pub fn to_yrg(&self) -> Yrg {
let y = self.y;
let c = self.c;
let h = self.h;
let sincos = f_sincosf(h);
let r = mlaf(0.21902143, c, sincos.1);
let g = mlaf(0.54371398, c, sincos.0);
Yrg { y, r, g }
}
}
// Pipeline and ICC luminance is CIE Y 1931
// Kirk Ych/Yrg uses CIE Y 2006
// 1 CIE Y 1931 = 1.05785528 CIE Y 2006, so we need to adjust that.
// This also accounts for the CAT16 D50->D65 adaptation that has to be done
// to go from RGB to CIE LMS 2006.
// Warning: only applies to achromatic pixels.
pub const fn cie_y_1931_to_cie_y_2006(x: f32) -> f32 {
1.05785528 * (x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_yrg() {
let xyz = Xyz::new(0.95, 1.0, 1.08);
let yrg = Yrg::from_xyz(xyz);
let yrg_to_xyz = yrg.to_xyz();
assert!((xyz.x - yrg_to_xyz.x) < 1e-5);
assert!((xyz.y - yrg_to_xyz.y) < 1e-5);
assert!((xyz.z - yrg_to_xyz.z) < 1e-5);
}
#[test]
fn test_ych() {
let xyz = Yrg::new(0.5, 0.4, 0.3);
let yrg = Ych::from_yrg(xyz);
let yrg_to_xyz = yrg.to_yrg();
assert!((xyz.y - yrg_to_xyz.y) < 1e-5);
assert!((xyz.r - yrg_to_xyz.r) < 1e-5);
assert!((xyz.g - yrg_to_xyz.g) < 1e-5);
}
}

View File

@@ -1,12 +0,0 @@
# Changelog
## 0.3.3 - 2021-04-14
### Features
- **from_checksum**: add `Adler32::from_checksum`
### Performance Improvements
- **scalar**: improve scalar performance by 90-600%
- Defer modulo until right before u16 overflow

View File

@@ -1,39 +0,0 @@
[package]
name = "simd-adler32"
authors = ["Marvin Countryman <me@maar.vin>"]
license = "MIT"
version = "0.3.7"
edition = "2018"
keywords = ["simd", "avx2", "ssse3", "adler", "adler32"]
categories = ["algorithms", "no-std"]
repository = "https://github.com/mcountryman/simd-adler32"
description = "A SIMD-accelerated Adler-32 hash algorithm implementation."
exclude = ["bench"]
[profile.release]
debug = true
opt-level = 2
[[bench]]
name = "alts"
path = "bench/alts.rs"
harness = false
[[bench]]
name = "variants"
path = "bench/variants.rs"
harness = false
[features]
default = ["std", "const-generics"]
std = []
nightly = []
const-generics = []
[dev-dependencies]
rand = "0.8"
criterion = "0.3"
# competition
adler = "1.0.2"
adler32 = "1.2.0"

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) [2021] [Marvin Countryman]
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.

View File

@@ -1,131 +0,0 @@
<h1 align="center">simd-adler32</h1>
<p align="center">
<a href="https://docs.rs/simd-adler32">
<img alt="docs.rs badge" src="https://img.shields.io/docsrs/simd-adler32?style=flat-square">
</a>
<a href="https://crates.io/crates/simd-adler32">
<img alt="crates.io badge" src="https://img.shields.io/crates/v/simd-adler32?style=flat-square">
</a>
<a href="https://github.com/mcountryman/simd-adler32/blob/main/LICENSE.md">
<img alt="mit license badge" src="https://img.shields.io/github/license/mcountryman/simd-adler32?style=flat-square">
</a>
</p>
A SIMD-accelerated Adler-32 hash algorithm implementation.
## Features
- No dependencies
- Support `no_std` (with `default-features = false`)
- Runtime CPU feature detection (when `std` enabled)
- Blazing fast performance on as many targets as possible (currently only x86 and x86_64)
- Default to scalar implementation when simd not available
## Quick start
> Cargo.toml
```toml
[dependencies]
simd-adler32 = "*"
```
> example.rs
```rust
use simd_adler32::Adler32;
let mut adler = Adler32::new();
adler.write(b"rust is pretty cool, man");
let hash = adler.finish();
println!("{}", hash);
// 1921255656
```
## Support
**CPU Features**
| impl | arch | feature |
| ---- | ---------------- | ------- |
| ✅ | `x86`, `x86_64` | avx512 |
| ✅ | `x86`, `x86_64` | avx2 |
| ✅ | `x86`, `x86_64` | ssse3 |
| ✅ | `x86`, `x86_64` | sse2 |
| 🚧 | `arm`, `aarch64` | neon |
| ✅ | `wasm32` | simd128 |
**MSRV** `1.36.0`\*\*
Minimum supported rust version is tested before a new version is published. [**] Feature
`const-generics` needs to disabled to build on rustc versions `<1.51` which can be done
by updating your dependency definition to the following.
> Cargo.toml
```toml
[dependencies]
simd-adler32 = { version "*", default-features = false, features = ["std"] }
```
## Performance
Benchmarks listed display number of randomly generated bytes (10k / 100k) and library
name. Benchmarks sources can be found under the [bench](/bench) directory. Crates used for
comparison are [adler](https://crates.io/crates/adler) and
[adler32](https://crates.io/crates/adler32).
> Windows 10 Pro - Intel i5-8300H @ 2.30GHz
| name | avg. time | avg. thrpt |
| ----------------------- | --------------- | ------------------ |
| **10k/simd-adler32** | **212.61 ns** | **43.805 GiB/s** |
| 10k/wuffs | 3843 ns | 2.63 GiB/s\* |
| 10k/adler32 | 4.8084 us | 1.9369 GiB/s |
| 10k/adler | 17.979 us | 530.43 MiB/s |
| ----------------------- | --------------- | ------------------ |
| **100k/simd-adler32** | **2.7951 us** | **33.320 GiB/s** |
| 100k/wuffs | 34733 ns | 2.6814 GiB/s\* |
| 100k/adler32 | 48.488 us | 1.9207 GiB/s |
| 100k/adler | 178.36 us | 534.69 MiB/s |
\* wuffs ran using mingw64/gcc, ran with `wuffs bench -ccompilers=gcc -reps=1 -iterscale=300 std/adler32`.
> MacBookPro16,1 - Intel i9-9880H CPU @ 2.30GHz
| name | avg. time | avg. thrpt |
| ----------------------- | --------------- | ------------------ |
| **10k/simd-adler32** | **200.37 ns** | **46.480 GiB/s** |
| 10k/adler32 | 4.1516 us | 2.2433 GiB/s |
| 10k/adler | 10.220 us | 933.15 MiB/s |
| ----------------------- | --------------- | ------------------ |
| **100k/simd-adler32** | **2.3282 us** | **40.003 GiB/s** |
| 100k/adler32 | 41.130 us | 2.2643 GiB/s |
| 100k/adler | 83.776 us | 534.69 MiB/s |
## Safety
This crate contains a significant amount of `unsafe` code due to the requirement of `unsafe`
for simd intrinsics. Fuzzing is done on release and debug builds prior to publishing via
`afl`. Fuzzy tests can be found under [fuzz](/fuzz) the directory.
## Resources
- [LICENSE](./LICENSE.md) - MIT
- [CHANGELOG](./CHANGELOG.md)
## Credits
Thank you to the contributors of the following projects.
- [adler](https://github.com/jonas-schievink/adler)
- [adler32](https://github.com/remram44/adler32-rs)
- [crc32fast](https://github.com/srijs/rust-crc32fast)
- [wuffs](https://github.com/google/wuffs)
- [chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=762564)
- [zlib](https://zlib.net/)
## Contributing
Feel free to submit a issue or pull request. :smile:

View File

@@ -1,156 +0,0 @@
use crate::{Adler32, Adler32Hash};
impl Adler32Hash for &[u8] {
fn hash(&self) -> u32 {
let mut hash = Adler32::new();
hash.write(self);
hash.finish()
}
}
impl Adler32Hash for &str {
fn hash(&self) -> u32 {
let mut hash = Adler32::new();
hash.write(self.as_bytes());
hash.finish()
}
}
#[cfg(feature = "const-generics")]
impl<const SIZE: usize> Adler32Hash for [u8; SIZE] {
fn hash(&self) -> u32 {
let mut hash = Adler32::new();
hash.write(self);
hash.finish()
}
}
macro_rules! array_impl {
($s:expr, $($size:expr),+) => {
array_impl!($s);
$(array_impl!{$size})*
};
($size:expr) => {
#[cfg(not(feature = "const-generics"))]
impl Adler32Hash for [u8; $size] {
fn hash(&self) -> u32 {
let mut hash = Adler32::new();
hash.write(self);
hash.finish()
}
}
};
}
array_impl!(
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100,
1024,
1024 * 1024,
1024 * 1024 * 1024,
2048,
4096
);

View File

@@ -1,13 +0,0 @@
pub mod scalar;
pub type Adler32Imp = fn(u16, u16, &[u8]) -> (u16, u16);
#[inline]
#[allow(non_snake_case)]
pub const fn _MM_SHUFFLE(z: u32, y: u32, x: u32, w: u32) -> i32 {
((z << 6) | (y << 4) | (x << 2) | w) as i32
}
pub fn get_imp() -> Adler32Imp {
scalar::update
}

View File

@@ -1,69 +0,0 @@
const MOD: u32 = 65521;
const NMAX: usize = 5552;
pub fn update(a: u16, b: u16, data: &[u8]) -> (u16, u16) {
let mut a = a as u32;
let mut b = b as u32;
let chunks = data.chunks_exact(NMAX);
let remainder = chunks.remainder();
for chunk in chunks {
for byte in chunk {
a = a.wrapping_add(*byte as _);
b = b.wrapping_add(a);
}
a %= MOD;
b %= MOD;
}
for byte in remainder {
a = a.wrapping_add(*byte as _);
b = b.wrapping_add(a);
}
a %= MOD;
b %= MOD;
(a as u16, b as u16)
}
#[cfg(test)]
mod tests {
#[test]
fn zeroes() {
assert_eq!(adler32(&[]), 1);
assert_eq!(adler32(&[0]), 1 | 1 << 16);
assert_eq!(adler32(&[0, 0]), 1 | 2 << 16);
assert_eq!(adler32(&[0; 100]), 0x00640001);
assert_eq!(adler32(&[0; 1024]), 0x04000001);
assert_eq!(adler32(&[0; 1024 * 1024]), 0x00f00001);
}
#[test]
fn ones() {
assert_eq!(adler32(&[0xff; 1024]), 0x79a6fc2e);
assert_eq!(adler32(&[0xff; 1024 * 1024]), 0x8e88ef11);
}
#[test]
fn mixed() {
assert_eq!(adler32(&[1]), 2 | 2 << 16);
assert_eq!(adler32(&[40]), 41 | 41 << 16);
assert_eq!(adler32(&[0xA5; 1024 * 1024]), 0xd5009ab1);
}
/// Example calculation from https://en.wikipedia.org/wiki/Adler-32.
#[test]
fn wiki() {
assert_eq!(adler32(b"Wikipedia"), 0x11E60398);
}
fn adler32(data: &[u8]) -> u32 {
let (a, b) = super::update(1, 0, data);
u32::from(b) << 16 | u32::from(a)
}
}

View File

@@ -1,309 +0,0 @@
//! # simd-adler32
//!
//! A SIMD-accelerated Adler-32 hash algorithm implementation.
//!
//! ## Features
//!
//! - No dependencies
//! - Support `no_std` (with `default-features = false`)
//! - Runtime CPU feature detection (when `std` enabled)
//! - Blazing fast performance on as many targets as possible (currently only x86 and x86_64)
//! - Default to scalar implementation when simd not available
//!
//! ## Quick start
//!
//! > Cargo.toml
//!
//! ```toml
//! [dependencies]
//! simd-adler32 = "*"
//! ```
//!
//! > example.rs
//!
//! ```rust
//! use simd_adler32::Adler32;
//!
//! let mut adler = Adler32::new();
//! adler.write(b"rust is pretty cool, man");
//! let hash = adler.finish();
//!
//! println!("{}", hash);
//! // 1921255656
//! ```
//!
//! ## Feature flags
//!
//! * `std` - Enabled by default
//!
//! Enables std support, see [CPU Feature Detection](#cpu-feature-detection) for runtime
//! detection support.
//! * `nightly`
//!
//! Enables nightly features required for avx512 support.
//!
//! * `const-generics` - Enabled by default
//!
//! Enables const-generics support allowing for user-defined array hashing by value. See
//! [`Adler32Hash`] for details.
//!
//! ## Support
//!
//! **CPU Features**
//!
//! | impl | arch | feature |
//! | ---- | ---------------- | ------- |
//! | ✅ | `x86`, `x86_64` | avx512 |
//! | ✅ | `x86`, `x86_64` | avx2 |
//! | ✅ | `x86`, `x86_64` | ssse3 |
//! | ✅ | `x86`, `x86_64` | sse2 |
//! | 🚧 | `arm`, `aarch64` | neon |
//! | | `wasm32` | simd128 |
//!
//! **MSRV** `1.36.0`\*\*
//!
//! Minimum supported rust version is tested before a new version is published. [**] Feature
//! `const-generics` needs to disabled to build on rustc versions `<1.51` which can be done
//! by updating your dependency definition to the following.
//!
//! ## CPU Feature Detection
//! simd-adler32 supports both runtime and compile time CPU feature detection using the
//! `std::is_x86_feature_detected` macro when the `Adler32` struct is instantiated with
//! the `new` fn.
//!
//! Without `std` feature enabled simd-adler32 falls back to compile time feature detection
//! using `target-feature` or `target-cpu` flags supplied to rustc. See [https://rust-lang.github.io/packed_simd/perf-guide/target-feature/rustflags.html](https://rust-lang.github.io/packed_simd/perf-guide/target-feature/rustflags.html)
//! for more information.
//!
//! Feature detection tries to use the fastest supported feature first.
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nightly", feature(stdsimd, avx512_target_feature))]
#[doc(hidden)]
pub mod hash;
#[doc(hidden)]
pub mod imp;
use imp::{get_imp, Adler32Imp};
/// An adler32 hash generator type.
#[derive(Clone)]
pub struct Adler32 {
a: u16,
b: u16,
update: Adler32Imp,
}
impl Adler32 {
/// Constructs a new `Adler32`.
///
/// Potential overhead here due to runtime feature detection although in testing on 100k
/// and 10k random byte arrays it was not really noticeable.
///
/// # Examples
/// ```rust
/// use simd_adler32::Adler32;
///
/// let mut adler = Adler32::new();
/// ```
pub fn new() -> Self {
Default::default()
}
/// Constructs a new `Adler32` using existing checksum.
///
/// Potential overhead here due to runtime feature detection although in testing on 100k
/// and 10k random byte arrays it was not really noticeable.
///
/// # Examples
/// ```rust
/// use simd_adler32::Adler32;
///
/// let mut adler = Adler32::from_checksum(0xdeadbeaf);
/// ```
pub fn from_checksum(checksum: u32) -> Self {
Self {
a: checksum as u16,
b: (checksum >> 16) as u16,
update: get_imp(),
}
}
/// Computes hash for supplied data and stores results in internal state.
pub fn write(&mut self, data: &[u8]) {
let (a, b) = (self.update)(self.a, self.b, data);
self.a = a;
self.b = b;
}
/// Returns the hash value for the values written so far.
///
/// Despite its name, the method does not reset the hashers internal state. Additional
/// writes will continue from the current value. If you need to start a fresh hash
/// value, you will have to use `reset`.
pub fn finish(&self) -> u32 {
(u32::from(self.b) << 16) | u32::from(self.a)
}
/// Resets the internal state.
pub fn reset(&mut self) {
self.a = 1;
self.b = 0;
}
}
/// Compute Adler-32 hash on `Adler32Hash` type.
///
/// # Arguments
/// * `hash` - A Adler-32 hash-able type.
///
/// # Examples
/// ```rust
/// use simd_adler32::adler32;
///
/// let hash = adler32(b"Adler-32");
/// println!("{}", hash); // 800813569
/// ```
pub fn adler32<H: Adler32Hash>(hash: &H) -> u32 {
hash.hash()
}
/// A Adler-32 hash-able type.
pub trait Adler32Hash {
/// Feeds this value into `Adler32`.
fn hash(&self) -> u32;
}
impl Default for Adler32 {
fn default() -> Self {
Self {
a: 1,
b: 0,
update: get_imp(),
}
}
}
#[cfg(feature = "std")]
pub mod read {
//! Reader-based hashing.
//!
//! # Example
//! ```rust
//! use std::io::Cursor;
//! use simd_adler32::read::adler32;
//!
//! let mut reader = Cursor::new(b"Hello there");
//! let hash = adler32(&mut reader).unwrap();
//!
//! println!("{}", hash) // 800813569
//! ```
use crate::Adler32;
use std::io::{Read, Result};
/// Compute Adler-32 hash on reader until EOF.
///
/// # Example
/// ```rust
/// use std::io::Cursor;
/// use simd_adler32::read::adler32;
///
/// let mut reader = Cursor::new(b"Hello there");
/// let hash = adler32(&mut reader).unwrap();
///
/// println!("{}", hash) // 800813569
/// ```
pub fn adler32<R: Read>(reader: &mut R) -> Result<u32> {
let mut hash = Adler32::new();
let mut buf = [0; 4096];
loop {
match reader.read(&mut buf) {
Ok(0) => return Ok(hash.finish()),
Ok(n) => {
hash.write(&buf[..n]);
}
Err(err) => return Err(err),
}
}
}
}
#[cfg(feature = "std")]
pub mod bufread {
//! BufRead-based hashing.
//!
//! Separate `BufRead` trait implemented to allow for custom buffer size optimization.
//!
//! # Example
//! ```rust
//! use std::io::{Cursor, BufReader};
//! use simd_adler32::bufread::adler32;
//!
//! let mut reader = Cursor::new(b"Hello there");
//! let mut reader = BufReader::new(reader);
//! let hash = adler32(&mut reader).unwrap();
//!
//! println!("{}", hash) // 800813569
//! ```
use crate::Adler32;
use std::io::{BufRead, ErrorKind, Result};
/// Compute Adler-32 hash on buf reader until EOF.
///
/// # Example
/// ```rust
/// use std::io::{Cursor, BufReader};
/// use simd_adler32::bufread::adler32;
///
/// let mut reader = Cursor::new(b"Hello there");
/// let mut reader = BufReader::new(reader);
/// let hash = adler32(&mut reader).unwrap();
///
/// println!("{}", hash) // 800813569
/// ```
pub fn adler32<R: BufRead>(reader: &mut R) -> Result<u32> {
let mut hash = Adler32::new();
loop {
let consumed = match reader.fill_buf() {
Ok(buf) => {
if buf.is_empty() {
return Ok(hash.finish());
}
hash.write(buf);
buf.len()
}
Err(err) => match err.kind() {
ErrorKind::Interrupted => continue,
ErrorKind::UnexpectedEof => return Ok(hash.finish()),
_ => return Err(err),
},
};
reader.consume(consumed);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_from_checksum() {
let buf = b"rust is pretty cool man";
let sum = 0xdeadbeaf;
let mut simd = super::Adler32::from_checksum(sum);
let mut adler = adler::Adler32::from_checksum(sum);
simd.write(buf);
adler.write_slice(buf);
let simd = simd.finish();
let scalar = adler.checksum();
assert_eq!(simd, scalar);
}
}