mirror of
https://github.com/edera-dev/sprout.git
synced 2026-02-04 07:10:18 +00:00
Implement splash screen feature.
This commit is contained in:
119
Cargo.lock
generated
119
Cargo.lock
generated
@@ -2,6 +2,18 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.10.3"
|
version = "0.10.3"
|
||||||
@@ -14,24 +26,77 @@ version = "2.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04bcaeafafdd3cd1cb5d986ff32096ad1136630207c49b9091e3ae541090d938"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.11.4"
|
version = "2.11.4"
|
||||||
@@ -48,6 +113,46 @@ version = "0.4.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.7.6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.101"
|
version = "1.0.101"
|
||||||
@@ -77,6 +182,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.41"
|
version = "1.0.41"
|
||||||
@@ -125,10 +239,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sprout"
|
name = "sprout"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"image",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
|
|||||||
16
Cargo.toml
16
Cargo.toml
@@ -7,6 +7,12 @@ edition = "2024"
|
|||||||
toml = "0.9.7"
|
toml = "0.9.7"
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
|
|
||||||
|
[dependencies.image]
|
||||||
|
version = "0.25.6"
|
||||||
|
default-features = false
|
||||||
|
features = ["png"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
@@ -19,6 +25,10 @@ features = ["alloc", "logger"]
|
|||||||
lto = "thin"
|
lto = "thin"
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["splash"]
|
||||||
|
splash = ["dep:image"]
|
||||||
|
|
||||||
[profile.release-debuginfo]
|
[profile.release-debuginfo]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
strip = "none"
|
strip = "none"
|
||||||
@@ -28,3 +38,9 @@ debug = 1
|
|||||||
inherits = "dev"
|
inherits = "dev"
|
||||||
strip = "debuginfo"
|
strip = "debuginfo"
|
||||||
debug = 0
|
debug = 0
|
||||||
|
|
||||||
|
[patch.crates-io.simd-adler32]
|
||||||
|
path = "deps/simd-adler32"
|
||||||
|
|
||||||
|
[patch.crates-io.moxcms]
|
||||||
|
path = "deps/moxcms"
|
||||||
|
|||||||
202
LICENSE
202
LICENSE
@@ -1 +1,201 @@
|
|||||||
This repository is proprietary and owned by Edera, Inc.
|
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 2025 Edera Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ COPY sprout.efi /work/${EFI_NAME}.EFI
|
|||||||
COPY sprout.toml /work/SPROUT.TOML
|
COPY sprout.toml /work/SPROUT.TOML
|
||||||
COPY kernel.efi /work/KERNEL.EFI
|
COPY kernel.efi /work/KERNEL.EFI
|
||||||
COPY shell.efi /work/SHELL.EFI
|
COPY shell.efi /work/SHELL.EFI
|
||||||
|
COPY edera-splash.png /work/EDERA-SPLASH.PNG
|
||||||
RUN truncate -s256MiB sprout.img && \
|
RUN truncate -s256MiB sprout.img && \
|
||||||
parted --script sprout.img mklabel gpt > /dev/null 2>&1 && \
|
parted --script sprout.img mklabel gpt > /dev/null 2>&1 && \
|
||||||
parted --script sprout.img mkpart primary fat32 1MiB 100% > /dev/null 2>&1 && \
|
parted --script sprout.img mkpart primary fat32 1MiB 100% > /dev/null 2>&1 && \
|
||||||
@@ -20,6 +21,7 @@ RUN truncate -s256MiB sprout.img && \
|
|||||||
mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \
|
mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \
|
||||||
mcopy -i sprout.img SHELL.EFI ::/EFI/BOOT/ && \
|
mcopy -i sprout.img SHELL.EFI ::/EFI/BOOT/ && \
|
||||||
mcopy -i sprout.img SPROUT.TOML ::/ && \
|
mcopy -i sprout.img SPROUT.TOML ::/ && \
|
||||||
|
mcopy -i sprout.img EDERA-SPLASH.PNG ::/ && \
|
||||||
mv sprout.img /sprout.img
|
mv sprout.img /sprout.img
|
||||||
|
|
||||||
FROM scratch AS final
|
FROM scratch AS final
|
||||||
|
|||||||
13
deps/README.md
vendored
Normal file
13
deps/README.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 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
Normal file
9
deps/moxcms/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
.idea
|
||||||
|
app/target
|
||||||
|
flamegraph.svg
|
||||||
|
perf.data
|
||||||
|
profile.json.gz
|
||||||
|
.cargo
|
||||||
|
rust-toolchain.toml
|
||||||
49
deps/moxcms/Cargo.toml
vendored
Normal file
49
deps/moxcms/Cargo.toml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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
Normal file
201
deps/moxcms/LICENSE-APACHE.md
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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
Normal file
26
deps/moxcms/LICENSE.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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
Normal file
74
deps/moxcms/README.md
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# 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
Normal file
172
deps/moxcms/src/chad.rs
vendored
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
143
deps/moxcms/src/chromaticity.rs
vendored
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
642
deps/moxcms/src/cicp.rs
vendored
Normal file
@@ -0,0 +1,642 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
121
deps/moxcms/src/conversions/bpc.rs
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
388
deps/moxcms/src/conversions/gray2rgb.rs
vendored
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
383
deps/moxcms/src/conversions/gray2rgb_extended.rs
vendored
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
599
deps/moxcms/src/conversions/interpolator.rs
vendored
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
118
deps/moxcms/src/conversions/katana/finalizers.rs
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
483
deps/moxcms/src/conversions/katana/md3x3.rs
vendored
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
321
deps/moxcms/src/conversions/katana/md4x3.rs
vendored
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
284
deps/moxcms/src/conversions/katana/md_3xn.rs
vendored
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
294
deps/moxcms/src/conversions/katana/md_nx3.rs
vendored
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
393
deps/moxcms/src/conversions/katana/md_pipeline.rs
vendored
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
56
deps/moxcms/src/conversions/katana/mod.rs
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
100
deps/moxcms/src/conversions/katana/pcs_stages.rs
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
162
deps/moxcms/src/conversions/katana/rgb_xyz.rs
vendored
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
85
deps/moxcms/src/conversions/katana/stages.rs
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
62
deps/moxcms/src/conversions/katana/xyz_lab.rs
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
223
deps/moxcms/src/conversions/katana/xyz_rgb.rs
vendored
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
428
deps/moxcms/src/conversions/lut3x3.rs
vendored
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
249
deps/moxcms/src/conversions/lut3x4.rs
vendored
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
360
deps/moxcms/src/conversions/lut4.rs
vendored
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
802
deps/moxcms/src/conversions/lut_transforms.rs
vendored
Normal file
@@ -0,0 +1,802 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
559
deps/moxcms/src/conversions/mab.rs
vendored
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
307
deps/moxcms/src/conversions/mab4x3.rs
vendored
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
302
deps/moxcms/src/conversions/mba3x4.rs
vendored
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
728
deps/moxcms/src/conversions/md_lut.rs
vendored
Normal file
@@ -0,0 +1,728 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
188
deps/moxcms/src/conversions/md_luts_factory.rs
vendored
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
66
deps/moxcms/src/conversions/mod.rs
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
328
deps/moxcms/src/conversions/prelude_lut_xyz_rgb.rs
vendored
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
189
deps/moxcms/src/conversions/rgb2gray.rs
vendored
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
181
deps/moxcms/src/conversions/rgb2gray_extended.rs
vendored
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
395
deps/moxcms/src/conversions/rgb_xyz_factory.rs
vendored
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
899
deps/moxcms/src/conversions/rgbxyz.rs
vendored
Normal file
@@ -0,0 +1,899 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
560
deps/moxcms/src/conversions/rgbxyz_fixed.rs
vendored
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
330
deps/moxcms/src/conversions/rgbxyz_float.rs
vendored
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
266
deps/moxcms/src/conversions/transform_lut3_to_3.rs
vendored
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
274
deps/moxcms/src/conversions/transform_lut3_to_4.rs
vendored
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
351
deps/moxcms/src/conversions/transform_lut4_to_3.rs
vendored
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
61
deps/moxcms/src/conversions/xyz_lab.rs
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
154
deps/moxcms/src/dat.rs
vendored
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
541
deps/moxcms/src/defaults.rs
vendored
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
359
deps/moxcms/src/dt_ucs.rs
vendored
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
141
deps/moxcms/src/err.rs
vendored
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
1078
deps/moxcms/src/gamma.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
66
deps/moxcms/src/gamut.rs
vendored
Normal file
66
deps/moxcms/src/gamut.rs
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
223
deps/moxcms/src/helpers.rs
vendored
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
192
deps/moxcms/src/ictcp.rs
vendored
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
434
deps/moxcms/src/jzazbz.rs
vendored
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
375
deps/moxcms/src/jzczhz.rs
vendored
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
242
deps/moxcms/src/lab.rs
vendored
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
133
deps/moxcms/src/lib.rs
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
106
deps/moxcms/src/lut_hint.rs
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
698
deps/moxcms/src/luv.rs
vendored
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
72
deps/moxcms/src/matan/curve_shape.rs
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
60
deps/moxcms/src/matan/degeneration.rs
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
74
deps/moxcms/src/matan/discontinuity.rs
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
40
deps/moxcms/src/matan/mod.rs
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
52
deps/moxcms/src/matan/monotonic.rs
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
84
deps/moxcms/src/matan/slope_limit.rs
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
68
deps/moxcms/src/math/mod.rs
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
1272
deps/moxcms/src/matrix.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
82
deps/moxcms/src/mlaf.rs
vendored
Normal file
82
deps/moxcms/src/mlaf.rs
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
1239
deps/moxcms/src/nd_array.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
354
deps/moxcms/src/oklab.rs
vendored
Normal file
354
deps/moxcms/src/oklab.rs
vendored
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
294
deps/moxcms/src/oklch.rs
vendored
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
1365
deps/moxcms/src/profile.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
955
deps/moxcms/src/reader.rs
vendored
Normal file
955
deps/moxcms/src/reader.rs
vendored
Normal file
@@ -0,0 +1,955 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
723
deps/moxcms/src/rgb.rs
vendored
Normal file
@@ -0,0 +1,723 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
103
deps/moxcms/src/safe_math.rs
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
102
deps/moxcms/src/srlab2.rs
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
271
deps/moxcms/src/tag.rs
vendored
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
1334
deps/moxcms/src/transform.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1583
deps/moxcms/src/trc.rs
vendored
Normal file
1583
deps/moxcms/src/trc.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
889
deps/moxcms/src/writer.rs
vendored
Normal file
889
deps/moxcms/src/writer.rs
vendored
Normal file
@@ -0,0 +1,889 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
98
deps/moxcms/src/xyy.rs
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
177
deps/moxcms/src/yrg.rs
vendored
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* // 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
Normal file
12
deps/simd-adler32/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 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
Normal file
39
deps/simd-adler32/Cargo.toml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
[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
Normal file
21
deps/simd-adler32/LICENSE.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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
Normal file
131
deps/simd-adler32/README.md
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<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
Normal file
156
deps/simd-adler32/src/hash.rs
vendored
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
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
Normal file
13
deps/simd-adler32/src/imp/mod.rs
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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
Normal file
69
deps/simd-adler32/src/imp/scalar.rs
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
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
Normal file
309
deps/simd-adler32/src/lib.rs
vendored
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
//! # 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
hack/assets/edera-splash.png
Normal file
BIN
hack/assets/edera-splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 MiB |
@@ -71,6 +71,7 @@ if [ "${SKIP_KERNEL_BUILD}" != "1" ]; then
|
|||||||
|
|
||||||
copy_from_image "${DOCKER_PREFIX}/sprout-kernel-${TARGET_ARCH}" "kernel.efi" "${FINAL_DIR}/kernel.efi"
|
copy_from_image "${DOCKER_PREFIX}/sprout-kernel-${TARGET_ARCH}" "kernel.efi" "${FINAL_DIR}/kernel.efi"
|
||||||
cp "hack/configs/${SPROUT_CONFIG_NAME}.sprout.toml" "${FINAL_DIR}/sprout.toml"
|
cp "hack/configs/${SPROUT_CONFIG_NAME}.sprout.toml" "${FINAL_DIR}/sprout.toml"
|
||||||
|
cp "hack/assets/edera-splash.png" "${FINAL_DIR}/edera-splash.png"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${SKIP_VM_BUILD}" != "1" ]; then
|
if [ "${SKIP_VM_BUILD}" != "1" ]; then
|
||||||
@@ -100,6 +101,7 @@ if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then
|
|||||||
cp "${FINAL_DIR}/shell.efi" "${FINAL_DIR}/efi/EFI/BOOT/SHELL.EFI"
|
cp "${FINAL_DIR}/shell.efi" "${FINAL_DIR}/efi/EFI/BOOT/SHELL.EFI"
|
||||||
fi
|
fi
|
||||||
cp "${FINAL_DIR}/sprout.toml" "${FINAL_DIR}/efi/SPROUT.TOML"
|
cp "${FINAL_DIR}/sprout.toml" "${FINAL_DIR}/efi/SPROUT.TOML"
|
||||||
|
cp "${FINAL_DIR}/edera-splash.png" "${FINAL_DIR}/efi/EDERA-SPLASH.PNG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${SKIP_BOOT_BUILD}" != "1" ]; then
|
if [ "${SKIP_BOOT_BUILD}" != "1" ]; then
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ default-options = "tty=hvc0"
|
|||||||
[actions.welcome]
|
[actions.welcome]
|
||||||
print.text = "Welcome to Sprout!"
|
print.text = "Welcome to Sprout!"
|
||||||
|
|
||||||
|
[actions.splash]
|
||||||
|
splash.image = "edera-splash.png"
|
||||||
|
splash.time = 5
|
||||||
|
|
||||||
[actions.chainload-kernel]
|
[actions.chainload-kernel]
|
||||||
chainload.path = "$path"
|
chainload.path = "$path"
|
||||||
chainload.options = ["$default-options"]
|
chainload.options = ["$default-options"]
|
||||||
@@ -17,4 +21,4 @@ entry.actions = ["chainload-kernel"]
|
|||||||
values.name = ["kernel.efi"]
|
values.name = ["kernel.efi"]
|
||||||
|
|
||||||
[[phases.startup]]
|
[[phases.startup]]
|
||||||
actions = ["welcome"]
|
actions = ["splash", "welcome"]
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ use std::rc::Rc;
|
|||||||
pub mod chainload;
|
pub mod chainload;
|
||||||
pub mod print;
|
pub mod print;
|
||||||
|
|
||||||
|
#[cfg(feature = "splash")]
|
||||||
|
pub mod splash;
|
||||||
|
|
||||||
pub fn execute(context: Rc<Context>, name: impl AsRef<str>) {
|
pub fn execute(context: Rc<Context>, name: impl AsRef<str>) {
|
||||||
let Some(action) = context.root().actions().get(name.as_ref()) else {
|
let Some(action) = context.root().actions().get(name.as_ref()) else {
|
||||||
panic!("unknown action: {}", name.as_ref());
|
panic!("unknown action: {}", name.as_ref());
|
||||||
@@ -11,10 +14,18 @@ pub fn execute(context: Rc<Context>, name: impl AsRef<str>) {
|
|||||||
let context = context.finalize().freeze();
|
let context = context.finalize().freeze();
|
||||||
|
|
||||||
if let Some(chainload) = &action.chainload {
|
if let Some(chainload) = &action.chainload {
|
||||||
chainload::chainload(context, chainload);
|
chainload::chainload(context.clone(), chainload);
|
||||||
|
return;
|
||||||
} else if let Some(print) = &action.print {
|
} else if let Some(print) = &action.print {
|
||||||
print::print(context, print);
|
print::print(context.clone(), print);
|
||||||
} else {
|
return;
|
||||||
panic!("unknown action configuration");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "splash")]
|
||||||
|
if let Some(splash) = &action.splash {
|
||||||
|
splash::splash(context.clone(), splash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("unknown action configuration");
|
||||||
}
|
}
|
||||||
|
|||||||
121
src/actions/splash.rs
Normal file
121
src/actions/splash.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use crate::config::SplashConfiguration;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::utils::read_file_contents;
|
||||||
|
use image::imageops::{FilterType, resize};
|
||||||
|
use image::math::Rect;
|
||||||
|
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageReader, Rgba};
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use uefi::boot::ScopedProtocol;
|
||||||
|
use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput};
|
||||||
|
|
||||||
|
struct Framebuffer {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
pixels: Vec<BltPixel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Framebuffer {
|
||||||
|
fn new(width: usize, height: usize) -> Self {
|
||||||
|
Framebuffer {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixels: vec![BltPixel::new(0, 0, 0); width * height],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixel(&mut self, x: usize, y: usize) -> Option<&mut BltPixel> {
|
||||||
|
self.pixels.get_mut(y * self.width + x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blit(&self, gop: &mut GraphicsOutput) {
|
||||||
|
gop.blt(BltOp::BufferToVideo {
|
||||||
|
buffer: &self.pixels,
|
||||||
|
src: BltRegion::Full,
|
||||||
|
dest: (0, 0),
|
||||||
|
dims: (self.width, self.height),
|
||||||
|
})
|
||||||
|
.expect("failed to blit framebuffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_graphics() -> ScopedProtocol<GraphicsOutput> {
|
||||||
|
let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>()
|
||||||
|
.expect("failed to get graphics output");
|
||||||
|
uefi::boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle)
|
||||||
|
.expect("failed to open graphics output")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect {
|
||||||
|
let input = Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: image.width(),
|
||||||
|
height: image.height(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_ratio = input.width as f32 / input.height as f32;
|
||||||
|
let frame_ratio = frame.width as f32 / frame.height as f32;
|
||||||
|
|
||||||
|
let mut output = Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: frame.width,
|
||||||
|
height: frame.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
if input_ratio < frame_ratio {
|
||||||
|
output.width = (frame.height as f32 * input_ratio).floor() as u32;
|
||||||
|
output.height = frame.height;
|
||||||
|
output.x = frame.x + (frame.width - output.width) / 2;
|
||||||
|
output.y = frame.y;
|
||||||
|
} else {
|
||||||
|
output.width = frame.width;
|
||||||
|
output.height = (frame.width as f32 / input_ratio).floor() as u32;
|
||||||
|
output.x = frame.x;
|
||||||
|
output.y = frame.y + (frame.height - output.height) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize_to_fit(image: &DynamicImage, frame: Rect) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
|
||||||
|
let image = image.to_rgba8();
|
||||||
|
resize(&image, frame.width, frame.height, FilterType::Lanczos3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(image: DynamicImage) {
|
||||||
|
let mut gop = setup_graphics();
|
||||||
|
let (width, height) = gop.current_mode_info().resolution();
|
||||||
|
let display_frame = Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: width as _,
|
||||||
|
height: height as _,
|
||||||
|
};
|
||||||
|
let fit = fit_to_frame(&image, display_frame);
|
||||||
|
let image = resize_to_fit(&image, fit);
|
||||||
|
|
||||||
|
let mut framebuffer = Framebuffer::new(width, height);
|
||||||
|
for (x, y, pixel) in image.enumerate_pixels() {
|
||||||
|
let Some(fb) = framebuffer.pixel((x + fit.x) as usize, (fit.y + y) as usize) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
fb.red = pixel[0];
|
||||||
|
fb.green = pixel[1];
|
||||||
|
fb.blue = pixel[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
framebuffer.blit(&mut gop);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn splash(context: Rc<Context>, configuration: &SplashConfiguration) {
|
||||||
|
let image = context.stamp(&configuration.image);
|
||||||
|
let image = read_file_contents(&image);
|
||||||
|
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
|
||||||
|
.decode()
|
||||||
|
.expect("failed to decode splash image");
|
||||||
|
draw(image);
|
||||||
|
std::thread::sleep(Duration::from_secs(configuration.time as u64));
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user