mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 17:10:17 +00:00
move patched deps to separate repository
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
13
deps/README.md
vendored
@@ -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.
|
||||
9
deps/moxcms/.gitignore
vendored
9
deps/moxcms/.gitignore
vendored
@@ -1,9 +0,0 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
.idea
|
||||
app/target
|
||||
flamegraph.svg
|
||||
perf.data
|
||||
profile.json.gz
|
||||
.cargo
|
||||
rust-toolchain.toml
|
||||
49
deps/moxcms/Cargo.toml
vendored
49
deps/moxcms/Cargo.toml
vendored
@@ -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
|
||||
201
deps/moxcms/LICENSE-APACHE.md
vendored
201
deps/moxcms/LICENSE-APACHE.md
vendored
@@ -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.
|
||||
26
deps/moxcms/LICENSE.md
vendored
26
deps/moxcms/LICENSE.md
vendored
@@ -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
74
deps/moxcms/README.md
vendored
@@ -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.
|
||||
172
deps/moxcms/src/chad.rs
vendored
172
deps/moxcms/src/chad.rs
vendored
@@ -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)
|
||||
}
|
||||
143
deps/moxcms/src/chromaticity.rs
vendored
143
deps/moxcms/src/chromaticity.rs
vendored
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
642
deps/moxcms/src/cicp.rs
vendored
642
deps/moxcms/src/cicp.rs
vendored
@@ -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, 13–21, 23–255 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, 13–21, 23–255 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, 19–255 are all reserved so all map to the same variant
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TransferCharacteristics {
|
||||
/// For future use by ITU-T | ISO/IEC
|
||||
Reserved,
|
||||
/// Rec. ITU-R BT.709-6<br />
|
||||
/// Rec. ITU-R BT.1361-0 conventional colour gamut system (historical)<br />
|
||||
/// (functionally the same as the values 6, 14 and 15) <br />
|
||||
Bt709 = 1,
|
||||
/// Image characteristics are unknown or are determined by the application.<br />
|
||||
Unspecified = 2,
|
||||
/// Rec. ITU-R BT.470-6 System M (historical)<br />
|
||||
/// United States National Television System Committee 1953 Recommendation for transmission standards for color television<br />
|
||||
/// United States Federal Communications Commission (2003) Title 47 Code of Federal Regulations 73.682 (a) (20)<br />
|
||||
/// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM<br />
|
||||
Bt470M = 4,
|
||||
/// Rec. ITU-R BT.470-6 System B, G (historical)<br />
|
||||
Bt470Bg = 5,
|
||||
/// Rec. ITU-R BT.601-7 525 or 625<br />
|
||||
/// Rec. ITU-R BT.1358-1 525 or 625 (historical)<br />
|
||||
/// Rec. ITU-R BT.1700-0 NTSC SMPTE 170M (2004)<br />
|
||||
/// (functionally the same as the values 1, 14 and 15)<br />
|
||||
Bt601 = 6,
|
||||
/// SMPTE 240M (1999) (historical)<br />
|
||||
Smpte240 = 7,
|
||||
/// Linear transfer characteristics<br />
|
||||
Linear = 8,
|
||||
/// Logarithmic transfer characteristic (100:1 range)<br />
|
||||
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);
|
||||
}
|
||||
}
|
||||
121
deps/moxcms/src/conversions/bpc.rs
vendored
121
deps/moxcms/src/conversions/bpc.rs
vendored
@@ -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);
|
||||
// }
|
||||
// }
|
||||
388
deps/moxcms/src/conversions/gray2rgb.rs
vendored
388
deps/moxcms/src/conversions/gray2rgb.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
383
deps/moxcms/src/conversions/gray2rgb_extended.rs
vendored
383
deps/moxcms/src/conversions/gray2rgb_extended.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
599
deps/moxcms/src/conversions/interpolator.rs
vendored
599
deps/moxcms/src/conversions/interpolator.rs
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
118
deps/moxcms/src/conversions/katana/finalizers.rs
vendored
118
deps/moxcms/src/conversions/katana/finalizers.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
483
deps/moxcms/src/conversions/katana/md3x3.rs
vendored
483
deps/moxcms/src/conversions/katana/md3x3.rs
vendored
@@ -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))
|
||||
}
|
||||
321
deps/moxcms/src/conversions/katana/md4x3.rs
vendored
321
deps/moxcms/src/conversions/katana/md4x3.rs
vendored
@@ -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))
|
||||
}
|
||||
284
deps/moxcms/src/conversions/katana/md_3xn.rs
vendored
284
deps/moxcms/src/conversions/katana/md_3xn.rs
vendored
@@ -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))
|
||||
}
|
||||
294
deps/moxcms/src/conversions/katana/md_nx3.rs
vendored
294
deps/moxcms/src/conversions/katana/md_nx3.rs
vendored
@@ -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))
|
||||
}
|
||||
393
deps/moxcms/src/conversions/katana/md_pipeline.rs
vendored
393
deps/moxcms/src/conversions/katana/md_pipeline.rs
vendored
@@ -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))
|
||||
}
|
||||
56
deps/moxcms/src/conversions/katana/mod.rs
vendored
56
deps/moxcms/src/conversions/katana/mod.rs
vendored
@@ -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;
|
||||
100
deps/moxcms/src/conversions/katana/pcs_stages.rs
vendored
100
deps/moxcms/src/conversions/katana/pcs_stages.rs
vendored
@@ -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,
|
||||
})
|
||||
}
|
||||
162
deps/moxcms/src/conversions/katana/rgb_xyz.rs
vendored
162
deps/moxcms/src/conversions/katana/rgb_xyz.rs
vendored
@@ -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,
|
||||
})
|
||||
}
|
||||
85
deps/moxcms/src/conversions/katana/stages.rs
vendored
85
deps/moxcms/src/conversions/katana/stages.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
62
deps/moxcms/src/conversions/katana/xyz_lab.rs
vendored
62
deps/moxcms/src/conversions/katana/xyz_lab.rs
vendored
@@ -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))
|
||||
}
|
||||
}
|
||||
223
deps/moxcms/src/conversions/katana/xyz_rgb.rs
vendored
223
deps/moxcms/src/conversions/katana/xyz_rgb.rs
vendored
@@ -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
|
||||
),
|
||||
}
|
||||
}
|
||||
428
deps/moxcms/src/conversions/lut3x3.rs
vendored
428
deps/moxcms/src/conversions/lut3x3.rs
vendored
@@ -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)
|
||||
}
|
||||
249
deps/moxcms/src/conversions/lut3x4.rs
vendored
249
deps/moxcms/src/conversions/lut3x4.rs
vendored
@@ -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)
|
||||
}
|
||||
360
deps/moxcms/src/conversions/lut4.rs
vendored
360
deps/moxcms/src/conversions/lut4.rs
vendored
@@ -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)
|
||||
}
|
||||
802
deps/moxcms/src/conversions/lut_transforms.rs
vendored
802
deps/moxcms/src/conversions/lut_transforms.rs
vendored
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
559
deps/moxcms/src/conversions/mab.rs
vendored
559
deps/moxcms/src/conversions/mab.rs
vendored
@@ -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(())
|
||||
}
|
||||
307
deps/moxcms/src/conversions/mab4x3.rs
vendored
307
deps/moxcms/src/conversions/mab4x3.rs
vendored
@@ -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)
|
||||
}
|
||||
302
deps/moxcms/src/conversions/mba3x4.rs
vendored
302
deps/moxcms/src/conversions/mba3x4.rs
vendored
@@ -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)
|
||||
}
|
||||
728
deps/moxcms/src/conversions/md_lut.rs
vendored
728
deps/moxcms/src/conversions/md_lut.rs
vendored
@@ -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!(),
|
||||
}
|
||||
}
|
||||
188
deps/moxcms/src/conversions/md_luts_factory.rs
vendored
188
deps/moxcms/src/conversions/md_luts_factory.rs
vendored
@@ -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
|
||||
}
|
||||
66
deps/moxcms/src/conversions/mod.rs
vendored
66
deps/moxcms/src/conversions/mod.rs
vendored
@@ -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,
|
||||
};
|
||||
328
deps/moxcms/src/conversions/prelude_lut_xyz_rgb.rs
vendored
328
deps/moxcms/src/conversions/prelude_lut_xyz_rgb.rs
vendored
@@ -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(())
|
||||
}
|
||||
189
deps/moxcms/src/conversions/rgb2gray.rs
vendored
189
deps/moxcms/src/conversions/rgb2gray.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
181
deps/moxcms/src/conversions/rgb2gray_extended.rs
vendored
181
deps/moxcms/src/conversions/rgb2gray_extended.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
395
deps/moxcms/src/conversions/rgb_xyz_factory.rs
vendored
395
deps/moxcms/src/conversions/rgb_xyz_factory.rs
vendored
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
899
deps/moxcms/src/conversions/rgbxyz.rs
vendored
899
deps/moxcms/src/conversions/rgbxyz.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
560
deps/moxcms/src/conversions/rgbxyz_fixed.rs
vendored
560
deps/moxcms/src/conversions/rgbxyz_fixed.rs
vendored
@@ -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
|
||||
);
|
||||
330
deps/moxcms/src/conversions/rgbxyz_float.rs
vendored
330
deps/moxcms/src/conversions/rgbxyz_float.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
266
deps/moxcms/src/conversions/transform_lut3_to_3.rs
vendored
266
deps/moxcms/src/conversions/transform_lut3_to_3.rs
vendored
@@ -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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
274
deps/moxcms/src/conversions/transform_lut3_to_4.rs
vendored
274
deps/moxcms/src/conversions/transform_lut3_to_4.rs
vendored
@@ -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!(),
|
||||
}
|
||||
}
|
||||
351
deps/moxcms/src/conversions/transform_lut4_to_3.rs
vendored
351
deps/moxcms/src/conversions/transform_lut4_to_3.rs
vendored
@@ -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,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
deps/moxcms/src/conversions/xyz_lab.rs
vendored
61
deps/moxcms/src/conversions/xyz_lab.rs
vendored
@@ -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
154
deps/moxcms/src/dat.rs
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
541
deps/moxcms/src/defaults.rs
vendored
541
deps/moxcms/src/defaults.rs
vendored
@@ -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 –aln(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
|
||||
}
|
||||
}
|
||||
359
deps/moxcms/src/dt_ucs.rs
vendored
359
deps/moxcms/src/dt_ucs.rs
vendored
@@ -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
141
deps/moxcms/src/err.rs
vendored
@@ -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
1078
deps/moxcms/src/gamma.rs
vendored
File diff suppressed because it is too large
Load Diff
66
deps/moxcms/src/gamut.rs
vendored
66
deps/moxcms/src/gamut.rs
vendored
@@ -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
|
||||
}
|
||||
223
deps/moxcms/src/helpers.rs
vendored
223
deps/moxcms/src/helpers.rs
vendored
@@ -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),
|
||||
],
|
||||
})
|
||||
}
|
||||
192
deps/moxcms/src/ictcp.rs
vendored
192
deps/moxcms/src/ictcp.rs
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
434
deps/moxcms/src/jzazbz.rs
vendored
434
deps/moxcms/src/jzazbz.rs
vendored
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
375
deps/moxcms/src/jzczhz.rs
vendored
375
deps/moxcms/src/jzczhz.rs
vendored
@@ -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
242
deps/moxcms/src/lab.rs
vendored
@@ -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
133
deps/moxcms/src/lib.rs
vendored
@@ -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};
|
||||
106
deps/moxcms/src/lut_hint.rs
vendored
106
deps/moxcms/src/lut_hint.rs
vendored
@@ -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
698
deps/moxcms/src/luv.rs
vendored
@@ -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 0–100 range.
|
||||
pub l: f32,
|
||||
/// The u\* value of the colour.
|
||||
///
|
||||
/// Together with v\* value, it defines chromaticity of the colour. The u\*
|
||||
/// coordinate represents colour’s position on red-green axis with negative
|
||||
/// values indicating more red and positive more green colour. Typical
|
||||
/// values are in -134–220 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 colour’s position on blue-yellow axis with
|
||||
/// negative values indicating more blue and positive more yellow colour.
|
||||
/// Typical values are in -140–122 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 0–100 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);
|
||||
}
|
||||
}
|
||||
72
deps/moxcms/src/matan/curve_shape.rs
vendored
72
deps/moxcms/src/matan/curve_shape.rs
vendored
@@ -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
|
||||
}
|
||||
60
deps/moxcms/src/matan/degeneration.rs
vendored
60
deps/moxcms/src/matan/degeneration.rs
vendored
@@ -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)
|
||||
}
|
||||
74
deps/moxcms/src/matan/discontinuity.rs
vendored
74
deps/moxcms/src/matan/discontinuity.rs
vendored
@@ -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
|
||||
}
|
||||
40
deps/moxcms/src/matan/mod.rs
vendored
40
deps/moxcms/src/matan/mod.rs
vendored
@@ -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;
|
||||
52
deps/moxcms/src/matan/monotonic.rs
vendored
52
deps/moxcms/src/matan/monotonic.rs
vendored
@@ -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
|
||||
}
|
||||
84
deps/moxcms/src/matan/slope_limit.rs
vendored
84
deps/moxcms/src/matan/slope_limit.rs
vendored
@@ -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_();
|
||||
}
|
||||
}
|
||||
}
|
||||
68
deps/moxcms/src/math/mod.rs
vendored
68
deps/moxcms/src/math/mod.rs
vendored
@@ -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;
|
||||
}
|
||||
1272
deps/moxcms/src/matrix.rs
vendored
1272
deps/moxcms/src/matrix.rs
vendored
File diff suppressed because it is too large
Load Diff
82
deps/moxcms/src/mlaf.rs
vendored
82
deps/moxcms/src/mlaf.rs
vendored
@@ -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)
|
||||
}
|
||||
1239
deps/moxcms/src/nd_array.rs
vendored
1239
deps/moxcms/src/nd_array.rs
vendored
File diff suppressed because it is too large
Load Diff
354
deps/moxcms/src/oklab.rs
vendored
354
deps/moxcms/src/oklab.rs
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
294
deps/moxcms/src/oklch.rs
vendored
294
deps/moxcms/src/oklch.rs
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
1365
deps/moxcms/src/profile.rs
vendored
1365
deps/moxcms/src/profile.rs
vendored
File diff suppressed because it is too large
Load Diff
955
deps/moxcms/src/reader.rs
vendored
955
deps/moxcms/src/reader.rs
vendored
@@ -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
723
deps/moxcms/src/rgb.rs
vendored
@@ -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())
|
||||
}
|
||||
}
|
||||
103
deps/moxcms/src/safe_math.rs
vendored
103
deps/moxcms/src/safe_math.rs
vendored
@@ -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);
|
||||
102
deps/moxcms/src/srlab2.rs
vendored
102
deps/moxcms/src/srlab2.rs
vendored
@@ -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
271
deps/moxcms/src/tag.rs
vendored
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
1334
deps/moxcms/src/transform.rs
vendored
1334
deps/moxcms/src/transform.rs
vendored
File diff suppressed because it is too large
Load Diff
1583
deps/moxcms/src/trc.rs
vendored
1583
deps/moxcms/src/trc.rs
vendored
File diff suppressed because it is too large
Load Diff
889
deps/moxcms/src/writer.rs
vendored
889
deps/moxcms/src/writer.rs
vendored
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
98
deps/moxcms/src/xyy.rs
vendored
98
deps/moxcms/src/xyy.rs
vendored
@@ -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
177
deps/moxcms/src/yrg.rs
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
12
deps/simd-adler32/CHANGELOG.md
vendored
12
deps/simd-adler32/CHANGELOG.md
vendored
@@ -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
|
||||
39
deps/simd-adler32/Cargo.toml
vendored
39
deps/simd-adler32/Cargo.toml
vendored
@@ -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"
|
||||
21
deps/simd-adler32/LICENSE.md
vendored
21
deps/simd-adler32/LICENSE.md
vendored
@@ -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.
|
||||
131
deps/simd-adler32/README.md
vendored
131
deps/simd-adler32/README.md
vendored
@@ -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:
|
||||
156
deps/simd-adler32/src/hash.rs
vendored
156
deps/simd-adler32/src/hash.rs
vendored
@@ -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
|
||||
);
|
||||
13
deps/simd-adler32/src/imp/mod.rs
vendored
13
deps/simd-adler32/src/imp/mod.rs
vendored
@@ -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
|
||||
}
|
||||
69
deps/simd-adler32/src/imp/scalar.rs
vendored
69
deps/simd-adler32/src/imp/scalar.rs
vendored
@@ -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)
|
||||
}
|
||||
}
|
||||
309
deps/simd-adler32/src/lib.rs
vendored
309
deps/simd-adler32/src/lib.rs
vendored
@@ -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 hasher’s 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user