palette-0.7.5/.cargo_vcs_info.json0000644000000001450000000000100125030ustar { "git": { "sha1": "94652f7479c08f35c4a6c7319ee3711cb794357f" }, "path_in_vcs": "palette" }palette-0.7.5/Cargo.lock0000644000000246360000000000100104710ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler32" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "assert_float_eq" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cea652ffbedecf29e9cd41bb4c066881057a42c0c119040f022802b26853e77" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "deflate" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", "byteorder", ] [[package]] name = "enterpolation" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fadf5c8cbf7c6765ff05ccbd8811cd7bc3a763e4671755204552bf8740d042a" dependencies = [ "assert_float_eq", "num-traits", "serde", "topology-traits", ] [[package]] name = "fast-srgb8" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "find-crate" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ "toml", ] [[package]] name = "image" version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", "color_quant", "num-iter", "num-rational", "num-traits", "png", ] [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "miniz_oxide" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" dependencies = [ "adler32", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-iter" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "palette" version = "0.7.5" dependencies = [ "approx", "bytemuck", "enterpolation", "fast-srgb8", "image", "libm", "palette_derive", "phf", "rand", "rand_mt", "ron", "serde", "serde_json", "wide", ] [[package]] name = "palette_derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8890702dbec0bad9116041ae586f84805b13eecd1d8b1df27c29998a9969d6d" dependencies = [ "find-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "phf" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_macros" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn", ] [[package]] name = "phf_shared" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] [[package]] name = "png" version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", "deflate", "miniz_oxide", ] [[package]] name = "proc-macro2" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rand_mt" version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49e018c6ded60e5252609887c12eb3ca2592e9248c5894a7db3975c8a7a1e2df" dependencies = [ "rand_core", ] [[package]] name = "ron" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" dependencies = [ "base64", "bitflags", "serde", ] [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "safe_arch" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" dependencies = [ "bytemuck", ] [[package]] name = "serde" version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "syn" version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "topology-traits" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0c8dab428531e30115d3bfd6e3092b55256a4a7b4f87cb3abe37a000b1f4032" dependencies = [ "num-traits", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "wide" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c68938b57b33da363195412cfc5fc37c9ed49aa9cfe2156fde64b8d2c9498242" dependencies = [ "bytemuck", "safe_arch", ] palette-0.7.5/Cargo.toml0000644000000050500000000000100105010ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.60.0" name = "palette" version = "0.7.5" authors = ["Erik Hedvall "] build = "build/main.rs" exclude = [ "scripts/*", "examples/*", "tests/*", "regression_tests/*", "benches/*", "res/*", ".travis.yml", ".gitignore", "CHANGELOG.md", "CONTRIBUTING.md", "version.sh", ] description = "Convert and manage colors with a focus on correctness, flexibility and ease of use." documentation = "https://docs.rs/palette/0.7.5/palette/" readme = "README.md" keywords = [ "color", "conversion", "linear", "pixel", "rgb", ] categories = [ "graphics", "multimedia::images", "no-std", ] license = "MIT OR Apache-2.0" repository = "https://github.com/Ogeon/palette" resolver = "2" [package.metadata.docs.rs] all-features = true [lib] bench = false [dependencies.approx] version = "0.5" optional = true default-features = false [dependencies.bytemuck] version = "1" optional = true [dependencies.fast-srgb8] version = "1.0.0" [dependencies.libm] version = "0.2.1" optional = true default-features = false [dependencies.palette_derive] version = "0.7.5" [dependencies.phf] version = "0.11.0" features = ["macros"] optional = true default-features = false [dependencies.rand] version = "0.8" optional = true default-features = false [dependencies.serde] version = "1" features = ["serde_derive"] optional = true [dependencies.wide] version = "0.7.3" optional = true default-features = false [dev-dependencies.enterpolation] version = "0.2.0" [dev-dependencies.image] version = "0.23.14" features = ["png"] default-features = false [dev-dependencies.rand_mt] version = "4" features = ["rand-traits"] default-features = false [dev-dependencies.ron] version = "=0.8.0" [dev-dependencies.serde_json] version = "1" [features] alloc = [] default = [ "named_from_str", "std", "approx", ] find-crate = ["palette_derive/find-crate"] named = [] named_from_str = [ "named", "phf", ] random = ["rand"] serializing = [ "serde", "std", ] std = [ "alloc", "approx?/std", ] palette-0.7.5/Cargo.toml.orig000064400000000000000000000037561046102023000141750ustar 00000000000000[package] name = "palette" version = "0.7.5" #automatically updated authors = ["Erik Hedvall "] exclude = [ "scripts/*", "examples/*", "tests/*", "regression_tests/*", "benches/*", "res/*", ".travis.yml", ".gitignore", "CHANGELOG.md", "CONTRIBUTING.md", "version.sh", ] description = "Convert and manage colors with a focus on correctness, flexibility and ease of use." documentation = "https://docs.rs/palette/0.7.5/palette/" repository = "https://github.com/Ogeon/palette" readme = "README.md" keywords = ["color", "conversion", "linear", "pixel", "rgb"] license = "MIT OR Apache-2.0" edition = "2018" resolver = "2" categories = ["graphics", "multimedia::images", "no-std"] build = "build/main.rs" rust-version = "1.60.0" [features] default = ["named_from_str", "std", "approx"] named_from_str = ["named", "phf"] named = [] random = ["rand"] serializing = ["serde", "std"] find-crate = ["palette_derive/find-crate"] std = ["alloc", "approx?/std"] alloc = [] [lib] bench = false [dependencies] palette_derive = { version = "0.7.5", path = "../palette_derive" } fast-srgb8 = "1.0.0" approx = { version = "0.5", default-features = false, optional = true } libm = { version = "0.2.1", default-features = false, optional = true } [dependencies.phf] version = "0.11.0" optional = true default-features = false features = ["macros"] [dependencies.rand] version = "0.8" default-features = false optional = true [dependencies.serde] version = "1" features = ["serde_derive"] optional = true [dependencies.bytemuck] version = "1" optional = true [dependencies.wide] version = "0.7.3" optional = true default-features = false [dev-dependencies] serde_json = "1" ron = "=0.8.0" # Pinned due to MSRV mismatch enterpolation = "0.2.0" [dev-dependencies.image] version = "0.23.14" default-features = false features = ["png"] [dev-dependencies.rand_mt] version = "4" default-features = false features = ["rand-traits"] [package.metadata.docs.rs] all-features = true palette-0.7.5/LICENSE-APACHE000064400000000000000000000251371046102023000132270ustar 00000000000000 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 [yyyy] [name of copyright owner] 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. palette-0.7.5/LICENSE-MIT000064400000000000000000000020701046102023000127260ustar 00000000000000The MIT License (MIT) Copyright (c) 2015 Erik Hedvall 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. palette-0.7.5/README.md000064400000000000000000000333741046102023000125640ustar 00000000000000# Palette A color management and conversion library that focuses on maintaining correctness, flexibility and ease of use. It makes use of the type system to prevent mistakes, support a wide range of color spaces (including user defined variants) and offer different ways of integrating with other libraries. [The announcement post for 0.7.5](https://ogeon.github.io/2024/02/25/palette-0.7.5.html). ## Feature Summary * Type system representations of color spaces, including RGB, HSL, HSV, HWB, L\*a\*b\*, L\*C\*h°, XYZ and xyY. * Copy free conversion to and from color buffers allows simple integration with other crates and systems. * Color operations implemented as traits, such as arithmetic, lighten/darken, hue shifting, mixing/interpolating, and SVG blend functions. * Color spaces can be customized, using type parameters, to support different levels of precision, linearity, white points, RGB standards, etc. * Supports `#[no_std]`. * Optional `serde`, `rand`, and `bytemuck` integration. ## Minimum Supported Rust Version (MSRV) This version of Palette has been automatically tested with Rust version `1.60.0` and the `stable`, `beta`, and `nightly` channels. Future versions of the library may advance the minimum supported version to make use of new language features, but this will normally be considered a breaking change. Exceptions may be made for security patches, dependencies advancing their MSRV in minor or patch releases, and similar changes. ## Getting Started Add the following lines to your `Cargo.toml` file: ```toml [dependencies] palette = "0.7.5" ``` or these lines if you want to opt out of `std`: ```toml [dependencies.palette] version = "0.7.5" default-features = false features = ["libm"] # Uses libm instead of std for floating point math ``` ### Cargo Features These features are enabled by default: * `"named"` - Enables color constants, located in the `named` module. * `"named_from_str"` - Enables `named::from_str`, which maps name strings to colors. * `"std"` - Enables use of the standard library. Also enables `"alloc"`. * `"alloc"` - Enables implementations for allocating types, such as `Vec` or `Box`. * `"approx"` - Enables approximate comparison using [`approx`]. These features are disabled by default: * `"serializing"` - Enables color serializing and deserializing using [`serde`]. * `"random"` - Enables generating random colors using [`rand`]. * `"libm"` - Uses the [`libm`] floating point math library (for when the `std` feature is disabled). * `"bytemuck"` - Enables casting between plain data types using [`bytemuck`]. * `"wide"` - Enables support for using SIMD types from [`wide`]. * `"find-crate"` - Enables derives to find the `palette` crate when it's renamed in `Cargo.toml`. ### Using palette in an embedded environment Palette supports `#![no_std]` environments by disabling the `"std"` feature. It uses [`libm`], via the `"libm"` feature, to provide the floating-point operations that are typically in `std`, and the `"alloc"` feature to provide features that use allocating types. However, serializing with `serde` is not available without the standard library. ## Examples These are examples of some of the features listed in the feature summary. ### Converting It's possible to convert from one color space to another with the `FromColor` and `IntoColor` traits. They are similar to `From` and `Into`, but tailored for colors: ```rust use palette::{FromColor, Hsl, IntoColor, Lch, Srgb}; let my_rgb = Srgb::new(0.8, 0.3, 0.3); let mut my_lch = Lch::from_color(my_rgb); my_lch.hue += 180.0; let mut my_hsl: Hsl = my_lch.into_color(); my_hsl.lightness *= 0.6; let my_new_rgb = Srgb::from_color(my_hsl); ``` This image shows the starting color and the results of the two changes: ![The result of each step in the "converting" example.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/gfx/readme_converting.png) Most of the common color spaces are already implemented in Palette, but some situations may require something more customized. The conversion traits make it possible to integrate custom color types into the system. For example, this can be used for adding new color spaces or making a simpler user-facing API. A longer and more advanced example that shows how to implement the conversion traits for a custom color type can be found further down. ### Pixels And Buffers When working with image or pixel buffers, or any color type that can be converted to a slice of components (ex. `&[u8]`), the `cast` module provides traits and functions for turning them into slices of Palette colors without cloning the whole buffer: ```rust use palette::{cast::ComponentsAsMut, Srgb}; // The input to this function could be data from an image file or // maybe a texture in a game. fn swap_red_and_blue(my_rgb_image: &mut [u8]) { // Convert `my_rgb_image` into `&mut [Srgb]` without copying. let my_rgb_image: &mut [Srgb] = my_rgb_image.components_as_mut(); for color in my_rgb_image { std::mem::swap(&mut color.red, &mut color.blue); } } ``` | Before | After | |--------|-------| | ![The fruit image before swapping the red and blue color channels.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/example-data/input/fruits-128.png) | ![The fruit image with the red and blue color channels swapped.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/gfx/readme_pixels_and_buffers.png) | It's also possible to create a single color from a slice or array. Let's say we are using something that implements `AsMut<[u8; 3]>`: ```rust use palette::Srgb; fn swap_red_and_blue(mut my_rgb: impl AsMut<[u8; 3]>) { let my_rgb: &mut Srgb = my_rgb.as_mut().into(); std::mem::swap(&mut my_rgb.red, &mut my_rgb.blue); } ``` This makes it possible to use Palette with any other crate that can convert their color types to slices and arrays, with minimal glue code and little to no overhead. It's also possible to go the opposite direction and convert Palette types to slices and arrays. ### Color Operations Palette comes with a number of color operations built in, such as saturate/desaturate, hue shift, etc., in the form of operator traits. That means it's possible to write generic functions that perform these operation on any color space that supports them. The output will vary depending on the color space's characteristics. ```rust use palette::{Hsl, Hsv, Lighten, Mix, ShiftHue}; fn transform_color(color: C, amount: f32) -> C where C: ShiftHue + Lighten + Mix + Copy, { let new_color = color.shift_hue(170.0).lighten(1.0); // Interpolate between the old and new color. color.mix(new_color, amount) } let new_hsl = transform_color(Hsl::new_srgb(0.00, 0.70, 0.20), 0.8); let new_hsv = transform_color(Hsv::new_srgb(0.00, 0.82, 0.34), 0.8); ``` This image shows the transition from the color to `new_color` in HSL and HSV: ![Gradients showing the transition from the starting color to the modified color in HSL and HSV.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/gfx/readme_color_operations_1.png) In addition to the operator traits, the SVG blend and composition functions have also been implemented. ```rust use palette::{ blend::Compose, cast::{ComponentsAs, ComponentsAsMut}, Srgb, WithAlpha, }; // The input to this function could be data from image files. fn alpha_blend_images(image1: &mut [u8], image2: &[u8]) { // Convert the images into `&mut [Srgb]` and `&[Srgb]` without copying. let image1: &mut [Srgb] = image1.components_as_mut(); let image2: &[Srgb] = image2.components_as(); for (color1, color2) in image1.iter_mut().zip(image2) { // Convert the colors to linear floating point format and give them transparency values. let color1_alpha = color1.into_linear().opaque(); let color2_alpha = color2.into_linear().with_alpha(0.5); // Alpha blend `color2_alpha` over `color1_alpha`. let blended = color2_alpha.over(color1_alpha); // Convert the color part back to `Srgb` and overwrite the value in image1. *color1 = blended.color.into_encoding(); } } ``` | Image 1 | Image 2 | Result | |---------|---------|--------| | ![A photo of various fruit.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/example-data/input/fruits-128.png) | ![A photo of kitten in a strawhat.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/example-data/input/cat-128.png) |![Image 2 blended over Image 1 with 50% transparency.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/gfx/readme_color_operations_2.png) There's also the option to explicitly convert to and from premultiplied alpha, to avoid converting back and forth more than necessary, using the `PreAlpha` type. ### Gradients Most color types are directly compatible with gradient and interpolation crates, such as [`enterpolation`]: ```rust use enterpolation::{linear::ConstEquidistantLinear, Curve}; use palette::LinSrgb; let gradient = ConstEquidistantLinear::::equidistant_unchecked([ LinSrgb::new(0.00, 0.05, 0.20), LinSrgb::new(0.70, 0.10, 0.20), LinSrgb::new(0.95, 0.90, 0.30), ]); let taken_colors: Vec<_> = gradient.take(10).collect(); ``` Here's the gradient as both its continuous form and as the 10 colors from `.take(10)`: ![An illustration of the gradient with the continuous form above a row of discrete color swatches.](https://raw.githubusercontent.com/Ogeon/palette/05e60121f3ab39aba972c477f258c70d0495551d/gfx/readme_gradients_1.png) ### Customizing Color Spaces The built-in color spaces have been made customizable to account for as much variation as possible. The more common variants have been exposed as type aliases (like `Srgb`, `Srgba` and `LinSrgb` from above), but it's entirely possible to make custom compositions, including with entirely new parameters. For example, making up your own RGB standard: ```rust use palette::{ encoding, white_point, rgb::Rgb, chromatic_adaptation::AdaptFrom, Srgb }; // RgbStandard and RgbSpace are implemented for 2 and 3 element tuples, // allowing mixing and matching of existing types. In this case we are // combining sRGB primaries, the CIE equal energy white point and the // sRGB transfer function (a.k.a. encoding or gamma). type EqualEnergyStandard = (encoding::Srgb, white_point::E, encoding::Srgb); type EqualEnergySrgb = Rgb; let ee_rgb = EqualEnergySrgb::new(1.0, 0.5, 0.3); // We need to use chromatic adaptation when going between white points. let srgb = Srgb::adapt_from(ee_rgb); ``` It's also possible to implement the traits for a custom type, for when the built-in options are not enough. ### Converting Custom Color Types The following example shows how it's possible for Palette users to convert from and into a custom made `Color` type. It's not exactly a one-liner, but it can still save a lot of repetitive manual work. ```rust use palette::{ convert::FromColorUnclamped, encoding, rgb::Rgb, IntoColor, WithAlpha, Clamp, Srgb, Lcha }; // This implements conversion to and from all Palette colors. #[derive(FromColorUnclamped, WithAlpha)] // We have to tell Palette that we will take care of converting to/from sRGB. #[palette(skip_derives(Rgb), rgb_standard = "encoding::Srgb")] struct Color { r: f32, g: f32, b: f32, // Let Palette know this is our alpha channel. #[palette(alpha)] a: f32, } // There's no blanket implementation for Self -> Self, unlike the From trait. // This is to better allow cases like Self -> Self. impl FromColorUnclamped for Color { fn from_color_unclamped(color: Color) -> Color { color } } // Convert from any kind of f32 sRGB. impl FromColorUnclamped> for Color where Srgb: FromColorUnclamped>, { fn from_color_unclamped(color: Rgb) -> Color { let srgb = Srgb::from_color_unclamped(color); Color { r: srgb.red, g: srgb.green, b: srgb.blue, a: 1.0 } } } // Convert into any kind of f32 sRGB. impl FromColorUnclamped for Rgb where Rgb: FromColorUnclamped, { fn from_color_unclamped(color: Color) -> Self { let srgb = Srgb::new(color.r, color.g, color.b); Self::from_color_unclamped(srgb) } } // Add the required clamping. impl Clamp for Color { fn clamp(self) -> Self { Color { r: self.r.min(1.0).max(0.0), g: self.g.min(1.0).max(0.0), b: self.b.min(1.0).max(0.0), a: self.a.min(1.0).max(0.0), } } } // This function uses only our `Color`, but Palette users can convert to it. fn do_something(color: Color) { // ... } do_something(Color { r: 1.0, g: 0.0, b: 1.0, a: 0.5 }); do_something(Lcha::new(60.0, 116.0, 328.0, 0.5).into_color()); // This function has the conversion built in and takes any compatible // color type as input. fn generic_do_something(color: impl IntoColor) { let color = color.into_color(); // ... } generic_do_something(Color { r: 1.0, g: 0.0, b: 1.0, a: 0.5 }); generic_do_something(Lcha::new(60.0, 116.0, 328.0, 0.5)); ``` ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. [`serde`]: https://crates.io/crates/serde [`rand`]: https://crates.io/crates/rand [`libm`]: https://crates.io/crates/libm [`bytemuck`]: https://crates.io/crates/bytemuck [`wide`]: https://crates.io/crates/wide [`approx`]: https://crates.io/crates/approx [`enterpolation`]: https://crates.io/crates/enterpolation palette-0.7.5/build/main.rs000064400000000000000000000000561046102023000136650ustar 00000000000000mod named; fn main() { named::build(); } palette-0.7.5/build/named.rs000064400000000000000000000050221046102023000140230ustar 00000000000000use std::fs::File; pub fn build() { use std::path::Path; let out_dir = ::std::env::var("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("named.rs"); let mut writer = File::create(dest_path).expect("couldn't create named.rs"); build_colors(&mut writer); } #[cfg(feature = "named")] pub fn build_colors(writer: &mut File) { use std::io::{BufRead, BufReader, Write}; let reader = BufReader::new(File::open("build/svg_colors.txt").expect("could not open svg_colors.txt")); let mut entries = vec![]; for line in reader.lines() { let line = line.unwrap(); let mut parts = line.split('\t'); let name = parts.next().expect("couldn't get the color name"); let mut rgb = parts .next() .unwrap_or_else(|| panic!("couldn't get color for {}", name)) .split(", "); let red: u8 = rgb .next() .and_then(|r| r.trim().parse().ok()) .unwrap_or_else(|| panic!("couldn't get red for {}", name)); let green: u8 = rgb .next() .and_then(|r| r.trim().parse().ok()) .unwrap_or_else(|| panic!("couldn't get green for {}", name)); let blue: u8 = rgb .next() .and_then(|r| r.trim().parse().ok()) .unwrap_or_else(|| panic!("couldn't get blue for {}", name)); writeln!(writer, "\n///
", name).unwrap(); writeln!( writer, "pub const {}: crate::rgb::Srgb = crate::rgb::Srgb::new({}, {}, {});", name.to_uppercase(), red, green, blue ) .unwrap(); entries.push((name.to_owned(), name.to_uppercase())); } gen_from_str(writer, &entries) } #[cfg(feature = "named_from_str")] fn gen_from_str(writer: &mut File, entries: &[(String, String)]) { use std::io::Write; writer .write_all( "static COLORS: ::phf::Map<&'static str, crate::rgb::Srgb> = phf::phf_map! {\n" .as_bytes(), ) .unwrap(); for (key, value) in entries { writeln!(writer, " \"{}\" => {},", key, value).unwrap(); } writer.write_all("};\n".as_bytes()).unwrap(); } #[cfg(not(feature = "named"))] pub fn build_colors(_writer: &mut File) {} #[allow(unused)] #[cfg(not(feature = "named_from_str"))] fn gen_from_str(_writer: &mut File, _entries: &[(String, String)]) {} palette-0.7.5/build/svg_colors.txt000064400000000000000000000065431046102023000153230ustar 00000000000000aliceblue 240, 248, 255 antiquewhite 250, 235, 215 aqua 0, 255, 255 aquamarine 127, 255, 212 azure 240, 255, 255 beige 245, 245, 220 bisque 255, 228, 196 black 0, 0, 0 blanchedalmond 255, 235, 205 blue 0, 0, 255 blueviolet 138, 43, 226 brown 165, 42, 42 burlywood 222, 184, 135 cadetblue 95, 158, 160 chartreuse 127, 255, 0 chocolate 210, 105, 30 coral 255, 127, 80 cornflowerblue 100, 149, 237 cornsilk 255, 248, 220 crimson 220, 20, 60 cyan 0, 255, 255 darkblue 0, 0, 139 darkcyan 0, 139, 139 darkgoldenrod 184, 134, 11 darkgray 169, 169, 169 darkgreen 0, 100, 0 darkgrey 169, 169, 169 darkkhaki 189, 183, 107 darkmagenta 139, 0, 139 darkolivegreen 85, 107, 47 darkorange 255, 140, 0 darkorchid 153, 50, 204 darkred 139, 0, 0 darksalmon 233, 150, 122 darkseagreen 143, 188, 143 darkslateblue 72, 61, 139 darkslategray 47, 79, 79 darkslategrey 47, 79, 79 darkturquoise 0, 206, 209 darkviolet 148, 0, 211 deeppink 255, 20, 147 deepskyblue 0, 191, 255 dimgray 105, 105, 105 dimgrey 105, 105, 105 dodgerblue 30, 144, 255 firebrick 178, 34, 34 floralwhite 255, 250, 240 forestgreen 34, 139, 34 fuchsia 255, 0, 255 gainsboro 220, 220, 220 ghostwhite 248, 248, 255 gold 255, 215, 0 goldenrod 218, 165, 32 gray 128, 128, 128 grey 128, 128, 128 green 0, 128, 0 greenyellow 173, 255, 47 honeydew 240, 255, 240 hotpink 255, 105, 180 indianred 205, 92, 92 indigo 75, 0, 130 ivory 255, 255, 240 khaki 240, 230, 140 lavender 230, 230, 250 lavenderblush 255, 240, 245 lawngreen 124, 252, 0 lemonchiffon 255, 250, 205 lightblue 173, 216, 230 lightcoral 240, 128, 128 lightcyan 224, 255, 255 lightgoldenrodyellow 250, 250, 210 lightgray 211, 211, 211 lightgreen 144, 238, 144 lightgrey 211, 211, 211 lightpink 255, 182, 193 lightsalmon 255, 160, 122 lightseagreen 32, 178, 170 lightskyblue 135, 206, 250 lightslategray 119, 136, 153 lightslategrey 119, 136, 153 lightsteelblue 176, 196, 222 lightyellow 255, 255, 224 lime 0, 255, 0 limegreen 50, 205, 50 linen 250, 240, 230 magenta 255, 0, 255 maroon 128, 0, 0 mediumaquamarine 102, 205, 170 mediumblue 0, 0, 205 mediumorchid 186, 85, 211 mediumpurple 147, 112, 219 mediumseagreen 60, 179, 113 mediumslateblue 123, 104, 238 mediumspringgreen 0, 250, 154 mediumturquoise 72, 209, 204 mediumvioletred 199, 21, 133 midnightblue 25, 25, 112 mintcream 245, 255, 250 mistyrose 255, 228, 225 moccasin 255, 228, 181 navajowhite 255, 222, 173 navy 0, 0, 128 oldlace 253, 245, 230 olive 128, 128, 0 olivedrab 107, 142, 35 orange 255, 165, 0 orangered 255, 69, 0 orchid 218, 112, 214 palegoldenrod 238, 232, 170 palegreen 152, 251, 152 paleturquoise 175, 238, 238 palevioletred 219, 112, 147 papayawhip 255, 239, 213 peachpuff 255, 218, 185 peru 205, 133, 63 pink 255, 192, 203 plum 221, 160, 221 powderblue 176, 224, 230 purple 128, 0, 128 rebeccapurple 102, 51, 153 red 255, 0, 0 rosybrown 188, 143, 143 royalblue 65, 105, 225 saddlebrown 139, 69, 19 salmon 250, 128, 114 sandybrown 244, 164, 96 seagreen 46, 139, 87 seashell 255, 245, 238 sienna 160, 82, 45 silver 192, 192, 192 skyblue 135, 206, 235 slateblue 106, 90, 205 slategray 112, 128, 144 slategrey 112, 128, 144 snow 255, 250, 250 springgreen 0, 255, 127 steelblue 70, 130, 180 tan 210, 180, 140 teal 0, 128, 128 thistle 216, 191, 216 tomato 255, 99, 71 turquoise 64, 224, 208 violet 238, 130, 238 wheat 245, 222, 179 white 255, 255, 255 whitesmoke 245, 245, 245 yellow 255, 255, 0 yellowgreen 154, 205, 50 palette-0.7.5/src/alpha/alpha.rs000064400000000000000000000776201046102023000146160ustar 00000000000000use core::{ fmt, iter::FromIterator, ops::{ Add, AddAssign, BitAnd, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Sub, SubAssign, }, }; #[cfg(feature = "approx")] use approx::{AbsDiffEq, RelativeEq, UlpsEq}; #[cfg(feature = "random")] use rand::{ distributions::{ uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}, Distribution, Standard, }, Rng, }; use crate::{ blend::{PreAlpha, Premultiply}, bool_mask::HasBoolMask, cast::ArrayCast, clamp, clamp_assign, convert::{FromColorUnclamped, IntoColorUnclamped}, num::{self, Arithmetics, One, PartialCmp, SaturatingAdd, SaturatingSub, Zero}, stimulus::Stimulus, ArrayExt, Clamp, ClampAssign, GetHue, IsWithinBounds, Lighten, LightenAssign, Mix, MixAssign, NextArray, Saturate, SaturateAssign, SetHue, ShiftHue, ShiftHueAssign, WithAlpha, WithHue, }; /// An alpha component wrapper for colors, for adding transparency. /// /// Instead of having separate types for "RGB with alpha", "HSV with alpha", and /// so on, Palette uses this wrapper type to attach the alpha component. The /// memory representation is the same as if `alpha` was the last member/property /// of `color`, which is just as space efficient. The perk of having a wrapper /// is that the alpha can easily be added to or separated form any color. /// /// # Creating Transparent Values /// /// The color types in Palette have transparent type aliases, such as /// [`Srgba`](crate::Srgba) for [`Srgb`][crate::Srgb] or [`Hsla`](crate::Hsla) /// for [`Hsl`](crate::Hsl). These aliases implement `new` and other useful /// methods. Here's the same example as for [`Rgb`](crate::rgb::Rgb), but with /// transparency: /// /// ``` /// use palette::Srgba; /// /// let rgba_u8 = Srgba::new(171u8, 193, 35, 128); /// let rgab_f32 = Srgba::new(0.3f32, 0.8, 0.1, 0.5); /// /// // `new` is also `const`: /// const RGBA_U8: Srgba = Srgba::new(171, 193, 35, 128); /// /// // Conversion methods from the color type are usually available for transparent /// // values too. For example `into_format` for changing the number format: /// let rgb_u8_from_f32 = Srgba::new(0.3f32, 0.8, 0.1, 0.5).into_format::(); /// /// // Hexadecimal is also supported for RGBA, with or without the #: /// let rgb_from_hex1: Srgba = "#f034e65a".parse().unwrap(); /// let rgb_from_hex2: Srgba = "f034e65a".parse().unwrap(); /// assert_eq!(rgb_from_hex1, rgb_from_hex2); /// /// // This includes the shorthand format: /// let rgb_from_short_hex: Srgba = "f3ea".parse().unwrap(); /// let rgb_from_long_hex: Srgba = "ff33eeaa".parse().unwrap(); /// assert_eq!(rgb_from_short_hex, rgb_from_long_hex); /// /// // It's also possible to convert from (and to) arrays, tuples and `u32` values: /// let rgb_from_array = Srgba::from([171u8, 193, 35, 128]); /// let rgb_from_tuple = Srgba::from((171u8, 193, 35, 128)); /// let rgb_from_u32 = Srgba::from(0x607F005A); /// ``` /// /// Opaque values can be made transparent using the [`WithAlpha`] trait, in /// addition to simply wrapping them in `Alpha`. [`WithAlpha`] is also useful in /// generic code, since it's implemented for both opaque and transparent types. /// /// ``` /// use palette::{WithAlpha, Srgb}; /// /// let rgb = Srgb::new(171u8, 193, 35); /// let rgba = rgb.with_alpha(128u8); /// assert_eq!(rgba.alpha, 128); /// ``` /// /// You may have noticed the `u8` in `rgb.with_alpha(128u8)`. That's because /// `Alpha` allows the transparency component to have a different type than the /// color components. It would be just as valid to write /// `rgb.with_alpha(0.5f32)`, for example. /// /// # Accessing Color Components /// /// To help with the nesting, `Alpha` implements [`Deref`] and [`DerefMut`]. /// This use of the traits is a bit unconventional, since `Alpha` isn't a smart /// pointer. It turned out to be a quite successful experiment that stuck /// around. /// /// ``` /// use palette::Srgba; /// /// let rgba = Srgba::new(171u8, 193, 35, 128); /// let red = rgba.red; // Accesses `rgba.color.red`. /// let alpha = rgba.alpha; // Accesses `rgba.alpha`. /// let rgb = rgba.color; // Accesses `rgba.color`; /// ``` /// /// The main drawback is in generic code: /// /// ```compile_fail /// use palette::Srgba; /// /// fn get_red(rgba: Srgba) -> T { /// rgba.red // Error: cannot move out of dereference of `Alpha, T>` /// } /// ``` /// /// `red` has to be accessed through `color`: /// /// ``` /// use palette::Srgba; /// /// fn get_red(rgba: Srgba) -> T { /// rgba.color.red /// } /// ``` #[derive(Clone, Copy, Debug)] #[repr(C)] pub struct Alpha { /// The color. pub color: C, /// The transparency component. 0.0 (or 0u8) is fully transparent and 1.0 /// (or 255u8) is fully opaque. pub alpha: T, } impl Alpha { /// Return an iterator over the colors in the wrapped collections. pub fn iter<'a>(&'a self) -> <&'a Self as IntoIterator>::IntoIter where &'a Self: IntoIterator, { self.into_iter() } /// Return an iterator that allows modifying the colors in the wrapped collections. pub fn iter_mut<'a>(&'a mut self) -> <&'a mut Self as IntoIterator>::IntoIter where &'a mut Self: IntoIterator, { self.into_iter() } } impl Alpha { /// Alpha mask the color by its transparency. pub fn premultiply(self) -> PreAlpha { PreAlpha::new(self.color, self.alpha) } } impl Alpha { /// Return the `alpha` value minimum. pub fn min_alpha() -> T { T::zero() } /// Return the `alpha` value maximum. pub fn max_alpha() -> T { T::max_intensity() } } impl PartialEq for Alpha where T: PartialEq, C: PartialEq, { fn eq(&self, other: &Self) -> bool { self.color == other.color && self.alpha == other.alpha } } impl Eq for Alpha where T: Eq, C: Eq, { } impl, C2, T> FromColorUnclamped for Alpha where C1::Color: IntoColorUnclamped, { fn from_color_unclamped(other: C1) -> Self { let (color, alpha) = other.split(); Alpha { color: color.into_color_unclamped(), alpha, } } } impl WithAlpha
for Alpha { type Color = C; type WithAlpha = Self; fn with_alpha(mut self, alpha: A) -> Self::WithAlpha { self.alpha = alpha; self } fn without_alpha(self) -> Self::Color { self.color } fn split(self) -> (Self::Color, A) { (self.color, self.alpha) } } impl Deref for Alpha { type Target = C; fn deref(&self) -> &C { &self.color } } impl DerefMut for Alpha { fn deref_mut(&mut self) -> &mut C { &mut self.color } } impl Mix for Alpha where C: Mix, C::Scalar: Zero + One + num::Clamp + Arithmetics + Clone, { type Scalar = C::Scalar; #[inline] fn mix(mut self, other: Self, factor: C::Scalar) -> Self { let factor = clamp(factor, C::Scalar::zero(), C::Scalar::one()); self.color = self.color.mix(other.color, factor.clone()); self.alpha = self.alpha.clone() + factor * (other.alpha - self.alpha); self } } impl MixAssign for Alpha where C: MixAssign, C::Scalar: Zero + One + num::Clamp + Arithmetics + AddAssign + Clone, { type Scalar = C::Scalar; #[inline] fn mix_assign(&mut self, other: Self, factor: C::Scalar) { let factor = clamp(factor, C::Scalar::zero(), C::Scalar::one()); self.color.mix_assign(other.color, factor.clone()); self.alpha += factor * (other.alpha - self.alpha.clone()); } } impl Lighten for Alpha { type Scalar = C::Scalar; #[inline] fn lighten(self, factor: C::Scalar) -> Self { Alpha { color: self.color.lighten(factor), alpha: self.alpha, } } #[inline] fn lighten_fixed(self, amount: C::Scalar) -> Self { Alpha { color: self.color.lighten_fixed(amount), alpha: self.alpha, } } } impl LightenAssign for Alpha { type Scalar = C::Scalar; #[inline] fn lighten_assign(&mut self, factor: C::Scalar) { self.color.lighten_assign(factor); } #[inline] fn lighten_fixed_assign(&mut self, amount: C::Scalar) { self.color.lighten_fixed_assign(amount); } } impl GetHue for Alpha { type Hue = C::Hue; #[inline] fn get_hue(&self) -> C::Hue { self.color.get_hue() } } impl WithHue for Alpha where C: WithHue, { #[inline] fn with_hue(mut self, hue: H) -> Self { self.color = self.color.with_hue(hue); self } } impl SetHue for Alpha where C: SetHue, { #[inline] fn set_hue(&mut self, hue: H) { self.color.set_hue(hue); } } impl ShiftHue for Alpha where C: ShiftHue, { type Scalar = C::Scalar; #[inline] fn shift_hue(mut self, amount: Self::Scalar) -> Self { self.color = self.color.shift_hue(amount); self } } impl ShiftHueAssign for Alpha where C: ShiftHueAssign, { type Scalar = C::Scalar; #[inline] fn shift_hue_assign(&mut self, amount: Self::Scalar) { self.color.shift_hue_assign(amount); } } impl Saturate for Alpha { type Scalar = C::Scalar; #[inline] fn saturate(self, factor: C::Scalar) -> Self { Alpha { color: self.color.saturate(factor), alpha: self.alpha, } } #[inline] fn saturate_fixed(self, amount: C::Scalar) -> Self { Alpha { color: self.color.saturate_fixed(amount), alpha: self.alpha, } } } impl SaturateAssign for Alpha { type Scalar = C::Scalar; #[inline] fn saturate_assign(&mut self, factor: C::Scalar) { self.color.saturate_assign(factor); } #[inline] fn saturate_fixed_assign(&mut self, amount: C::Scalar) { self.color.saturate_fixed_assign(amount); } } impl IsWithinBounds for Alpha where C: IsWithinBounds, T: Stimulus + PartialCmp + IsWithinBounds, C::Mask: BitAnd, { #[inline] fn is_within_bounds(&self) -> C::Mask { self.color.is_within_bounds() & self.alpha.gt_eq(&Self::min_alpha()) & self.alpha.lt_eq(&Self::max_alpha()) } } impl Clamp for Alpha where C: Clamp, T: Stimulus + num::Clamp, { #[inline] fn clamp(self) -> Self { Alpha { color: self.color.clamp(), alpha: clamp(self.alpha, Self::min_alpha(), Self::max_alpha()), } } } impl ClampAssign for Alpha where C: ClampAssign, T: Stimulus + num::ClampAssign, { #[inline] fn clamp_assign(&mut self) { self.color.clamp_assign(); clamp_assign(&mut self.alpha, Self::min_alpha(), Self::max_alpha()); } } unsafe impl ArrayCast for Alpha::Array as ArrayExt>::Item> where C: ArrayCast, C::Array: NextArray, { type Array = ::Next; } impl HasBoolMask for Alpha where C: HasBoolMask, T: HasBoolMask, { type Mask = C::Mask; } impl Default for Alpha { fn default() -> Alpha { Alpha { color: C::default(), alpha: Self::max_alpha(), } } } #[cfg(feature = "approx")] impl AbsDiffEq for Alpha where C: AbsDiffEq, T: AbsDiffEq, T::Epsilon: Clone, { type Epsilon = T::Epsilon; fn default_epsilon() -> Self::Epsilon { T::default_epsilon() } fn abs_diff_eq(&self, other: &Self, epsilon: T::Epsilon) -> bool { self.color.abs_diff_eq(&other.color, epsilon.clone()) && self.alpha.abs_diff_eq(&other.alpha, epsilon) } } #[cfg(feature = "approx")] impl RelativeEq for Alpha where C: RelativeEq, T: RelativeEq, T::Epsilon: Clone, { fn default_max_relative() -> Self::Epsilon { T::default_max_relative() } fn relative_eq( &self, other: &Alpha, epsilon: Self::Epsilon, max_relative: Self::Epsilon, ) -> bool { self.color .relative_eq(&other.color, epsilon.clone(), max_relative.clone()) && self.alpha.relative_eq(&other.alpha, epsilon, max_relative) } } #[cfg(feature = "approx")] impl UlpsEq for Alpha where C: UlpsEq, T: UlpsEq, T::Epsilon: Clone, { fn default_max_ulps() -> u32 { T::default_max_ulps() } fn ulps_eq(&self, other: &Alpha, epsilon: Self::Epsilon, max_ulps: u32) -> bool { self.color.ulps_eq(&other.color, epsilon.clone(), max_ulps) && self.alpha.ulps_eq(&other.alpha, epsilon, max_ulps) } } impl Add for Alpha where C: Add, T: Add, { type Output = Alpha::Output>; fn add(self, other: Alpha) -> Self::Output { Alpha { color: self.color + other.color, alpha: self.alpha + other.alpha, } } } impl Add for Alpha where T: Add + Clone, C: Add, { type Output = Alpha::Output>; fn add(self, c: T) -> Self::Output { Alpha { color: self.color + c.clone(), alpha: self.alpha + c, } } } impl AddAssign for Alpha where C: AddAssign, T: AddAssign, { fn add_assign(&mut self, other: Alpha) { self.color += other.color; self.alpha += other.alpha; } } impl AddAssign for Alpha where T: AddAssign + Clone, C: AddAssign, { fn add_assign(&mut self, c: T) { self.color += c.clone(); self.alpha += c; } } impl SaturatingAdd for Alpha where C: SaturatingAdd, T: SaturatingAdd, { type Output = Alpha::Output>; fn saturating_add(self, other: Alpha) -> Self::Output { Alpha { color: self.color.saturating_add(other.color), alpha: self.alpha.saturating_add(other.alpha), } } } impl SaturatingAdd for Alpha where T: SaturatingAdd + Clone, C: SaturatingAdd, { type Output = Alpha::Output>; fn saturating_add(self, c: T) -> Self::Output { Alpha { color: self.color.saturating_add(c.clone()), alpha: self.alpha.saturating_add(c), } } } impl Sub for Alpha where C: Sub, T: Sub, { type Output = Alpha::Output>; fn sub(self, other: Alpha) -> Self::Output { Alpha { color: self.color - other.color, alpha: self.alpha - other.alpha, } } } impl Sub for Alpha where T: Sub + Clone, C: Sub, { type Output = Alpha::Output>; fn sub(self, c: T) -> Self::Output { Alpha { color: self.color - c.clone(), alpha: self.alpha - c, } } } impl SubAssign for Alpha where C: SubAssign, T: SubAssign, { fn sub_assign(&mut self, other: Alpha) { self.color -= other.color; self.alpha -= other.alpha; } } impl SubAssign for Alpha where T: SubAssign + Clone, C: SubAssign, { fn sub_assign(&mut self, c: T) { self.color -= c.clone(); self.alpha -= c; } } impl SaturatingSub for Alpha where C: SaturatingSub, T: SaturatingSub, { type Output = Alpha::Output>; fn saturating_sub(self, other: Alpha) -> Self::Output { Alpha { color: self.color.saturating_sub(other.color), alpha: self.alpha.saturating_sub(other.alpha), } } } impl SaturatingSub for Alpha where T: SaturatingSub + Clone, C: SaturatingSub, { type Output = Alpha::Output>; fn saturating_sub(self, c: T) -> Self::Output { Alpha { color: self.color.saturating_sub(c.clone()), alpha: self.alpha.saturating_sub(c), } } } impl Mul for Alpha where C: Mul, T: Mul, { type Output = Alpha::Output>; fn mul(self, other: Alpha) -> Self::Output { Alpha { color: self.color * other.color, alpha: self.alpha * other.alpha, } } } impl Mul for Alpha where T: Mul + Clone, C: Mul, { type Output = Alpha::Output>; fn mul(self, c: T) -> Self::Output { Alpha { color: self.color * c.clone(), alpha: self.alpha * c, } } } impl MulAssign for Alpha where C: MulAssign, T: MulAssign, { fn mul_assign(&mut self, other: Alpha) { self.color *= other.color; self.alpha *= other.alpha; } } impl MulAssign for Alpha where T: MulAssign + Clone, C: MulAssign, { fn mul_assign(&mut self, c: T) { self.color *= c.clone(); self.alpha *= c; } } impl Div for Alpha where C: Div, T: Div, { type Output = Alpha::Output>; fn div(self, other: Alpha) -> Self::Output { Alpha { color: self.color / other.color, alpha: self.alpha / other.alpha, } } } impl Div for Alpha where T: Div + Clone, C: Div, { type Output = Alpha::Output>; fn div(self, c: T) -> Self::Output { Alpha { color: self.color / c.clone(), alpha: self.alpha / c, } } } impl DivAssign for Alpha where C: DivAssign, T: DivAssign, { fn div_assign(&mut self, other: Alpha) { self.color /= other.color; self.alpha /= other.alpha; } } impl DivAssign for Alpha where T: DivAssign + Clone, C: DivAssign, { fn div_assign(&mut self, c: T) { self.color /= c.clone(); self.alpha /= c; } } impl_array_casts!([C, T, const N: usize] Alpha, [T; N], where Alpha: ArrayCast); impl From for Alpha { fn from(color: C) -> Alpha { Alpha { color, alpha: Self::max_alpha(), } } } impl fmt::LowerHex for Alpha where T: fmt::LowerHex, C: fmt::LowerHex, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let size = f.width().unwrap_or(::core::mem::size_of::() * 2); write!( f, "{:0width$x}{:0width$x}", self.color, self.alpha, width = size ) } } impl fmt::UpperHex for Alpha where T: fmt::UpperHex, C: fmt::UpperHex, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let size = f.width().unwrap_or(::core::mem::size_of::() * 2); write!( f, "{:0width$X}{:0width$X}", self.color, self.alpha, width = size ) } } impl Extend> for Alpha where C: Extend, A: Extend, { fn extend>>(&mut self, iter: T) { for color in iter { self.color.extend(core::iter::once(color.color)); self.alpha.extend(core::iter::once(color.alpha)); } } } impl FromIterator> for Alpha where C: Extend + FromIterator, A: Extend + Default, { fn from_iter>>(iter: T) -> Self { let mut result = Self { color: C::from_iter(None), // Default is currently a used for black, meaning it's not always what we want here. alpha: A::default(), }; for color in iter { result.color.extend(core::iter::once(color.color)); result.alpha.extend(core::iter::once(color.alpha)); } result } } /// An iterator for transparent colors. pub struct Iter { pub(crate) color: C, pub(crate) alpha: A, } impl Iterator for Iter where C: Iterator, A: Iterator, { type Item = Alpha; fn next(&mut self) -> Option { let color = self.color.next(); let alpha = self.alpha.next(); if let (Some(color), Some(alpha)) = (color, alpha) { Some(Alpha { color, alpha }) } else { None } } fn size_hint(&self) -> (usize, Option) { let hint = self.color.size_hint(); debug_assert_eq!( self.alpha.size_hint(), hint, "the color and alpha iterators have different size hints" ); hint } fn count(self) -> usize { let count = self.color.count(); debug_assert_eq!( self.alpha.count(), count, "the color and alpha iterators have different counts" ); count } } impl DoubleEndedIterator for Iter where C: DoubleEndedIterator, A: DoubleEndedIterator, { fn next_back(&mut self) -> Option { let color = self.color.next_back(); let alpha = self.alpha.next_back(); if let (Some(color), Some(alpha)) = (color, alpha) { Some(Alpha { color, alpha }) } else { None } } } impl ExactSizeIterator for Iter where C: ExactSizeIterator, A: ExactSizeIterator, { fn len(&self) -> usize { let len = self.color.len(); debug_assert_eq!( self.alpha.len(), len, "the color and alpha iterators have different lengths" ); len } } #[cfg(feature = "serializing")] impl serde::Serialize for Alpha where C: serde::Serialize, T: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.color.serialize(crate::serde::AlphaSerializer { inner: serializer, alpha: &self.alpha, }) } } #[cfg(feature = "serializing")] impl<'de, C, T> serde::Deserialize<'de> for Alpha where C: serde::Deserialize<'de>, T: serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let mut alpha: Option = None; let color = C::deserialize(crate::serde::AlphaDeserializer { inner: deserializer, alpha: &mut alpha, })?; if let Some(alpha) = alpha { Ok(Self { color, alpha }) } else { Err(serde::de::Error::missing_field("alpha")) } } } #[cfg(feature = "random")] impl Distribution> for Standard where Standard: Distribution + Distribution, { fn sample(&self, rng: &mut R) -> Alpha { Alpha { color: rng.gen(), alpha: rng.gen(), } } } /// Sample transparent colors uniformly. #[cfg(feature = "random")] pub struct UniformAlpha where T: SampleUniform, C: SampleUniform, { color: Uniform, alpha: Uniform, } #[cfg(feature = "random")] impl SampleUniform for Alpha where T: Clone + SampleUniform, C: Clone + SampleUniform, { type Sampler = UniformAlpha; } #[cfg(feature = "random")] impl UniformSampler for UniformAlpha where T: Clone + SampleUniform, C: Clone + SampleUniform, { type X = Alpha; fn new(low_b: B1, high_b: B2) -> Self where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); UniformAlpha { color: Uniform::new::(low.color, high.color), alpha: Uniform::new::<_, T>(low.alpha, high.alpha), } } fn new_inclusive(low_b: B1, high_b: B2) -> Self where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); UniformAlpha { color: Uniform::new_inclusive::(low.color, high.color), alpha: Uniform::new_inclusive::<_, T>(low.alpha, high.alpha), } } fn sample(&self, rng: &mut R) -> Alpha { Alpha { color: self.color.sample(rng), alpha: self.alpha.sample(rng), } } } #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Alpha where C: bytemuck::Zeroable, T: bytemuck::Zeroable, { } // Safety: // // It is a requirement of `ArrayCast` that the in-memory representation of `C` // is made of `T`s. Because `T` is `Pod`, `Alpha` is `Pod` as well because // no internal padding can be introduced during monomorphization. #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Alpha where T: bytemuck::Pod, C: bytemuck::Pod + ArrayCast, { } #[cfg(test)] mod test { use crate::encoding::Srgb; use crate::rgb::Rgba; #[test] fn lower_hex() { assert_eq!( format!("{:x}", Rgba::::new(171, 193, 35, 161)), "abc123a1" ); } #[test] fn lower_hex_small_numbers() { assert_eq!( format!("{:x}", Rgba::::new(1, 2, 3, 4)), "01020304" ); assert_eq!( format!("{:x}", Rgba::::new(1, 2, 3, 4)), "0001000200030004" ); assert_eq!( format!("{:x}", Rgba::::new(1, 2, 3, 4)), "00000001000000020000000300000004" ); assert_eq!( format!("{:x}", Rgba::::new(1, 2, 3, 4)), "0000000000000001000000000000000200000000000000030000000000000004" ); } #[test] fn lower_hex_custom_width() { assert_eq!( format!("{:03x}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); assert_eq!( format!("{:03x}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); assert_eq!( format!("{:03x}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); assert_eq!( format!("{:03x}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); } #[test] fn upper_hex() { assert_eq!( format!("{:X}", Rgba::::new(171, 193, 35, 161)), "ABC123A1" ); } #[test] fn upper_hex_small_numbers() { assert_eq!( format!("{:X}", Rgba::::new(1, 2, 3, 4)), "01020304" ); assert_eq!( format!("{:X}", Rgba::::new(1, 2, 3, 4)), "0001000200030004" ); assert_eq!( format!("{:X}", Rgba::::new(1, 2, 3, 4)), "00000001000000020000000300000004" ); assert_eq!( format!("{:X}", Rgba::::new(1, 2, 3, 4)), "0000000000000001000000000000000200000000000000030000000000000004" ); } #[test] fn upper_hex_custom_width() { assert_eq!( format!("{:03X}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); assert_eq!( format!("{:03X}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); assert_eq!( format!("{:03X}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); assert_eq!( format!("{:03X}", Rgba::::new(1, 2, 3, 4)), "001002003004" ); } #[test] fn check_min_max_components() { assert_eq!(Rgba::::min_alpha(), 0.0); assert_eq!(Rgba::::max_alpha(), 1.0); } #[cfg(feature = "serializing")] #[test] fn serialize() { let color = Rgba::::new(0.3, 0.8, 0.1, 0.5); assert_eq!( serde_json::to_string(&color).unwrap(), r#"{"red":0.3,"green":0.8,"blue":0.1,"alpha":0.5}"# ); assert_eq!( ron::to_string(&color).unwrap(), r#"(red:0.3,green:0.8,blue:0.1,alpha:0.5)"# ); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let color = Rgba::::new(0.3, 0.8, 0.1, 0.5); assert_eq!( serde_json::from_str::>(r#"{"alpha":0.5,"red":0.3,"green":0.8,"blue":0.1}"#) .unwrap(), color ); assert_eq!( ron::from_str::>(r#"(alpha:0.5,red:0.3,green:0.8,blue:0.1)"#).unwrap(), color ); assert_eq!( ron::from_str::>(r#"Rgb(alpha:0.5,red:0.3,green:0.8,blue:0.1)"#).unwrap(), color ); } #[cfg(feature = "serializing")] #[test] fn serde_round_trips() { let color = Rgba::::new(0.3, 0.8, 0.1, 0.5); assert_eq!( serde_json::from_str::>(&serde_json::to_string(&color).unwrap()).unwrap(), color ); assert_eq!( ron::from_str::>(&ron::to_string(&color).unwrap()).unwrap(), color ); } #[cfg(feature = "serializing")] #[test] fn serde_various_types() { macro_rules! test_roundtrip { ($value:expr $(, $ron_name:expr)?) => { let value = super::Alpha { color: $value, alpha: 0.5, }; assert_eq!( serde_json::from_str::>( &serde_json::to_string(&value).expect("json serialization") ) .expect("json deserialization"), value ); let ron_string = ron::to_string(&value).expect("ron serialization"); assert_eq!( ron::from_str::>(&ron_string) .expect("ron deserialization"), value ); $( assert_eq!( ron::from_str::>(&format!("{}{ron_string}", $ron_name)) .expect("ron deserialization"), value ); )? }; } #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Empty; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct UnitTuple(); #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Newtype(f32); #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Tuple(f32, f32); #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Struct { value: f32, } test_roundtrip!(()); test_roundtrip!(Empty, "Empty"); test_roundtrip!(UnitTuple(), "UnitTuple"); test_roundtrip!(Newtype(0.1), "Newtype"); test_roundtrip!(Tuple(0.1, 0.2), "Tuple"); test_roundtrip!(Struct { value: 0.1 }, "Struct"); } test_uniform_distribution! { Rgba { red: (0.0, 1.0), green: (0.0, 1.0), blue: (0.0, 1.0), alpha: (0.0, 1.0) }, min: Rgba::new(0.0f32, 0.0, 0.0, 0.0), max: Rgba::new(1.0, 1.0, 1.0, 1.0) } } palette-0.7.5/src/alpha.rs000064400000000000000000000121461046102023000135210ustar 00000000000000//! Types related to transparent colors. #[doc(hidden)] pub use palette_derive::WithAlpha; use crate::{num::Zero, stimulus::Stimulus}; pub use self::alpha::*; #[doc(no_inline)] pub use crate::blend::PreAlpha; // Cross-link for visibility. #[allow(clippy::module_inception)] mod alpha; /// A trait for color types that can have or be given transparency (alpha channel). /// /// `WithAlpha` is an interface for adding, removing and setting the alpha /// channel of a color type. The color type itself doesn't need to store the /// transparency value as it can be transformed into or wrapped in a type that /// has a representation of transparency. This would typically be done by /// wrapping it in an [`Alpha`](crate::Alpha) instance. /// /// # Deriving /// The trait is trivial enough to be automatically derived. If the color type /// has a field for transparency (an alpha channel), it has to be marked with /// `#[palette(alpha)]` to be taken into account. /// /// Derived without an internal alpha channel: /// /// ``` /// use palette::WithAlpha; /// /// #[derive(WithAlpha)] /// struct CustomColor { /// redness: f32, /// glow: f32, /// glitter: f32, /// } /// /// let color = CustomColor { /// redness: 0.8, /// glow: 2.5, /// glitter: 1000.0 /// }; /// let transparent = color.with_alpha(0.3); /// /// assert_eq!(transparent.alpha, 0.3); /// ``` /// /// Derived with an internal alpha channel: /// /// ``` /// use palette::WithAlpha; /// /// #[derive(WithAlpha)] /// struct CustomColor { /// redness: f32, /// glow: f32, /// glitter: f32, /// /// #[palette(alpha)] /// alpha: u8, /// } /// /// let color = CustomColor { /// redness: 0.8, /// glow: 2.5, /// glitter: 1000.0, /// alpha: 255 /// }; /// let transparent = color.with_alpha(10); /// /// assert_eq!(transparent.alpha, 10); /// ``` pub trait WithAlpha: Sized { /// The opaque color type, without any transparency. /// /// This is typically `Self`. type Color; /// The color type with transparency applied. /// /// This is typically `Alpha`. type WithAlpha: WithAlpha; /// Transforms the color into a transparent color with the provided /// alpha value. If `Self` already has a transparency, it is /// overwritten. /// /// ``` /// use palette::{Srgb, WithAlpha}; /// /// let color = Srgb::new(255u8, 0, 255); /// /// // This results in an `Alpha, f32>` /// let transparent = color.with_alpha(0.3f32); /// assert_eq!(transparent.alpha, 0.3); /// /// // This changes the transparency to 0.8 /// let transparent = transparent.with_alpha(0.8f32); /// assert_eq!(transparent.alpha, 0.8); /// ``` #[must_use] fn with_alpha(self, alpha: A) -> Self::WithAlpha; /// Removes the transparency from the color. If `Self::Color` has /// an internal transparency field, that field will be set to /// `A::max_intensity()` to make it opaque. /// /// ``` /// use palette::{Srgba, Srgb, WithAlpha}; /// /// let transparent = Srgba::new(255u8, 0, 255, 10); /// /// // This unwraps the color information from the `Alpha` wrapper /// let color = transparent.without_alpha(); /// assert_eq!(transparent.color, color); /// ``` #[must_use] fn without_alpha(self) -> Self::Color; /// Splits the color into separate color and transparency values. /// /// A color without any transparency field will return /// `A::max_intensity()` instead. If `Self::Color` has an internal /// transparency field, that field will be set to /// `A::max_intensity()` to make it opaque. /// /// ``` /// use palette::{Srgba, Srgb, WithAlpha}; /// /// let transparent = Srgba::new(255u8, 0, 255, 10); /// /// // This unwraps both the color and alpha from the `Alpha` wrapper /// let (color, alpha) = transparent.split(); /// assert_eq!(transparent.color, color); /// assert_eq!(transparent.alpha, alpha); /// ``` #[must_use] fn split(self) -> (Self::Color, A); /// Transforms the color into a fully opaque color with a transparency /// field. If `Self` already has a transparency, it is overwritten. /// /// ``` /// use palette::{Srgb, Srgba, WithAlpha}; /// /// let color = Srgb::new(255u8, 0, 255); /// /// let opaque: Srgba = color.opaque(); /// assert_eq!(opaque.alpha, 255); /// ``` #[must_use] #[inline] fn opaque(self) -> Self::WithAlpha where A: Stimulus, { self.with_alpha(A::max_intensity()) } /// Transforms the color into a fully transparent color. If `Self` /// already has a transparency, it is overwritten. /// /// ``` /// use palette::{Srgb, Srgba, WithAlpha}; /// /// let color = Srgb::new(255u8, 0, 255); /// /// let transparent: Srgba = color.transparent(); /// assert_eq!(transparent.alpha, 0); /// ``` #[must_use] #[inline] fn transparent(self) -> Self::WithAlpha where A: Zero, { self.with_alpha(A::zero()) } } palette-0.7.5/src/angle/wide.rs000064400000000000000000000031061046102023000144460ustar 00000000000000use ::wide::{f32x4, f32x8, f64x2, f64x4, CmpEq}; use super::*; macro_rules! impl_angle_wide_float { ($($ty: ident),+) => { $( impl HalfRotation for $ty { #[inline] fn half_rotation() -> Self { $ty::splat(180.0) } } impl FullRotation for $ty { #[inline] fn full_rotation() -> Self { $ty::splat(360.0) } } impl RealAngle for $ty { #[inline] fn degrees_to_radians(self) -> Self { self.to_radians() } #[inline] fn radians_to_degrees(self) -> Self { self.to_degrees() } } impl AngleEq for $ty { #[inline] fn angle_eq(&self, other: &Self) -> Self { self.normalize_unsigned_angle().cmp_eq(other.normalize_unsigned_angle()) } } impl SignedAngle for $ty { #[inline] fn normalize_signed_angle(self) -> Self { self - Round::ceil(((self + 180.0) / 360.0) - 1.0) * 360.0 } } impl UnsignedAngle for $ty { #[inline] fn normalize_unsigned_angle(self) -> Self { self - (Round::floor(self / 360.0) * 360.0) } } )+ }; } impl_angle_wide_float!(f32x4, f32x8, f64x2, f64x4); palette-0.7.5/src/angle.rs000064400000000000000000000123041046102023000135160ustar 00000000000000//! Traits for working with angular values, such as for in hues. use crate::{ bool_mask::HasBoolMask, num::{Real, Round}, }; #[cfg(feature = "wide")] mod wide; /// Represents types that can express half of a rotation (i.e. 180 degrees). pub trait HalfRotation { /// Return a value that represents half of a rotation (i.e. 180 degrees). #[must_use] fn half_rotation() -> Self; } /// Represents types that can express a full rotation (i.e. 360 degrees). pub trait FullRotation { /// Return a value that represents a full rotation (i.e. 360 degrees). #[must_use] fn full_rotation() -> Self; } /// Angle values that are real numbers and can represent both radians and /// degrees. pub trait RealAngle: Real { /// Consider `self` to be radians and convert it to degrees. #[must_use] fn radians_to_degrees(self) -> Self; /// Consider `self` to be degrees and convert it to radians. #[must_use] fn degrees_to_radians(self) -> Self; } /// Angular equality, where 0 degrees and 360 degrees are equal. pub trait AngleEq: HasBoolMask { /// Check if `self` and `other` represent the same angle on a circle. #[must_use] fn angle_eq(&self, other: &Self) -> Self::Mask; } /// Angle types that can represent the full circle using positive and negative /// values. pub trait SignedAngle { /// Normalize `self` to a range corresponding to -180 to 180 degrees. #[must_use] fn normalize_signed_angle(self) -> Self; } /// Angle types that can represent the full circle as positive values. pub trait UnsignedAngle { /// Normalize `self` to a range corresponding to 0 to 360 degrees. #[must_use] fn normalize_unsigned_angle(self) -> Self; } /// Performs value-to-value conversion between angle types. See also [`IntoAngle`]. pub trait FromAngle { /// Performs a conversion from `angle`. fn from_angle(angle: T) -> Self; } impl FromAngle for T { #[inline] fn from_angle(angle: Self) -> Self { angle } } /// Performs value-to-value conversion between angle types. See also [`IntoAngle`]. pub trait IntoAngle { /// Performs a conversion into `T`. fn into_angle(self) -> T; } impl IntoAngle for T where U: FromAngle, { #[inline] fn into_angle(self) -> U { U::from_angle(self) } } macro_rules! impl_angle_float { ($($ty: ident),+) => { $( impl HalfRotation for $ty { #[inline] fn half_rotation() -> Self { 180.0 } } impl FullRotation for $ty { #[inline] fn full_rotation() -> Self { 360.0 } } impl RealAngle for $ty { #[inline] fn degrees_to_radians(self) -> Self { self.to_radians() } #[inline] fn radians_to_degrees(self) -> Self { self.to_degrees() } } impl AngleEq for $ty { #[inline] fn angle_eq(&self, other: &Self) -> bool { self.normalize_unsigned_angle() == other.normalize_unsigned_angle() } } impl SignedAngle for $ty { #[inline] fn normalize_signed_angle(self) -> Self { self - Round::ceil(((self + 180.0) / 360.0) - 1.0) * 360.0 } } impl UnsignedAngle for $ty { #[inline] fn normalize_unsigned_angle(self) -> Self { self - (Round::floor(self / 360.0) * 360.0) } } )+ }; } macro_rules! impl_from_angle_float { ($ty: ident to $other_ty: ident) => { impl FromAngle<$other_ty> for $ty { #[inline] fn from_angle(angle: $other_ty) -> Self { angle as $ty } } }; } macro_rules! impl_from_angle_u8 { ($($float_ty: ident),*) => { $( impl FromAngle for $float_ty { #[inline] fn from_angle(angle: u8) -> Self { (angle as $float_ty / 256.0) * Self::full_rotation() } } impl FromAngle<$float_ty> for u8 { #[inline] fn from_angle(angle: $float_ty) -> Self { let normalized = angle.normalize_unsigned_angle() / $float_ty::full_rotation(); let rounded = normalized.round(); if rounded > 255.5 { 0 } else { rounded as u8 } } } )* }; } impl_angle_float!(f32, f64); impl_from_angle_float!(f32 to f64); impl_from_angle_float!(f64 to f32); impl_from_angle_u8!(f32, f64); impl HalfRotation for u8 { #[inline] fn half_rotation() -> Self { 128 } } impl AngleEq for u8 { #[inline] fn angle_eq(&self, other: &Self) -> bool { self == other } } impl UnsignedAngle for u8 { #[inline] fn normalize_unsigned_angle(self) -> Self { self } } palette-0.7.5/src/blend/blend.rs000064400000000000000000000342251046102023000146060ustar 00000000000000use crate::{ bool_mask::LazySelect, cast::{self, ArrayCast}, num::{Abs, Arithmetics, Clamp, MinMax, One, PartialCmp, Real, Sqrt, Zero}, stimulus::{Stimulus, StimulusColor}, Alpha, }; use super::{blend_alpha, PreAlpha, Premultiply}; /// A trait for different ways of mixing colors together. /// /// This implements the classic separable blend modes, [as described by /// W3C](https://www.w3.org/TR/compositing-1/#blending). /// /// _Note: The default implementations of the blend modes are meant for color /// components in the range [0.0, 1.0] and may otherwise produce strange /// results._ pub trait Blend { /// Multiply `self` with `other`. This uses the alpha component to regulate /// the effect, so it's not just plain component wise multiplication. #[must_use] fn multiply(self, other: Self) -> Self; /// Make a color which is at least as light as `self` or `other`. #[must_use] fn screen(self, other: Self) -> Self; /// Multiply `self` or `other` if other is dark, or screen them if `other` /// is light. This results in an S curve. #[must_use] fn overlay(self, other: Self) -> Self; /// Return the darkest parts of `self` and `other`. #[must_use] fn darken(self, other: Self) -> Self; /// Return the lightest parts of `self` and `other`. #[must_use] fn lighten(self, other: Self) -> Self; /// Lighten `other` to reflect `self`. Results in `other` if `self` is /// black. #[must_use] fn dodge(self, other: Self) -> Self; /// Darken `other` to reflect `self`. Results in `other` if `self` is /// white. #[must_use] fn burn(self, other: Self) -> Self; /// Multiply `self` or `other` if other is dark, or screen them if `self` /// is light. This is similar to `overlay`, but depends on `self` instead /// of `other`. #[must_use] fn hard_light(self, other: Self) -> Self; /// Lighten `other` if `self` is light, or darken `other` as if it's burned /// if `self` is dark. The effect is increased if the components of `self` /// is further from 0.5. #[must_use] fn soft_light(self, other: Self) -> Self; /// Return the absolute difference between `self` and `other`. It's /// basically `abs(self - other)`, but regulated by the alpha component. #[must_use] fn difference(self, other: Self) -> Self; /// Similar to `difference`, but appears to result in a lower contrast. /// `other` is inverted if `self` is white, and preserved if `self` is /// black. #[must_use] fn exclusion(self, other: Self) -> Self; } impl Blend for PreAlpha where C: Premultiply + StimulusColor + ArrayCast + Clone, T: Real + Zero + One + MinMax + Clamp + Sqrt + Abs + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { #[inline] fn multiply(self, other: Self) -> Self { blend_separable(self.into(), other.into(), multiply_blend) } #[inline] fn screen(self, other: Self) -> Self { blend_separable(self.into(), other.into(), screen_blend) } #[inline] fn overlay(self, other: Self) -> Self { blend_separable(self.into(), other.into(), overlay_blend) } #[inline] fn darken(self, other: Self) -> Self { blend_separable(self.into(), other.into(), darken_blend) } #[inline] fn lighten(self, other: Self) -> Self { blend_separable(self.into(), other.into(), lighten_blend) } #[inline] fn dodge(self, other: Self) -> Self { blend_separable(self.into(), other.into(), dodge_blend) } #[inline] fn burn(self, other: Self) -> Self { blend_separable(self.into(), other.into(), burn_blend) } #[inline] fn hard_light(self, other: Self) -> Self { blend_separable(self.into(), other.into(), hard_light_blend) } #[inline] fn soft_light(self, other: Self) -> Self { blend_separable(self.into(), other.into(), soft_light_blend) } #[inline] fn difference(self, other: Self) -> Self { blend_separable(self.into(), other.into(), difference_blend) } #[inline] fn exclusion(self, other: Self) -> Self { blend_separable(self.into(), other.into(), exclusion_blend) } } impl Blend for C where C: Premultiply + StimulusColor + ArrayCast + Clone, T: Real + Zero + One + MinMax + Clamp + Sqrt + Abs + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { fn multiply(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, multiply_blend) .unpremultiply() .color } fn screen(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, screen_blend) .unpremultiply() .color } fn overlay(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, overlay_blend) .unpremultiply() .color } fn darken(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, darken_blend) .unpremultiply() .color } fn lighten(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, lighten_blend) .unpremultiply() .color } fn dodge(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, dodge_blend).unpremultiply().color } fn burn(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, burn_blend).unpremultiply().color } fn hard_light(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, hard_light_blend) .unpremultiply() .color } fn soft_light(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, soft_light_blend) .unpremultiply() .color } fn difference(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, difference_blend) .unpremultiply() .color } fn exclusion(self, other: Self) -> Self { let src = BlendInput::new_opaque(self); let dst = BlendInput::new_opaque(other); blend_separable(src, dst, exclusion_blend) .unpremultiply() .color } } impl Blend for Alpha where C: Premultiply + StimulusColor + ArrayCast + Clone, T: Real + Zero + One + MinMax + Clamp + Sqrt + Abs + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { #[inline] fn multiply(self, other: Self) -> Self { blend_separable(self.into(), other.into(), multiply_blend).unpremultiply() } #[inline] fn screen(self, other: Self) -> Self { blend_separable(self.into(), other.into(), screen_blend).unpremultiply() } #[inline] fn overlay(self, other: Self) -> Self { blend_separable(self.into(), other.into(), overlay_blend).unpremultiply() } #[inline] fn darken(self, other: Self) -> Self { blend_separable(self.into(), other.into(), darken_blend).unpremultiply() } #[inline] fn lighten(self, other: Self) -> Self { blend_separable(self.into(), other.into(), lighten_blend).unpremultiply() } #[inline] fn dodge(self, other: Self) -> Self { blend_separable(self.into(), other.into(), dodge_blend).unpremultiply() } #[inline] fn burn(self, other: Self) -> Self { blend_separable(self.into(), other.into(), burn_blend).unpremultiply() } #[inline] fn hard_light(self, other: Self) -> Self { blend_separable(self.into(), other.into(), hard_light_blend).unpremultiply() } #[inline] fn soft_light(self, other: Self) -> Self { blend_separable(self.into(), other.into(), soft_light_blend).unpremultiply() } #[inline] fn difference(self, other: Self) -> Self { blend_separable(self.into(), other.into(), difference_blend).unpremultiply() } #[inline] fn exclusion(self, other: Self) -> Self { blend_separable(self.into(), other.into(), exclusion_blend).unpremultiply() } } struct BlendInput { color: C, color_pre: C, alpha: C::Scalar, } impl BlendInput where C: Premultiply + Clone, { fn new_opaque(color: C) -> Self { BlendInput { color_pre: color.clone(), color, alpha: C::Scalar::max_intensity(), } } } impl From> for BlendInput where C: Premultiply + Clone, { fn from(color: Alpha) -> Self { let color_pre: PreAlpha = color.color.clone().premultiply(color.alpha); BlendInput { color: color.color, color_pre: color_pre.color, alpha: color_pre.alpha, } } } impl From> for BlendInput where C: Premultiply + Clone, { fn from(color: PreAlpha) -> Self { let color_pre = color.color.clone(); let (color, alpha) = C::unpremultiply(color); BlendInput { color, color_pre, alpha, } } } #[inline] fn multiply_blend(src: T, dst: T) -> T where T: Arithmetics, { src * dst } #[inline] fn screen_blend(src: T, dst: T) -> T where T: Arithmetics + Clone, { src.clone() + &dst - src * dst } #[inline] fn overlay_blend(src: T, dst: T) -> T where T: One + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { hard_light_blend(dst, src) } #[inline] fn darken_blend(src: T, dst: T) -> T where T: MinMax, { src.min(dst) } #[inline] fn lighten_blend(src: T, dst: T) -> T where T: MinMax, { src.max(dst) } #[inline] fn dodge_blend(src: T, dst: T) -> T where T: One + Zero + MinMax + Arithmetics + PartialCmp, T::Mask: LazySelect, { // The original algorithm assumes values within [0, 1], but we check for // values outside it and clamp. lazy_select! { if dst.lt_eq(&T::zero()) => T::zero(), if src.gt_eq(&T::one()) => T::one(), else => T::one().min(dst / (T::one() - src)), } } #[inline] fn burn_blend(src: T, dst: T) -> T where T: One + Zero + MinMax + Arithmetics + PartialCmp, T::Mask: LazySelect, { // The original algorithm assumes values within [0, 1], but we check for // values outside it and clamp. lazy_select! { if dst.gt_eq(&T::one()) => T::one(), if src.lt_eq(&T::zero()) => T::zero(), else => T::one() - T::one().min((T::one() - dst) / src), } } #[inline] fn hard_light_blend(src: T, dst: T) -> T where T: One + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { let two_src = src.clone() + src; lazy_select! { if two_src.lt_eq(&T::one()) => multiply_blend(two_src.clone(), dst.clone()), else => screen_blend(two_src.clone() - T::one(), dst.clone()), } } #[inline] fn soft_light_blend(src: T, dst: T) -> T where T: Real + One + Arithmetics + Sqrt + PartialCmp + Clone, T::Mask: LazySelect, { let four = T::from_f64(4.0); let twelve = T::from_f64(12.0); let four_dst = dst.clone() * &four; let two_src = src.clone() + &src; let d_dst = lazy_select! { if four_dst.lt_eq(&T::one()) => { let sixteen_dst = four_dst * &four; ((sixteen_dst - twelve) * &dst + four) * &dst }, else => dst.clone().sqrt(), }; lazy_select! { if two_src.lt_eq(&T::one()) => { dst.clone() - (T::one() - &two_src) * &dst * (T::one() - &dst) }, else => dst.clone() + (two_src.clone() - T::one()) * (d_dst - &dst), } } #[inline] fn difference_blend(src: T, dst: T) -> T where T: Arithmetics + Abs, { (dst - src).abs() } #[inline] fn exclusion_blend(src: T, dst: T) -> T where T: Arithmetics + Clone, { dst.clone() + &src - (dst.clone() + dst) * src } #[inline] fn blend_separable( src: BlendInput, mut dst: BlendInput, mut blend: F, ) -> PreAlpha where C: ArrayCast + Premultiply, T: One + Zero + Arithmetics + Clamp + Clone, F: FnMut(T, T) -> T, { let src_alpha = src.alpha.clone(); let zipped_input = zip_input(src, dst.color, &mut dst.color_pre, dst.alpha.clone()); for (src, src_pre, src_alpha, dst, dst_pre, dst_alpha) in zipped_input { *dst_pre = src_pre * (T::one() - &dst_alpha) + blend(src, dst) * &src_alpha * dst_alpha + (T::one() - src_alpha) * &*dst_pre; } PreAlpha { color: dst.color_pre, alpha: blend_alpha(src_alpha, dst.alpha), } } fn zip_input<'a, C, T, const N: usize>( src: BlendInput, dst: C, dst_pre: &'a mut C, dst_alpha: T, ) -> impl Iterator where C: ArrayCast + Premultiply, T: 'a + Clone, { let src_alpha = src.alpha; IntoIterator::into_iter(cast::into_array(src.color)) .zip(cast::into_array(src.color_pre)) .zip(cast::into_array(dst)) .zip(cast::into_array_mut(dst_pre)) .map(move |(((src_color, src_pre), dst_color), dst_pre)| { ( src_color, src_pre, src_alpha.clone(), dst_color, dst_pre, dst_alpha.clone(), ) }) } palette-0.7.5/src/blend/blend_with.rs000064400000000000000000000046111046102023000156350ustar 00000000000000use crate::{stimulus::Stimulus, Alpha}; use super::{BlendFunction, PreAlpha, Premultiply}; /// Blending with a custom blend function. /// /// This is a convenience trait that makes it possible to use [`BlendFunction`] /// via a method on the source color. This makes custom blending more similar to /// how the [`Compose`][super::Compose] and [`Blend`][super::Blend] are used, /// including automatic pre-multiplication. pub trait BlendWith { /// The base color type of `Self`. type Color: Premultiply; /// Blend self, as the source color, with `destination`, using /// `blend_function`. Anything that implements [`BlendFunction`] is /// acceptable, including functions and closures. /// /// ``` /// use palette::{LinSrgb, LinSrgba}; /// use palette::blend::{BlendWith, PreAlpha}; /// /// type PreRgba = PreAlpha>; /// /// fn blend_mode(a: PreRgba, b: PreRgba) -> PreRgba { /// PreAlpha { /// color: LinSrgb::new(a.red * b.green, a.green * b.blue, a.blue * b.red), /// alpha: a.alpha * b.alpha, /// } /// } /// /// let a = LinSrgba::new(0.2, 0.5, 0.1, 0.8); /// let b = LinSrgba::new(0.6, 0.3, 0.5, 0.1); /// let c = a.blend_with(b, blend_mode); /// ``` #[must_use] fn blend_with(self, destination: Self, blend_function: F) -> Self where F: BlendFunction; } impl BlendWith for PreAlpha where C: Premultiply, { type Color = C; #[inline] fn blend_with(self, other: Self, blend_function: F) -> Self where F: BlendFunction, { blend_function.apply_to(self, other) } } impl BlendWith for Alpha where C: Premultiply, { type Color = C; fn blend_with(self, destination: Self, blend_function: F) -> Self where F: crate::blend::BlendFunction, { self.premultiply() .blend_with(destination.premultiply(), blend_function) .unpremultiply() } } impl BlendWith for C where C: Premultiply, C::Scalar: Stimulus, { type Color = C; fn blend_with(self, other: Self, blend_function: F) -> Self where F: BlendFunction, { PreAlpha::new_opaque(self) .blend_with(PreAlpha::new_opaque(other), blend_function) .unpremultiply() .color } } palette-0.7.5/src/blend/compose.rs000064400000000000000000000131521046102023000151630ustar 00000000000000use crate::{ cast::ArrayCast, clamp, num::{Arithmetics, Clamp, One, Real, Zero}, stimulus::Stimulus, Alpha, }; use super::{blend_alpha, zip_colors, PreAlpha, Premultiply}; /// The Porter Duff composition operators, [as described by /// W3C](https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators). /// /// This set of operators exclude the variants where source and destination are /// swapped, as well as the "clear", "copy" and "destination" operators. Those /// can easily be achieved using other means. pub trait Compose { /// Place `self` over `other`. This is the good old common alpha composition /// equation. #[must_use] fn over(self, other: Self) -> Self; /// Results in the parts of `self` that overlaps the visible parts of /// `other`. #[must_use] fn inside(self, other: Self) -> Self; /// Results in the parts of `self` that lies outside the visible parts of /// `other`. #[must_use] fn outside(self, other: Self) -> Self; /// Place `self` over only the visible parts of `other`. #[must_use] fn atop(self, other: Self) -> Self; /// Results in either `self` or `other`, where they do not overlap. #[must_use] fn xor(self, other: Self) -> Self; /// Add `self` and `other`. This uses the alpha component to regulate the /// effect, so it's not just plain component wise addition. #[must_use] fn plus(self, other: Self) -> Self; } impl Compose for PreAlpha where C: ArrayCast + Premultiply, T: Real + Zero + One + Arithmetics + Clamp + Clone, { #[inline] fn over(self, mut other: Self) -> Self { for (src, dst) in zip_colors(self.color, &mut other.color) { *dst = src + (T::one() - &self.alpha) * &*dst; } other.alpha = blend_alpha(self.alpha, other.alpha); other } #[inline] fn inside(self, mut other: Self) -> Self { for (src, dst) in zip_colors(self.color, &mut other.color) { *dst = src * &other.alpha; } other.alpha = clamp(self.alpha * other.alpha, T::zero(), T::one()); other } #[inline] fn outside(self, mut other: Self) -> Self { for (src, dst) in zip_colors(self.color, &mut other.color) { *dst = src * (T::one() - &other.alpha); } other.alpha = clamp(self.alpha * (T::one() - other.alpha), T::zero(), T::one()); other } #[inline] fn atop(self, mut other: Self) -> Self { for (src, dst) in zip_colors(self.color, &mut other.color) { *dst = src * &other.alpha + (T::one() - &self.alpha) * &*dst; } other.alpha = clamp(other.alpha, T::zero(), T::one()); other } #[inline] fn xor(self, mut other: Self) -> Self { let two = || T::one() + T::one(); for (src, dst) in zip_colors(self.color, &mut other.color) { *dst = src * (T::one() - &other.alpha) + (T::one() - &self.alpha) * &*dst; } other.alpha = clamp( self.alpha.clone() + &other.alpha - two() * self.alpha * other.alpha, T::zero(), T::one(), ); other } #[inline] fn plus(self, mut other: Self) -> Self { for (src, dst) in zip_colors(self.color, &mut other.color) { *dst = src + &*dst; } other.alpha = clamp(self.alpha + other.alpha, T::zero(), T::one()); other } } impl Compose for Alpha where C: Premultiply, PreAlpha: Compose, { #[inline] fn over(self, other: Self) -> Self { self.premultiply().over(other.premultiply()).unpremultiply() } #[inline] fn inside(self, other: Self) -> Self { self.premultiply() .inside(other.premultiply()) .unpremultiply() } #[inline] fn outside(self, other: Self) -> Self { self.premultiply() .outside(other.premultiply()) .unpremultiply() } #[inline] fn atop(self, other: Self) -> Self { self.premultiply().atop(other.premultiply()).unpremultiply() } #[inline] fn xor(self, other: Self) -> Self { self.premultiply().xor(other.premultiply()).unpremultiply() } #[inline] fn plus(self, other: Self) -> Self { self.premultiply().plus(other.premultiply()).unpremultiply() } } impl Compose for C where C: Premultiply, C::Scalar: Stimulus, PreAlpha: Compose, { #[inline] fn over(self, other: Self) -> Self { PreAlpha::new_opaque(self) .over(PreAlpha::new_opaque(other)) .unpremultiply() .color } #[inline] fn inside(self, other: Self) -> Self { PreAlpha::new_opaque(self) .inside(PreAlpha::new_opaque(other)) .unpremultiply() .color } #[inline] fn outside(self, other: Self) -> Self { PreAlpha::new_opaque(self) .outside(PreAlpha::new_opaque(other)) .unpremultiply() .color } #[inline] fn atop(self, other: Self) -> Self { PreAlpha::new_opaque(self) .atop(PreAlpha::new_opaque(other)) .unpremultiply() .color } #[inline] fn xor(self, other: Self) -> Self { PreAlpha::new_opaque(self) .xor(PreAlpha::new_opaque(other)) .unpremultiply() .color } #[inline] fn plus(self, other: Self) -> Self { PreAlpha::new_opaque(self) .plus(PreAlpha::new_opaque(other)) .unpremultiply() .color } } palette-0.7.5/src/blend/equations.rs000064400000000000000000000201731046102023000155270ustar 00000000000000use core::ops::{Mul, Sub}; use crate::{ blend::{BlendFunction, PreAlpha}, cast::ArrayCast, num::{Arithmetics, IsValidDivisor, MinMax, One, Real, Sqrt, Zero}, }; use super::{zip_colors, Premultiply}; /// A pair of blending equations and corresponding parameters. /// /// The `Equations` type is similar to how blending works in OpenGL, where a /// blend function has can be written as `e(sp * S, dp * D)`. `e` is the /// equation (like `s + d`), `sp` and `dp` are the source and destination /// parameters, and `S` and `D` are the source and destination colors. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Equations { /// The equation for the color components. pub color_equation: Equation, /// The equation for the alpha component. pub alpha_equation: Equation, /// The parameters for the color components. pub color_parameters: Parameters, /// The parameters for the alpha component. pub alpha_parameters: Parameters, } impl Equations { /// Create a pair of blending equations, where all the parameters are /// `One`. pub fn from_equations(color: Equation, alpha: Equation) -> Equations { Equations { color_equation: color, alpha_equation: alpha, color_parameters: Parameters { source: Parameter::One, destination: Parameter::One, }, alpha_parameters: Parameters { source: Parameter::One, destination: Parameter::One, }, } } /// Create a pair of additive blending equations with the provided /// parameters. pub fn from_parameters(source: Parameter, destination: Parameter) -> Equations { Equations { color_equation: Equation::Add, alpha_equation: Equation::Add, color_parameters: Parameters { source, destination, }, alpha_parameters: Parameters { source, destination, }, } } } impl BlendFunction for Equations where C: Clone + Premultiply + Mul + Mul + ArrayCast, PreAlpha: ArrayCast, S: Real + One + Zero + MinMax + Sqrt + IsValidDivisor + Arithmetics + Clone, { fn apply_to(self, source: PreAlpha, destination: PreAlpha) -> PreAlpha { let (src_color, mut dst_color) = if matches!(self.color_equation, Equation::Min | Equation::Max) { (source.color.clone(), destination.color.clone()) } else { let col_src_param = self .color_parameters .source .apply_to(source.clone(), destination.clone()); let col_dst_param = self .color_parameters .destination .apply_to(source.clone(), destination.clone()); ( col_src_param.mul_color(source.color.clone()), col_dst_param.mul_color(destination.color.clone()), ) }; let (src_alpha, dst_alpha) = if matches!(self.alpha_equation, Equation::Min | Equation::Max) { (source.alpha, destination.alpha) } else { let alpha_src_param = self .alpha_parameters .source .apply_to(source.clone(), destination.clone()); let alpha_dst_param = self .alpha_parameters .destination .apply_to(source.clone(), destination.clone()); ( alpha_src_param.mul_constant(source.alpha), alpha_dst_param.mul_constant(destination.alpha), ) }; let color_op = match self.color_equation { Equation::Add => |src, dst| src + dst, Equation::Subtract => |src, dst| src - dst, Equation::ReverseSubtract => |src, dst| dst - src, Equation::Min => MinMax::min, Equation::Max => MinMax::max, }; let alpha_op = match self.alpha_equation { Equation::Add => |src, dst| src + dst, Equation::Subtract => |src, dst| src - dst, Equation::ReverseSubtract => |src, dst| dst - src, Equation::Min => MinMax::min, Equation::Max => MinMax::max, }; for (src, dst) in zip_colors(src_color, &mut dst_color) { *dst = color_op(src, dst.clone()); } PreAlpha { color: dst_color, alpha: alpha_op(src_alpha, dst_alpha), } } } /// A blending equation. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Equation { /// Add the source and destination, according to `sp * S + dp * D`. Add, /// Subtract the destination from the source, according to `sp * S - dp * /// D`. Subtract, /// Subtract the source from the destination, according to `dp * D - sp * /// S`. ReverseSubtract, /// Create a color where each component is the smallest of each of the /// source and destination components. A.k.a. component wise min. The /// parameters are ignored. Min, /// Create a color where each component is the largest of each of the /// source and destination components. A.k.a. component wise max. The /// parameters are ignored. Max, } /// A pair of source and destination parameters. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Parameters { /// The source parameter. pub source: Parameter, /// The destination parameter. pub destination: Parameter, } /// A blending parameter. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Parameter { /// A simple 1. One, /// A simple 0. Zero, /// The source color, or alpha. SourceColor, /// One minus the source color, or alpha. OneMinusSourceColor, /// The destination color, or alpha. DestinationColor, /// One minus the destination color, or alpha. OneMinusDestinationColor, /// The source alpha. SourceAlpha, /// One minus the source alpha. OneMinusSourceAlpha, /// The destination alpha. DestinationAlpha, /// One minus the destination alpha. OneMinusDestinationAlpha, } impl Parameter { fn apply_to( &self, source: PreAlpha, destination: PreAlpha, ) -> ParamOut where C: Premultiply, PreAlpha: ArrayCast, T: Real + One + Zero + Sub, { match *self { Parameter::One => ParamOut::Constant(T::one()), Parameter::Zero => ParamOut::Constant(T::zero()), Parameter::SourceColor => ParamOut::Color(source), Parameter::OneMinusSourceColor => { ParamOut::Color(<[T; N]>::from(source).map(|a| T::one() - a).into()) } Parameter::DestinationColor => ParamOut::Color(destination), Parameter::OneMinusDestinationColor => { ParamOut::Color(<[T; N]>::from(destination).map(|a| T::one() - a).into()) } Parameter::SourceAlpha => ParamOut::Constant(source.alpha), Parameter::OneMinusSourceAlpha => ParamOut::Constant(T::one() - source.alpha), Parameter::DestinationAlpha => ParamOut::Constant(destination.alpha), Parameter::OneMinusDestinationAlpha => ParamOut::Constant(T::one() - destination.alpha), } } } enum ParamOut { Color(PreAlpha), Constant(C::Scalar), } impl ParamOut where C: Mul + Mul + Premultiply, T: Mul + Clone, { fn mul_constant(self, other: T) -> T { match self { ParamOut::Color(c) => c.alpha * other, ParamOut::Constant(c) => c * other, } } fn mul_color(self, other: C) -> C { match self { ParamOut::Color(c) => other * c.color, ParamOut::Constant(c) => other * c, } } } palette-0.7.5/src/blend/pre_alpha.rs000064400000000000000000000303611046102023000154520ustar 00000000000000use core::ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; #[cfg(feature = "approx")] use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use crate::{ cast::ArrayCast, clamp, num::{self, Arithmetics, One, Real, Zero}, stimulus::Stimulus, Alpha, ArrayExt, Mix, MixAssign, NextArray, }; use super::Premultiply; /// Premultiplied alpha wrapper. /// /// Premultiplied, or alpha masked, or associated alpha colors have had their /// component values multiplied with their alpha value. They are commonly used /// in composition algorithms and as output from computer generated graphics. It /// may also be preferred when interpolating between colors and in other image /// manipulation operations, such as blurring or resizing images. /// /// ``` /// use palette::{LinSrgb, LinSrgba}; /// use palette::blend::{Blend, PreAlpha}; /// /// let a = PreAlpha::from(LinSrgba::new(0.4, 0.5, 0.5, 0.3)); /// let b = PreAlpha::from(LinSrgba::new(0.3, 0.8, 0.4, 0.4)); /// let c = PreAlpha::from(LinSrgba::new(0.7, 0.1, 0.8, 0.8)); /// /// let res: LinSrgba = a.screen(b).overlay(c).into(); /// ``` /// /// Note that converting to and from premultiplied alpha will cause the alpha /// component to be clamped to [0.0, 1.0], and fully transparent colors will /// become black. #[derive(Clone, Copy, Debug)] #[repr(C)] pub struct PreAlpha { /// The premultiplied color components (`original.color * original.alpha`). pub color: C, /// The transparency component. 0.0 is fully transparent and 1.0 is fully /// opaque. pub alpha: C::Scalar, } impl PreAlpha where C: Premultiply, { /// Alpha mask `color` with `alpha`. pub fn new(color: C, alpha: C::Scalar) -> Self { color.premultiply(alpha) } /// Create an opaque alpha masked color. pub fn new_opaque(color: C) -> Self where C::Scalar: Stimulus, { Self { color, alpha: C::Scalar::max_intensity(), } } /// Alpha unmask the color. pub fn unpremultiply(self) -> Alpha { let (color, alpha) = C::unpremultiply(self); Alpha { color, alpha } } } impl PartialEq for PreAlpha where C: PartialEq + Premultiply, C::Scalar: PartialEq, { fn eq(&self, other: &Self) -> bool { self.color == other.color && self.alpha == other.alpha } } impl Eq for PreAlpha where C: Eq + Premultiply, C::Scalar: Eq, { } impl From> for PreAlpha where C: Premultiply, { #[inline] fn from(color: Alpha) -> Self { color.color.premultiply(color.alpha) } } impl From> for Alpha where C: Premultiply, { #[inline] fn from(color: PreAlpha) -> Self { let (color, alpha) = C::unpremultiply(color); Alpha { color, alpha } } } impl From for PreAlpha where C: Premultiply, C::Scalar: Stimulus, { fn from(color: C) -> Self { color.premultiply(C::Scalar::max_intensity()) } } impl Mix for PreAlpha where C: Mix + Premultiply, T: Real + Zero + One + num::Clamp + Arithmetics + Clone, { type Scalar = T; #[inline] fn mix(mut self, other: Self, factor: T) -> Self { let factor = clamp(factor, T::zero(), T::one()); self.color = self.color.mix(other.color, factor.clone()); self.alpha = self.alpha.clone() + factor * (other.alpha - self.alpha); self } } impl MixAssign for PreAlpha where C: MixAssign + Premultiply, T: Real + Zero + One + num::Clamp + Arithmetics + AddAssign + Clone, { type Scalar = T; #[inline] fn mix_assign(&mut self, other: Self, factor: T) { let factor = clamp(factor, T::zero(), T::one()); self.color.mix_assign(other.color, factor.clone()); self.alpha += factor * (other.alpha - self.alpha.clone()); } } unsafe impl ArrayCast for PreAlpha where C: ArrayCast + Premultiply, C::Array: NextArray + ArrayExt, { type Array = ::Next; } impl Default for PreAlpha where C: Default + Premultiply, C::Scalar: Stimulus, { fn default() -> PreAlpha { PreAlpha { color: C::default(), alpha: C::Scalar::max_intensity(), } } } #[cfg(feature = "approx")] impl AbsDiffEq for PreAlpha where C: AbsDiffEq + Premultiply, T: AbsDiffEq, T::Epsilon: Clone, { type Epsilon = T::Epsilon; fn default_epsilon() -> Self::Epsilon { T::default_epsilon() } fn abs_diff_eq(&self, other: &PreAlpha, epsilon: Self::Epsilon) -> bool { self.color.abs_diff_eq(&other.color, epsilon.clone()) && self.alpha.abs_diff_eq(&other.alpha, epsilon) } } #[cfg(feature = "approx")] impl RelativeEq for PreAlpha where C: RelativeEq + Premultiply, T: RelativeEq, T::Epsilon: Clone, { fn default_max_relative() -> Self::Epsilon { T::default_max_relative() } fn relative_eq( &self, other: &PreAlpha, epsilon: Self::Epsilon, max_relative: Self::Epsilon, ) -> bool { self.color .relative_eq(&other.color, epsilon.clone(), max_relative.clone()) && self.alpha.relative_eq(&other.alpha, epsilon, max_relative) } } #[cfg(feature = "approx")] impl UlpsEq for PreAlpha where C: UlpsEq + Premultiply, T: UlpsEq, T::Epsilon: Clone, { fn default_max_ulps() -> u32 { T::default_max_ulps() } fn ulps_eq(&self, other: &PreAlpha, epsilon: Self::Epsilon, max_ulps: u32) -> bool { self.color.ulps_eq(&other.color, epsilon.clone(), max_ulps) && self.alpha.ulps_eq(&other.alpha, epsilon, max_ulps) } } macro_rules! impl_binop { ( $op_trait:ident::$op_trait_fn:ident, $op_assign_trait:ident::$op_assign_trait_fn:ident ) => { impl $op_trait for PreAlpha where C: $op_trait + Premultiply, C::Scalar: $op_trait, { type Output = PreAlpha; fn $op_trait_fn(self, other: PreAlpha) -> Self::Output { PreAlpha { color: self.color.$op_trait_fn(other.color), alpha: self.alpha.$op_trait_fn(other.alpha), } } } impl $op_assign_trait for PreAlpha where C: $op_assign_trait + Premultiply, C::Scalar: $op_assign_trait + Real, { fn $op_assign_trait_fn(&mut self, other: PreAlpha) { self.color.$op_assign_trait_fn(other.color); self.alpha.$op_assign_trait_fn(other.alpha); } } }; } impl_binop!(Add::add, AddAssign::add_assign); impl_binop!(Sub::sub, SubAssign::sub_assign); impl_binop!(Mul::mul, MulAssign::mul_assign); impl_binop!(Div::div, DivAssign::div_assign); macro_rules! impl_scalar_binop { ( $op_trait:ident::$op_trait_fn:ident, $op_assign_trait:ident::$op_assign_trait_fn:ident, [$($ty:ident),+] ) => { $( impl $op_trait<$ty> for PreAlpha where C: $op_trait<$ty, Output = C> + Premultiply, { type Output = PreAlpha; fn $op_trait_fn(self, c: $ty) -> Self::Output { PreAlpha { color: self.color.$op_trait_fn(c), alpha: self.alpha.$op_trait_fn(c), } } } // // Disabled as work-around for https://github.com/Ogeon/palette/issues/283 // // Blocked by https://github.com/rust-lang/rust/issues/80542 // impl $op_trait> for $ty // where // C: Premultiply, // $ty: $op_trait<$ty, Output = $ty> + $op_trait, // { // type Output = PreAlpha; // // fn $op_trait_fn(self, color: PreAlpha) -> Self::Output { // PreAlpha { // color: $op_trait::::$op_trait_fn(self, color.color), // alpha: $op_trait::<$ty>::$op_trait_fn(self, color.alpha), // } // } // } impl $op_assign_trait<$ty> for PreAlpha where C: $op_assign_trait<$ty> + Premultiply, { fn $op_assign_trait_fn(&mut self, c: $ty) { self.color.$op_assign_trait_fn(c); self.alpha.$op_assign_trait_fn(c); } } )+ }; } impl_scalar_binop!(Add::add, AddAssign::add_assign, [f32, f64]); impl_scalar_binop!(Sub::sub, SubAssign::sub_assign, [f32, f64]); impl_scalar_binop!(Mul::mul, MulAssign::mul_assign, [f32, f64]); impl_scalar_binop!(Div::div, DivAssign::div_assign, [f32, f64]); impl_array_casts!([C: Premultiply, const N: usize] PreAlpha, [C::Scalar; N], where PreAlpha: ArrayCast); impl Deref for PreAlpha { type Target = C; fn deref(&self) -> &C { &self.color } } impl DerefMut for PreAlpha { fn deref_mut(&mut self) -> &mut C { &mut self.color } } #[cfg(feature = "serializing")] impl serde::Serialize for PreAlpha where C: Premultiply + serde::Serialize, C::Scalar: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.color.serialize(crate::serde::AlphaSerializer { inner: serializer, alpha: &self.alpha, }) } } #[cfg(feature = "serializing")] impl<'de, C> serde::Deserialize<'de> for PreAlpha where C: Premultiply + serde::Deserialize<'de>, C::Scalar: serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let mut alpha: Option = None; let color = C::deserialize(crate::serde::AlphaDeserializer { inner: deserializer, alpha: &mut alpha, })?; if let Some(alpha) = alpha { Ok(Self { color, alpha }) } else { Err(serde::de::Error::missing_field("alpha")) } } } #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for PreAlpha where C: bytemuck::Zeroable + Premultiply, C::Scalar: bytemuck::Zeroable, { } // Safety: // // See `Alpha`'s implementation of `Pod`. #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for PreAlpha where C: bytemuck::Pod + ArrayCast + Premultiply, C::Scalar: bytemuck::Pod, { } #[cfg(test)] #[cfg(feature = "serializing")] mod test { use super::PreAlpha; use crate::LinSrgb; #[cfg(feature = "serializing")] #[test] fn serialize() { let color = PreAlpha { color: LinSrgb::new(0.3, 0.8, 0.1), alpha: 0.5, }; assert_eq!( serde_json::to_string(&color).unwrap(), r#"{"red":0.3,"green":0.8,"blue":0.1,"alpha":0.5}"# ); assert_eq!( ron::to_string(&color).unwrap(), r#"(red:0.3,green:0.8,blue:0.1,alpha:0.5)"# ); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let color = PreAlpha { color: LinSrgb::new(0.3, 0.8, 0.1), alpha: 0.5, }; assert_eq!( serde_json::from_str::>( r#"{"alpha":0.5,"red":0.3,"green":0.8,"blue":0.1}"# ) .unwrap(), color ); assert_eq!( ron::from_str::>(r#"(alpha:0.5,red:0.3,green:0.8,blue:0.1)"#) .unwrap(), color ); assert_eq!( ron::from_str::>(r#"Rgb(alpha:0.5,red:0.3,green:0.8,blue:0.1)"#) .unwrap(), color ); } } palette-0.7.5/src/blend/test.rs000064400000000000000000000251101046102023000144720ustar 00000000000000#![cfg(feature = "approx")] use crate::{ blend::{Blend, BlendWith, Compose}, LinSrgb, LinSrgba, }; #[test] fn blend_color() { let a = LinSrgb::new(1.0, 0.0, 0.0); let b = LinSrgb::new(0.0, 0.0, 1.0); let c: LinSrgb = a.blend_with(b, |a, b| a + b); assert_relative_eq!(LinSrgb::new(0.5, 0.0, 0.5), c); } #[test] fn blend_alpha_color() { let a = LinSrgba::new(1.0, 0.0, 0.0, 0.2); let b = LinSrgba::new(0.0, 0.0, 1.0, 0.2); let c: LinSrgba = a.blend_with(b, |a, b| a + b); assert_relative_eq!(LinSrgba::new(0.2 / 0.4, 0.0, 0.2 / 0.4, 0.4), c); } #[test] fn over() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.5, 0.0, 0.3), a.over(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.over(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 0.5); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!( LinSrgba::new(0.5 / 0.75, 0.05 / 0.75, 0.15 / 0.75, 0.75), a.over(b) ); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.over(b)); } #[test] fn inside() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.5, 0.0, 0.3), a.inside(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 0.5), a.inside(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.0, 0.0, 0.0, 0.0), a.inside(b)); } #[test] fn outside() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.0, 0.0, 0.0), a.outside(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 0.5), a.outside(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.outside(b)); } #[test] fn atop() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.5, 0.0, 0.3), a.atop(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 0.5), a.atop(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 0.5); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.1, 0.15, 0.5), a.atop(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.0, 0.0, 0.0, 0.0), a.atop(b)); } #[test] fn xor() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.0, 0.0, 0.0), a.xor(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 0.5), a.xor(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 0.5); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.1, 0.15, 0.5), a.xor(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.xor(b)); } #[test] fn plus() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(1.5, 0.2, 0.3), a.plus(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(1.0, 0.1, 0.3, 1.0), a.plus(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 0.5); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.1, 0.15, 1.0), a.plus(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.plus(b)); } #[test] fn multiply() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(0.5, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.25, 0.0, 0.0), a.multiply(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(0.5, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.375, 0.0, 0.15, 1.0), a.multiply(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(0.5, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.multiply(b)); } #[test] fn screen() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(0.5, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.75, 0.2, 0.3), a.screen(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(0.5, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.625, 0.1, 0.3, 1.0), a.screen(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(0.5, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.screen(b)); } #[test] fn overlay() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(0.5, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.5, 0.0, 0.0), a.overlay(b)); let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(1.0, 0.0, 0.0), a.overlay(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.0, 0.15, 1.0), a.overlay(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.overlay(b)); } #[test] fn darken() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.5, 0.0, 0.0), a.darken(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.15, 1.0), a.darken(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 0.5); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!( LinSrgba::new(0.5 / 0.75, 0.05 / 0.75, 0.075 / 0.75, 0.75), a.darken(b) ); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.darken(b)); } #[test] fn lighten() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(1.0, 0.2, 0.3), a.lighten(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.1, 0.3, 1.0), a.lighten(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 0.5); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!( LinSrgba::new(0.625 / 0.75, 0.1 / 0.75, 0.15 / 0.75, 0.75), a.lighten(b) ); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.lighten(b)); } #[test] fn dodge() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(1.0, 0.2, 0.0), a.dodge(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.1, 0.15, 1.0), a.dodge(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.dodge(b)); } #[test] fn burn() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(1.0, 0.0, 0.0), a.burn(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.0, 0.15, 1.0), a.burn(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.burn(b)); } #[test] fn hard_light() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(1.0, 0.0, 0.0), a.hard_light(b)); let a = LinSrgb::new(1.0, 0.2, 0.0); let b = LinSrgb::new(0.5, 0.0, 0.3); assert_relative_eq!(LinSrgb::new(1.0, 0.0, 0.0), a.hard_light(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.0, 0.15, 1.0), a.hard_light(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.hard_light(b)); } #[test] fn soft_light() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(1.0, 0.04, 0.0), a.soft_light(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.75, 0.02, 0.15, 1.0), a.soft_light(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.soft_light(b)); } #[test] fn difference() { let a = LinSrgb::new(0.5, 0.0, 0.3); let b = LinSrgb::new(1.0, 0.2, 0.0); assert_relative_eq!(LinSrgb::new(0.5, 0.2, 0.3), a.difference(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.1, 0.3, 1.0), a.difference(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.difference(b)); } #[test] fn exclusion() { let a = LinSrgb::new(1.0, 0.5, 0.0); let b = LinSrgb::new(0.8, 0.4, 0.3); assert_relative_eq!(LinSrgb::new(0.2, 0.5, 0.3), a.exclusion(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.5); assert_relative_eq!(LinSrgba::new(0.5, 0.1, 0.3, 1.0), a.difference(b)); let a = LinSrgba::new(0.5, 0.0, 0.3, 1.0); let b = LinSrgba::new(1.0, 0.2, 0.0, 0.0); assert_relative_eq!(LinSrgba::new(0.5, 0.0, 0.3, 1.0), a.difference(b)); } palette-0.7.5/src/blend.rs000064400000000000000000000063701046102023000135220ustar 00000000000000//! Color blending and blending equations. //! //! Palette offers both OpenGL style blending equations, as well as most of the //! SVG composition operators (also common in photo manipulation software). The //! composition operators are all implemented in the [`Compose`] and [`Blend`] //! traits, and ready to use with any appropriate color type: //! //! ``` //! use palette::{blend::Blend, LinSrgba}; //! //! let a = LinSrgba::new(0.2, 0.5, 0.1, 0.8); //! let b = LinSrgba::new(0.6, 0.3, 0.5, 0.1); //! let c = a.overlay(b); //! ``` //! //! Blending equations can be defined using the //! [`Equations`](crate::blend::Equations) type, which is then passed to the //! `blend` function, from the `Blend` trait: //! //! ``` //! use palette::LinSrgba; //! use palette::blend::{BlendWith, Equations, Parameter}; //! //! let blend_mode = Equations::from_parameters( //! Parameter::SourceAlpha, //! Parameter::OneMinusSourceAlpha //! ); //! //! let a = LinSrgba::new(0.2, 0.5, 0.1, 0.8); //! let b = LinSrgba::new(0.6, 0.3, 0.5, 0.1); //! let c = a.blend_with(b, blend_mode); //! ``` //! //! Note that blending will use [premultiplied alpha](crate::blend::PreAlpha), //! which may result in loss of some color information in some cases. One such //! case is that a completely transparent resultant color will become black. use crate::{ cast::{self, ArrayCast}, clamp, num::{Arithmetics, Clamp, One, Real, Zero}, stimulus::Stimulus, }; pub use self::{ blend::Blend, blend_with::BlendWith, compose::Compose, equations::{Equation, Equations, Parameter, Parameters}, pre_alpha::PreAlpha, }; #[allow(clippy::module_inception)] mod blend; mod blend_with; mod compose; mod equations; mod pre_alpha; #[cfg(test)] mod test; /// A trait for custom blend functions. pub trait BlendFunction where C: Premultiply, { /// Apply this blend function to a pair of colors. #[must_use] fn apply_to(self, source: PreAlpha, destination: PreAlpha) -> PreAlpha; } impl BlendFunction for F where C: Premultiply, F: FnOnce(PreAlpha, PreAlpha) -> PreAlpha, { #[inline] fn apply_to(self, source: PreAlpha, destination: PreAlpha) -> PreAlpha { (self)(source, destination) } } /// Alpha masking and unmasking. pub trait Premultiply: Sized { /// The color's component type. type Scalar: Real + Stimulus; /// Alpha mask the color. /// /// This is done by multiplying the color's component by `alpha`. #[must_use] fn premultiply(self, alpha: Self::Scalar) -> PreAlpha; /// Alpha unmask the color, resulting in a color and transparency pair. /// /// This is done by dividing the masked color's component by `alpha`, or /// returning a black color if `alpha` is `0`. #[must_use] fn unpremultiply(premultiplied: PreAlpha) -> (Self, Self::Scalar); } fn blend_alpha(src: T, dst: T) -> T where T: Zero + One + Arithmetics + Clamp + Clone, { clamp(src.clone() + &dst - src * dst, T::zero(), T::one()) } fn zip_colors<'a, C, T, const N: usize>( src: C, dst: &'a mut C, ) -> impl Iterator where C: ArrayCast, T: 'a, { IntoIterator::into_iter(cast::into_array(src)).zip(cast::into_array_mut(dst)) } palette-0.7.5/src/bool_mask/wide.rs000064400000000000000000000047411046102023000153340ustar 00000000000000use wide::{f32x4, f32x8, f64x2, f64x4}; use super::{BoolMask, HasBoolMask, LazySelect, Select}; macro_rules! impl_wide_bool_mask { ($($ty: ident: ($scalar: ident, $uint: ident)),+) => { $( impl BoolMask for $ty { #[inline] fn from_bool(value: bool) -> Self { $ty::splat(if value { $scalar::from_bits($uint::MAX) } else { 0.0 }) } #[inline] fn is_true(&self) -> bool { self.all() } #[inline] fn is_false(&self) -> bool { self.none() } } impl HasBoolMask for $ty { type Mask = Self; } impl Select for $ty { #[inline] fn select(self, a: Self, b: Self) -> Self { self.blend(a, b) } } impl LazySelect for $ty { #[inline] fn lazy_select(self, a: A, b: B) -> Self where A: FnOnce() -> Self, B: FnOnce() -> Self, { let a = a(); let b = b(); self.select(a, b) } } )+ }; } impl_wide_bool_mask!( f32x4: (f32, u32), f32x8: (f32, u32), f64x2: (f64, u64), f64x4: (f64, u64) ); #[cfg(test)] mod test { use wide::{f32x4, f32x8, f64x2, f64x4}; use crate::bool_mask::BoolMask; #[test] fn from_true() { assert!(f32x4::from_bool(true).is_true()); assert!(!f32x4::from_bool(true).is_false()); assert!(f32x8::from_bool(true).is_true()); assert!(!f32x8::from_bool(true).is_false()); assert!(f64x2::from_bool(true).is_true()); assert!(!f64x2::from_bool(true).is_false()); assert!(f64x4::from_bool(true).is_true()); assert!(!f64x4::from_bool(true).is_false()); } #[test] fn from_false() { assert!(f32x4::from_bool(false).is_false()); assert!(!f32x4::from_bool(false).is_true()); assert!(f32x8::from_bool(false).is_false()); assert!(!f32x8::from_bool(false).is_true()); assert!(f64x2::from_bool(false).is_false()); assert!(!f64x2::from_bool(false).is_true()); assert!(f64x4::from_bool(false).is_false()); assert!(!f64x4::from_bool(false).is_true()); } } palette-0.7.5/src/bool_mask.rs000064400000000000000000000074021046102023000144010ustar 00000000000000//! Traits for abstracting over Boolean types. //! //! These traits are mainly useful for allowing SIMD values, where bit masks are //! typically used instead of `bool`. use core::ops::{BitAnd, BitOr, BitXor, Not}; #[cfg(feature = "wide")] mod wide; /// Associates a Boolean type to the implementing type. /// /// This is primarily used in traits and functions that can accept SIMD values /// and return a Boolean result. SIMD values use masks to select different values for /// each lane and `HasBoolMask::Mask` can be used to know which type that mask /// has. pub trait HasBoolMask { /// The mask type to use for selecting `Self` values. type Mask: BoolMask; } impl HasBoolMask for &'_ T where T: HasBoolMask, { type Mask = T::Mask; } impl HasBoolMask for &'_ mut T where T: HasBoolMask, { type Mask = T::Mask; } impl HasBoolMask for [T] where T: HasBoolMask, { type Mask = T::Mask; } impl HasBoolMask for [T; N] where T: HasBoolMask, { type Mask = T::Mask; } macro_rules! impl_has_bool_mask { ($($ty:ident),+) => { $( impl HasBoolMask for $ty { type Mask = bool; } )+ }; } impl_has_bool_mask!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); /// Basic methods for boolean masks. pub trait BoolMask { /// Create a new mask where each lane is set to `value`. #[must_use] fn from_bool(value: bool) -> Self; /// Checks if all lanes in the mask are `true`. #[must_use] fn is_true(&self) -> bool; /// Checks if all lanes in the mask are `false`. #[must_use] fn is_false(&self) -> bool; } impl BoolMask for bool { #[inline] fn from_bool(value: bool) -> Self { value } #[inline] fn is_true(&self) -> bool { *self } #[inline] fn is_false(&self) -> bool { !*self } } /// Makes a mask bale to select between two values. pub trait Select where T: HasBoolMask, { /// Select lanes from `a` when corresponding lanes in `self` are `true`, and /// select from `b` when `false`. #[must_use] fn select(self, a: T, b: T) -> T; } impl Select for bool where T: HasBoolMask, { #[inline(always)] fn select(self, a: T, b: T) -> T { if self { a } else { b } } } /// Like [`Select`], but can avoid evaluating the input. pub trait LazySelect: Select where T: HasBoolMask, { /// Select lanes from the output of `a` when corresponding lanes in `self` /// are `true`, and select from the output of `b` when `false`. May avoid /// evaluating either option if it's not selected. #[must_use] fn lazy_select(self, a: A, b: B) -> T where A: FnOnce() -> T, B: FnOnce() -> T; } impl LazySelect for bool where T: HasBoolMask, { #[inline(always)] fn lazy_select(self, a: A, b: B) -> T where A: FnOnce() -> T, B: FnOnce() -> T, { if self { a() } else { b() } } } /// A helper trait that collects bit traits under one name. pub trait BitOps: Sized + BitAnd + BitOr + BitXor + Not + for<'a> BitAnd<&'a Self, Output = Self> + for<'a> BitOr<&'a Self, Output = Self> + for<'a> BitXor<&'a Self, Output = Self> { } impl BitOps for T where T: Sized + BitAnd + BitOr + BitXor + Not + for<'a> BitAnd<&'a Self, Output = Self> + for<'a> BitOr<&'a Self, Output = Self> + for<'a> BitXor<&'a Self, Output = Self> { } palette-0.7.5/src/cast/array.rs000064400000000000000000001377461046102023000145220ustar 00000000000000use core::mem::{transmute_copy, ManuallyDrop}; #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::{boxed::Box, vec::Vec}; pub use palette_derive::ArrayCast; use crate::ArrayExt; /// Marker trait for types that can be represented as a fixed size array. /// /// A type that implements this trait is assumed to have the exact same memory /// layout and representation as a fixed size array. This implies a couple of /// useful properties: /// /// * Casting between `T` and `T::Array` is free and will (or should) be /// optimized away. /// * `[T]` can be cast to and from `[T::Array]`, which can be cast to and from /// `[U]` where `T::Array = [U; N]` and the length is a multiple of `N`. /// /// This allows a number of common and useful optimizations, including casting /// buffers and reusing memory. It does however come with some strict /// requirements and the recommendation is to use `#[derive(ArrayCast)]` which /// checks for them automatically. /// /// # Deriving /// /// `ArrayCast` can be automatically derived. The only requirements are that the /// type is a `struct`, that it has a `#[repr(C)]` or `#[repr(transparent)]` /// attribute, and that all of its fields have the same types. It stays on the /// conservative side and will show an error if any of those requirements are /// not fulfilled. If some fields have different types, but the same memory /// layout, or are zero-sized, they can be marked with attributes to show that /// their types are safe to use. /// /// ## Field Attributes /// /// * `#[palette_unsafe_same_layout_as = "SomeType"]`: Mark the field as having /// the same memory layout as `SomeType`. /// /// **Safety:** corrupt data and undefined behavior may occur if this is not /// true! /// /// * `#[palette_unsafe_zero_sized]`: Mark the field as being zero-sized, and /// thus not taking up any memory space. This means that it can be ignored. /// /// **Safety:** corrupt data and undefined behavior may occur if this is not /// true! /// /// ## Examples /// /// Basic use: /// /// ```rust /// use palette::cast::{self, ArrayCast}; /// /// #[derive(PartialEq, Debug, ArrayCast)] /// #[repr(C)] /// struct MyCmyk { /// cyan: f32, /// magenta: f32, /// yellow: f32, /// key: f32, /// } /// /// let buffer = [0.1, 0.2, 0.3, 0.4]; /// let color: MyCmyk = cast::from_array(buffer); /// /// assert_eq!( /// color, /// MyCmyk { /// cyan: 0.1, /// magenta: 0.2, /// yellow: 0.3, /// key: 0.4, /// } /// ); /// ``` /// /// Heterogenous field types: /// /// ```rust /// use std::marker::PhantomData; /// /// use palette::{cast::{self, ArrayCast}, encoding::Srgb, RgbHue}; /// /// #[derive(PartialEq, Debug, ArrayCast)] /// #[repr(C)] /// struct MyCoolColor { /// #[palette(unsafe_zero_sized)] /// standard: PhantomData, /// // RgbHue is a wrapper with `#[repr(C)]`, so it can safely /// // be converted straight from `f32`. /// #[palette(unsafe_same_layout_as = "f32")] /// hue: RgbHue, /// lumen: f32, /// chroma: f32, /// } /// /// let buffer = [172.0, 100.0, 0.3]; /// let color: MyCoolColor = cast::from_array(buffer); /// /// assert_eq!( /// color, /// MyCoolColor { /// hue: 172.0.into(), /// lumen: 100.0, /// chroma: 0.3, /// standard: PhantomData, /// } /// ); /// ``` /// /// ## Safety /// /// * The type must be inhabited (eg: no /// [Infallible](std::convert::Infallible)). /// * The type must allow any values in the array items (eg: either no /// requirements or some ability to recover from invalid values). /// * The type must be homogeneous (eg: all fields have the same type, or are /// wrappers that implement `ArrayCast` with the same field type, or are zero /// sized). /// * The length of `Array` must be the sum of the number of color component /// fields in the type and in any possible compound fields. /// * The type must be `repr(C)` or `repr(transparent)`. /// * The type must have the same size and alignment as `Self::Array`. /// /// Note also that the type is assumed to not implement `Drop`. This will /// rarely, if ever, be an issue. The requirements above ensures that the /// underlying field types stay the same and will be dropped. /// /// For example: /// /// * `Srgb` can be cast to `[T; 3]` because it has three non-zero sized /// fields of type `T`. /// * `Alpha, T>` can be cast to `[T; 4]`, that is `3 + 1` items, /// because it's the sum of the three items from `Srgb` and the one extra /// `alpha` field. /// * `Alpha, U>` is not allowed because `T` and `U` are different /// types. pub unsafe trait ArrayCast: Sized { /// The output type of a cast to an array. type Array: ArrayExt; } /// Cast from a color type to an array. /// /// ``` /// use palette::{cast, Srgb}; /// /// let color = Srgb::new(23u8, 198, 76); /// assert_eq!(cast::into_array(color), [23, 198, 76]); /// ``` /// /// It's also possible to use `From` and `Into` when casting built-in types: /// /// ``` /// use palette::Srgb; /// /// let color = Srgb::new(23u8, 198, 76); /// /// // Colors implement `Into`: /// let array1: [_; 3] = color.into(); /// /// // Arrays implement `From`: /// let array2 = <[_; 3]>::from(color); /// ``` #[inline] pub fn into_array(color: T) -> T::Array where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); // Safety: The requirements of implementing `ArrayCast`, as well as the size // assert, ensures that transmuting `T` into `T::Array` is safe. unsafe { transmute_copy(&ManuallyDrop::new(color)) } } /// Cast from an array to a color type. /// /// ``` /// use palette::{cast, Srgb}; /// /// let array = [23, 198, 76]; /// assert_eq!(cast::from_array::>(array), Srgb::new(23, 198, 76)); /// ``` /// /// It's also possible to use `From` and `Into` when casting built-in types: /// /// ``` /// use palette::Srgb; /// /// let array = [23, 198, 76]; /// /// // Arrays implement `Into`: /// let color1: Srgb = array.into(); /// /// // Colors implement `From`: /// let color2 = Srgb::from(array); /// ``` #[inline] pub fn from_array(array: T::Array) -> T where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); // Safety: The requirements of implementing `ArrayCast`, as well as the size // assert, ensures that transmuting `T::Array` into `T` is safe. unsafe { transmute_copy(&ManuallyDrop::new(array)) } } /// Cast from a color type reference to an array reference. /// /// ``` /// use palette::{cast, Srgb}; /// /// let color = Srgb::new(23u8, 198, 76); /// assert_eq!(cast::into_array_ref(&color), &[23, 198, 76]); /// ``` /// /// It's also possible to use `From`, `Into` and `AsRef` when casting built-in /// types: /// /// ``` /// use palette::Srgb; /// /// let color = Srgb::new(23u8, 198, 76); /// /// // Colors implement `AsRef`: /// let array1: &[_; 3] = color.as_ref(); /// /// // Color references implement `Into`: /// let array2: &[_; 3] = (&color).into(); // /// // Array references implement `From`: /// let array3 = <&[_; 3]>::from(&color); /// ``` #[inline] pub fn into_array_ref(value: &T) -> &T::Array where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let value: *const T = value; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. unsafe { &*value.cast::() } } /// Cast from an array reference to a color type reference. /// /// ``` /// use palette::{cast, Srgb}; /// /// let array = [23, 198, 76]; /// assert_eq!(cast::from_array_ref::>(&array), &Srgb::new(23, 198, 76)); /// ``` /// /// It's also possible to use `From`, `Into` and `AsRef` when casting built-in /// types: /// /// ``` /// use palette::Srgb; /// /// let array = [23, 198, 76]; /// /// // Arrays implement `AsRef`: /// let color1: &Srgb = array.as_ref(); /// /// // Array references implement `Into`: /// let color2: &Srgb = (&array).into(); /// /// // Color references implement `From`: /// let color3 = <&Srgb>::from(&array); /// ``` #[inline] pub fn from_array_ref(value: &T::Array) -> &T where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let value: *const T::Array = value; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T::Array` as `T` is safe. unsafe { &*value.cast::() } } /// Cast from a mutable color type reference to a mutable array reference. /// /// ``` /// use palette::{cast, Srgb}; /// /// let mut color = Srgb::new(23u8, 198, 76); /// assert_eq!(cast::into_array_mut(&mut color), &mut [23, 198, 76]); /// ``` /// /// It's also possible to use `From`, `Into` and `AsMut` when casting built-in /// types: /// /// ``` /// use palette::Srgb; /// /// let mut color = Srgb::new(23u8, 198, 76); /// /// // Colors implement `AsMut`: /// let array1: &mut [_; 3] = color.as_mut(); /// /// // Color references implement `Into`: /// let array2: &mut [_; 3] = (&mut color).into(); // /// // Array references implement `From`: /// let array3 = <&mut [_; 3]>::from(&mut color); /// ``` #[inline] pub fn into_array_mut(value: &mut T) -> &mut T::Array where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let value: *mut T = value; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. unsafe { &mut *value.cast::() } } /// Cast from a mutable array reference to a mutable color type reference. /// /// ``` /// use palette::{cast, Srgb}; /// /// let mut array = [23, 198, 76]; /// assert_eq!(cast::from_array_mut::>(&mut array), &mut Srgb::new(23, 198, 76)); /// ``` /// /// It's also possible to use `From`, `Into` and `AsMut` when casting built-in /// types: /// /// ``` /// use palette::Srgb; /// /// let mut array = [23, 198, 76]; /// /// // Arrays implement `AsMut`: /// let color1: &mut Srgb = array.as_mut(); /// /// // Array references implement `Into`: /// let color2: &mut Srgb = (&mut array).into(); /// /// // Color references implement `From`: /// let color3 = <&mut Srgb>::from(&mut array); /// ``` #[inline] pub fn from_array_mut(value: &mut T::Array) -> &mut T where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let value: *mut T::Array = value; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T::Array` as `T` is safe. unsafe { &mut *value.cast::() } } /// Cast from an array of colors to an array of arrays. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!(cast::into_array_array(colors), [[64, 139, 10], [93, 18, 214]]) /// ``` #[inline] pub fn into_array_array(values: [T; N]) -> [T::Array; N] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // The length and memory layout of the array is the same because the size is // the same. unsafe { transmute_copy(&ManuallyDrop::new(values)) } } /// Cast from an array of colors to an array of color components. /// /// ## Panics /// /// It's unfortunately not able to infer the length of the component array, /// until generic const expressions are stabilized. In the meantime, it's going /// to panic if `M` isn't `N * T::Array::LENGTH`. A future version will remove /// the `M` parameter and make the mismatch a compiler error. /// /// No `try_*` alternative is provided, since the array size can't be changed /// during runtime. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!(cast::into_component_array(colors), [64, 139, 10, 93, 18, 214]) /// ``` /// /// It panics when the array lengths don't match up: /// /// ```should_panic /// use palette::{cast, Srgb}; /// /// let colors = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!(cast::into_component_array(colors), [64, 139, 10]) // Too short /// ``` #[inline] pub fn into_component_array( values: [T; N], ) -> [::Item; M] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // This check can be replaced with `[::Item; N * // T::Array::LENGTH]` when generic const expressions are stabilized. assert_eq!( N * T::Array::LENGTH, M, "expected the output array to have length {}, but its length is {}", N * T::Array::LENGTH, M ); assert_eq!( core::mem::size_of::<[T; N]>(), core::mem::size_of::<[::Item; M]>() ); assert_eq!( core::mem::align_of::<[T; N]>(), core::mem::align_of::<[::Item; M]>() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // The sizes and memory layout of the arrays are also asserted to be the // same. unsafe { transmute_copy(&ManuallyDrop::new(values)) } } /// Cast from an array of arrays to an array of colors. /// /// ``` /// use palette::{cast, Srgb}; /// /// let arrays = [[64, 139, 10], [93, 18, 214]]; /// assert_eq!( /// cast::from_array_array::, 2>(arrays), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ) /// ``` #[inline] pub fn from_array_array(values: [T::Array; N]) -> [T; N] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T::Array` as `T` is safe. // The length and memory layout of the array is the same because the size is // the same. unsafe { transmute_copy(&ManuallyDrop::new(values)) } } /// Cast from an array of color components to an array of colors. /// /// ## Panics /// /// The cast will panic if the length of the input array is not a multiple of /// the color's array length. This is unfortunately unavoidable until generic /// const expressions are stabilized. /// /// No `try_*` alternative is provided, since the array size can't be changed /// during runtime. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = [64, 139, 10, 93, 18, 214]; /// assert_eq!( /// cast::from_component_array::, 6, 2>(components), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast, Srgb}; /// /// let components = [64, 139, 10, 93, 18]; // Not a multiple of 3 /// assert_eq!( /// cast::from_component_array::, 5, 2>(components), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// ``` #[inline] pub fn from_component_array( values: [::Item; N], ) -> [T; M] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // These checks can be replaced with `[::Item; N / // T::Array::LENGTH]` and a compile time check for `values.len() % // T::Array::LENGTH == 0` when generic const expressions are stabilized. assert_eq!( N % T::Array::LENGTH, 0, "expected the array length ({}) to be divisible by {}", N, T::Array::LENGTH ); assert_eq!( N / T::Array::LENGTH, M, "expected the output array to have length {}, but its length is {}", N / T::Array::LENGTH, M ); assert_eq!( core::mem::size_of::<[::Item; N]>(), core::mem::size_of::<[T; M]>() ); assert_eq!( core::mem::align_of::<[::Item; N]>(), core::mem::align_of::<[T; M]>() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // The sizes and memory layout of the arrays are also asserted to be the // same. unsafe { transmute_copy(&ManuallyDrop::new(values)) } } /// Cast from a slice of colors to a slice of arrays. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!(cast::into_array_slice(colors), &[[64, 139, 10], [93, 18, 214]]) /// ``` #[inline] pub fn into_array_slice(values: &[T]) -> &[T::Array] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts(values.as_ptr().cast::(), values.len()) } } /// Cast from a slice of colors to a slice of color components. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!(cast::into_component_slice(colors), &[64, 139, 10, 93, 18, 214]) /// ``` #[inline] pub fn into_component_slice(values: &[T]) -> &[::Item] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let length = values.len() * T::Array::LENGTH; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `[T]` as `[T::Array::Item]` // is safe. The length is multiplied by the array length. unsafe { core::slice::from_raw_parts( values.as_ptr().cast::<::Item>(), length, ) } } /// Cast from a slice of arrays to a slice of colors. /// /// ``` /// use palette::{cast, Srgb}; /// /// let arrays = &[[64, 139, 10], [93, 18, 214]]; /// assert_eq!( /// cast::from_array_slice::>(arrays), /// &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ) /// ``` #[inline] pub fn from_array_slice(values: &[T::Array]) -> &[T] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T::Array` as `T` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts(values.as_ptr().cast::(), values.len()) } } /// The same as [`try_from_component_slice`] but panics on error. /// /// ## Panics /// /// The cast will panic if the length of the input slice is not a multiple of /// the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = &[64, 139, 10, 93, 18, 214]; /// assert_eq!( /// cast::from_component_slice::>(components), /// &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ) /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast, Srgb}; /// /// let components = &[64, 139, 10, 93, 18, 214, 0, 123]; // Not a multiple of 3 /// cast::from_component_slice::>(components); /// ``` #[inline] pub fn from_component_slice(values: &[::Item]) -> &[T] where T: ArrayCast, { try_from_component_slice(values).unwrap() } /// Cast from a slice of color components to a slice of colors. /// /// ## Errors /// /// The cast will return an error if the length of the input slice is not a /// multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = &[64, 139, 10, 93, 18, 214]; /// assert_eq!( /// cast::try_from_component_slice::>(components), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_ref()) /// ) /// ``` /// /// This produces an error: /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = &[64, 139, 10, 93, 18]; // Not a multiple of 3 /// assert!(cast::try_from_component_slice::>(components).is_err()); /// ``` #[inline] pub fn try_from_component_slice( values: &[::Item], ) -> Result<&[T], SliceCastError> where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); if values.len() % T::Array::LENGTH != 0 { return Err(SliceCastError); } let length = values.len() / T::Array::LENGTH; let raw = values.as_ptr().cast::(); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. unsafe { Ok(core::slice::from_raw_parts(raw, length)) } } /// Cast from a mutable slice of colors to a mutable slice of arrays. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!( /// cast::into_array_slice_mut(colors), /// &mut [[64, 139, 10], [93, 18, 214]] /// ) /// ``` #[inline] pub fn into_array_slice_mut(values: &mut [T]) -> &mut [T::Array] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts_mut(values.as_mut_ptr().cast::(), values.len()) } } /// Cast from a slice of colors to a slice of color components. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!( /// cast::into_component_slice_mut(colors), /// &mut [64, 139, 10, 93, 18, 214] /// ) /// ``` #[inline] pub fn into_component_slice_mut(values: &mut [T]) -> &mut [::Item] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let length = values.len() * T::Array::LENGTH; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `[T]` as `[T::Array::Item]` // is safe. The length is multiplied by the array length. unsafe { core::slice::from_raw_parts_mut( values.as_mut_ptr().cast::<::Item>(), length, ) } } /// Cast from a mutable slice of arrays to a mutable slice of colors. /// /// ``` /// use palette::{cast, Srgb}; /// /// let arrays = &mut [[64, 139, 10], [93, 18, 214]]; /// assert_eq!( /// cast::from_array_slice_mut::>(arrays), /// &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ) /// ``` #[inline] pub fn from_array_slice_mut(values: &mut [T::Array]) -> &mut [T] where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T::Array` as `T` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts_mut(values.as_mut_ptr().cast::(), values.len()) } } /// The same as [`try_from_component_slice_mut`] but panics on error. /// /// ## Panics /// /// The cast will panic if the length of the input slice is not a multiple of /// the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = &mut [64, 139, 10, 93, 18, 214]; /// assert_eq!( /// cast::from_component_slice_mut::>(components), /// &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ) /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast, Srgb}; /// /// let components = &mut [64, 139, 10, 93, 18, 214, 0, 123]; // Not a multiple of 3 /// cast::from_component_slice_mut::>(components); /// ``` #[inline] pub fn from_component_slice_mut(values: &mut [::Item]) -> &mut [T] where T: ArrayCast, { try_from_component_slice_mut(values).unwrap() } /// Cast from a mutable slice of color components to a slice of colors. /// /// ## Errors /// /// The cast will return an error if the length of the input slice is not a /// multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = &mut [64, 139, 10, 93, 18, 214]; /// assert_eq!( /// cast::try_from_component_slice_mut::>(components), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_mut()) /// ) /// ``` /// /// This produces an error: /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = &mut [64, 139, 10, 93, 18]; // Not a multiple of 3 /// assert!(cast::try_from_component_slice_mut::>(components).is_err()); /// ``` #[inline] pub fn try_from_component_slice_mut( values: &mut [::Item], ) -> Result<&mut [T], SliceCastError> where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); if values.len() % T::Array::LENGTH != 0 { return Err(SliceCastError); } let length = values.len() / T::Array::LENGTH; let raw = values.as_mut_ptr().cast::(); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. unsafe { Ok(core::slice::from_raw_parts_mut(raw, length)) } } /// Cast from a boxed color type to a boxed array. /// /// ``` /// use palette::{cast, Srgb}; /// /// let color = Box::new(Srgb::new(23u8, 198, 76)); /// assert_eq!(cast::into_array_box(color), Box::new([23, 198, 76])); /// ``` /// /// It's also possible to use `From` and `Into` when casting built-in types: /// /// ``` /// use palette::Srgb; /// /// // Boxed colors implement `Into`: /// let color1 = Box::new(Srgb::new(23u8, 198, 76)); /// let array1: Box<[_; 3]> = color1.into(); /// /// // Boxed arrays implement `From`: /// let color2 = Box::new(Srgb::new(23u8, 198, 76)); /// let array2 = >::from(color2); /// ``` #[cfg(feature = "alloc")] #[inline] pub fn into_array_box(value: Box) -> Box where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let raw = Box::into_raw(value); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. unsafe { Box::from_raw(raw.cast::()) } } /// Cast from a boxed array to a boxed color type. /// /// ``` /// use palette::{cast, Srgb}; /// /// let array = Box::new([23, 198, 76]); /// assert_eq!(cast::from_array_box::>(array), Box::new(Srgb::new(23, 198, 76))); /// ``` /// /// It's also possible to use `From` and `Into` when casting built-in types: /// /// ``` /// use palette::Srgb; /// /// /// // Boxed arrays implement `Into`: /// let array1 = Box::new([23, 198, 76]); /// let color1: Box> = array1.into(); /// /// // Boxed colors implement `From`: /// let array2 = Box::new([23, 198, 76]); /// let color2 = >>::from(array2); /// ``` #[cfg(feature = "alloc")] #[inline] pub fn from_array_box(value: Box) -> Box where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let raw = Box::into_raw(value); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T::Array` as `T` is safe. unsafe { Box::from_raw(raw.cast::()) } } /// Cast from a boxed slice of colors to a boxed slice of arrays. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].into_boxed_slice(); /// assert_eq!( /// cast::into_array_slice_box(colors), /// vec![[64, 139, 10], [93, 18, 214]].into_boxed_slice() /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn into_array_slice_box(values: Box<[T]>) -> Box<[T::Array]> where T: ArrayCast, { let raw: *mut [T::Array] = into_array_slice_mut(Box::leak(values)); // Safety: We know the pointer comes from a `Box` and thus has the correct lifetime. unsafe { Box::from_raw(raw) } } /// Cast from a boxed slice of colors to a boxed slice of color components. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].into_boxed_slice(); /// assert_eq!( /// cast::into_component_slice_box(colors), /// vec![64, 139, 10, 93, 18, 214].into_boxed_slice() /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn into_component_slice_box(values: Box<[T]>) -> Box<[::Item]> where T: ArrayCast, { let raw: *mut [::Item] = into_component_slice_mut(Box::leak(values)); // Safety: We know the pointer comes from a `Box` and thus has the correct lifetime. unsafe { Box::from_raw(raw) } } /// Cast from a boxed slice of arrays to a boxed slice of colors. /// /// ``` /// use palette::{cast, Srgb}; /// /// let arrays = vec![[64, 139, 10], [93, 18, 214]].into_boxed_slice(); /// assert_eq!( /// cast::from_array_slice_box::>(arrays), /// vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].into_boxed_slice() /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn from_array_slice_box(values: Box<[T::Array]>) -> Box<[T]> where T: ArrayCast, { let raw: *mut [T] = from_array_slice_mut(Box::leak(values)); // Safety: We know the pointer comes from a `Box` and thus has the correct lifetime. unsafe { Box::from_raw(raw) } } /// The same as [`try_from_component_slice_box`] but panics on error. /// /// ## Panics /// /// The cast will panic if the length of the input slice is not a multiple of /// the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = vec![64, 139, 10, 93, 18, 214].into_boxed_slice(); /// assert_eq!( /// cast::from_component_slice_box::>(components), /// vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].into_boxed_slice() /// ) /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast, Srgb}; /// /// // Not a multiple of 3: /// let components = vec![64, 139, 10, 93, 18, 214, 0, 123].into_boxed_slice(); /// cast::from_component_slice_box::>(components); /// ``` #[cfg(feature = "alloc")] #[inline] pub fn from_component_slice_box(values: Box<[::Item]>) -> Box<[T]> where T: ArrayCast, { try_from_component_slice_box(values).unwrap() } /// Cast from a boxed slice of color components to a boxed slice of colors. /// /// ## Errors /// /// The cast will return an error if the length of the input slice is not a /// multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = vec![64, 139, 10, 93, 18, 214].into_boxed_slice(); /// assert_eq!( /// cast::try_from_component_slice_box::>(components), /// Ok(vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].into_boxed_slice()) /// ) /// ``` /// /// This produces an error: /// /// ``` /// use palette::{cast, Srgb}; /// /// // Not a multiple of 3: /// let components = vec![64, 139, 10, 93, 18].into_boxed_slice(); /// /// if let Err(error) = cast::try_from_component_slice_box::>(components) { /// // We get the original values back on error: /// assert_eq!( /// error.values, /// vec![64, 139, 10, 93, 18].into_boxed_slice() /// ); /// } else { /// unreachable!(); /// } /// ``` #[cfg(feature = "alloc")] #[inline] pub fn try_from_component_slice_box( values: Box<[::Item]>, ) -> Result, BoxedSliceCastError<::Item>> where T: ArrayCast, { if values.len() % T::Array::LENGTH != 0 { return Err(BoxedSliceCastError { values }); } let raw: *mut [T] = from_component_slice_mut(Box::leak(values)); // Safety: We know the pointer comes from a `Box` and thus has the correct lifetime. unsafe { Ok(Box::from_raw(raw)) } } /// Cast from a `Vec` of colors to a `Vec` of arrays. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!( /// cast::into_array_vec(colors), /// vec![[64, 139, 10], [93, 18, 214]] /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn into_array_vec(values: Vec) -> Vec where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let mut values = ManuallyDrop::new(values); let raw = values.as_mut_ptr(); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // Length and capacity are the same because the size is the same. unsafe { Vec::from_raw_parts(raw.cast::(), values.len(), values.capacity()) } } /// Cast from a `Vec` of colors to a `Vec` of color components. /// /// ``` /// use palette::{cast, Srgb}; /// /// let colors = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// assert_eq!( /// cast::into_component_vec(colors), /// vec![64, 139, 10, 93, 18, 214] /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn into_component_vec(values: Vec) -> Vec<::Item> where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let mut values = ManuallyDrop::new(values); let raw = values.as_mut_ptr(); let length = values.len() * T::Array::LENGTH; let capacity = values.capacity() * T::Array::LENGTH; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // The length and capacity are multiplied by the array length. unsafe { Vec::from_raw_parts(raw.cast::<::Item>(), length, capacity) } } /// Cast from a `Vec` of arrays to a `Vec` of colors. /// /// ``` /// use palette::{cast, Srgb}; /// /// let arrays = vec![[64, 139, 10], [93, 18, 214]]; /// assert_eq!( /// cast::from_array_vec::>(arrays), /// vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn from_array_vec(values: Vec) -> Vec where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let mut values = ManuallyDrop::new(values); let raw = values.as_mut_ptr(); // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T::Array` as `T` is safe. // Length and capacity are the same because the size is the same. unsafe { Vec::from_raw_parts(raw.cast::(), values.len(), values.capacity()) } } /// The same as [`try_from_component_vec`] but panics on error. /// /// ## Panics /// /// The cast will panic if the length or capacity of the input `Vec` is not a /// multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = vec![64, 139, 10, 93, 18, 214]; /// assert_eq!( /// cast::from_component_vec::>(components), /// vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ) /// ``` /// /// This panics due to the incorrect length: /// /// ```should_panic /// use palette::{cast, Srgb}; /// /// // Not a multiple of 3: /// let components = vec![64, 139, 10, 93, 18, 214, 0, 123]; /// cast::from_component_vec::>(components); /// ``` /// /// This panics due to the incorrect capacity: /// /// ```should_panic /// use palette::{cast, Srgb}; /// /// let mut components = vec![64, 139, 10, 93, 18, 214]; /// components.reserve_exact(2); // Not a multiple of 3 /// cast::from_component_vec::>(components); /// ``` #[cfg(feature = "alloc")] #[inline] pub fn from_component_vec(values: Vec<::Item>) -> Vec where T: ArrayCast, { try_from_component_vec(values).unwrap() } /// Cast from a `Vec` of color components to a `Vec` of colors. /// /// ## Errors /// /// The cast will return an error if the length or capacity of the input `Vec` /// is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast, Srgb}; /// /// let components = vec![64, 139, 10, 93, 18, 214]; /// assert_eq!( /// cast::try_from_component_vec::>(components), /// Ok(vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]) /// ) /// ``` /// /// This produces an error due to the incorrect length: /// /// ``` /// use palette::{cast, Srgb}; /// /// // Not a multiple of 3: /// let components = vec![64, 139, 10, 93, 18]; /// /// if let Err(error) = cast::try_from_component_vec::>(components) { /// // We get the original values back on error: /// assert_eq!(error.values, vec![64, 139, 10, 93, 18]); /// } else { /// unreachable!(); /// } /// ``` /// /// This produces an error due to the incorrect capacity: /// /// ``` /// use palette::{cast, Srgb}; /// /// let mut components = vec![64, 139, 10, 93, 18, 214]; /// components.reserve_exact(2); // Not a multiple of 3 /// /// if let Err(error) = cast::try_from_component_vec::>(components) { /// // We get the original values back on error: /// assert_eq!(error.values, vec![64, 139, 10, 93, 18, 214]); /// assert_eq!(error.values.capacity(), 8); /// } else { /// unreachable!(); /// } /// ``` #[cfg(feature = "alloc")] #[inline] pub fn try_from_component_vec( values: Vec<::Item>, ) -> Result, VecCastError<::Item>> where T: ArrayCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); if values.len() % T::Array::LENGTH != 0 { return Err(VecCastError { values, kind: VecCastErrorKind::LengthMismatch, }); } if values.capacity() % T::Array::LENGTH != 0 { return Err(VecCastError { values, kind: VecCastErrorKind::CapacityMismatch, }); } let mut values = ManuallyDrop::new(values); let raw = values.as_mut_ptr(); let length = values.len() / T::Array::LENGTH; let capacity = values.capacity() / T::Array::LENGTH; // Safety: The requirements of implementing `ArrayCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Array` is safe. // The length and capacity are multiplies of the array length. unsafe { Ok(Vec::from_raw_parts(raw.cast::(), length, capacity)) } } /// Map values of color A to values of color B without creating a new `Vec`. /// /// This uses the guarantees of [`ArrayCast`] to reuse the allocation. #[cfg(feature = "alloc")] #[inline] pub fn map_vec_in_place(values: Vec, mut map: F) -> Vec where A: ArrayCast, B: ArrayCast, F: FnMut(A) -> B, { // We are checking `B` in advance, to stop the program before any work is // done. `A` is checked when converting to arrays. assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let mut values = ManuallyDrop::new(into_array_vec(values)); for item in &mut *values { // Safety: We will put a new value back below, and `values` will not be dropped on panic. let input = unsafe { core::ptr::read(item) }; let output = into_array::(map(from_array::(input))); // Safety: `output` is derived from the original value, so this is putting it back into place. unsafe { core::ptr::write(item, output) }; } from_array_vec(ManuallyDrop::into_inner(values)) } /// Map values of color A to values of color B without creating a new `Box<[B]>`. /// /// This uses the guarantees of [`ArrayCast`] to reuse the allocation. #[cfg(feature = "alloc")] #[inline] pub fn map_slice_box_in_place(values: Box<[A]>, mut map: F) -> Box<[B]> where A: ArrayCast, B: ArrayCast, F: FnMut(A) -> B, { // We are checking `B` in advance, to stop the program before any work is // done. `A` is checked when converting to arrays. assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!( core::mem::align_of::(), core::mem::align_of::() ); let mut values = ManuallyDrop::new(into_array_slice_box(values)); for item in &mut **values { // Safety: We will put a new value back below, and `values` will not be dropped on panic. let input = unsafe { core::ptr::read(item) }; let output = into_array::(map(from_array::(input))); // Safety: `output` is derived from the original value, so this is putting it back into place. unsafe { core::ptr::write(item, output) }; } from_array_slice_box(ManuallyDrop::into_inner(values)) } /// The error type returned when casting a slice of components fails. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SliceCastError; impl core::fmt::Display for SliceCastError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("could not convert component slice to colors") } } #[cfg(feature = "std")] impl std::error::Error for SliceCastError {} /// The error type returned when casting a boxed slice of components fails. #[derive(Clone, PartialEq, Eq)] #[cfg(feature = "alloc")] pub struct BoxedSliceCastError { /// The original values. pub values: Box<[T]>, } #[cfg(feature = "alloc")] impl core::fmt::Debug for BoxedSliceCastError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("BoxedSliceCastError").finish() } } #[cfg(feature = "alloc")] impl core::fmt::Display for BoxedSliceCastError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("could not convert boxed component slice to colors") } } #[cfg(feature = "std")] impl std::error::Error for BoxedSliceCastError {} /// The error type returned when casting a `Vec` of components fails. #[derive(Clone, PartialEq, Eq)] #[cfg(feature = "alloc")] pub struct VecCastError { /// The type of error that occurred. pub kind: VecCastErrorKind, /// The original values. pub values: Vec, } #[cfg(feature = "alloc")] impl core::fmt::Debug for VecCastError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("VecCastError") .field("kind", &self.kind) .finish() } } #[cfg(feature = "alloc")] impl core::fmt::Display for VecCastError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("could not convert component vector to colors") } } #[cfg(feature = "std")] impl std::error::Error for VecCastError {} /// The type of error that is returned when casting a `Vec` of components. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg(feature = "alloc")] pub enum VecCastErrorKind { /// The type of error returned when the length of a `Vec` didn't match the /// requirements. LengthMismatch, /// The type of error returned when the capacity of a `Vec` didn't match the /// requirements. CapacityMismatch, } #[cfg(test)] mod test { #[cfg(feature = "alloc")] use crate::{LinSrgb, Srgb}; #[cfg(feature = "alloc")] #[test] fn array_vec_len_cap() { let mut original = vec![ Srgb::new(255u8, 0, 0), Srgb::new(0, 255, 0), Srgb::new(0, 0, 255), ]; original.reserve_exact(5); // Bringing it to 8 let colors_arrays = super::into_array_vec(original); assert_eq!(colors_arrays.len(), 3); assert_eq!(colors_arrays.capacity(), 8); let colors = super::from_array_vec::>(colors_arrays); assert_eq!(colors.len(), 3); assert_eq!(colors.capacity(), 8); let colors_components = super::into_component_vec(colors); assert_eq!(colors_components.len(), 9); assert_eq!(colors_components.capacity(), 24); let colors = super::from_component_vec::>(colors_components); assert_eq!(colors.len(), 3); assert_eq!(colors.capacity(), 8); } #[cfg(feature = "alloc")] #[test] fn map_vec_in_place() { fn do_things(rgb: Srgb) -> LinSrgb { let mut linear = rgb.into_linear(); core::mem::swap(&mut linear.red, &mut linear.blue); linear } let values = vec![Srgb::new(0.8, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)]; let result = super::map_vec_in_place(values, do_things); assert_eq!( result, vec![ do_things(Srgb::new(0.8, 1.0, 0.2)), do_things(Srgb::new(0.9, 0.1, 0.3)) ] ) } #[cfg(feature = "alloc")] #[test] fn map_slice_box_in_place() { fn do_things(rgb: Srgb) -> LinSrgb { let mut linear = rgb.into_linear(); core::mem::swap(&mut linear.red, &mut linear.blue); linear } let values = vec![Srgb::new(0.8, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)].into_boxed_slice(); let result = super::map_slice_box_in_place(values, do_things); assert_eq!( result, vec![ do_things(Srgb::new(0.8, 1.0, 0.2)), do_things(Srgb::new(0.9, 0.1, 0.3)) ] .into_boxed_slice() ) } } palette-0.7.5/src/cast/as_arrays_traits.rs000064400000000000000000000175041046102023000167430ustar 00000000000000use super::{ from_array_slice, from_array_slice_mut, into_array_slice, into_array_slice_mut, ArrayCast, }; /// Trait for casting a reference to a collection of colors into a reference to /// a collection of arrays without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::AsArrays, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice: &[_] = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(array.as_arrays(), &[[64, 139, 10], [93, 18, 214]]); /// assert_eq!(slice.as_arrays(), &[[64, 139, 10], [93, 18, 214]]); /// assert_eq!(vec.as_arrays(), &[[64, 139, 10], [93, 18, 214]]); /// ``` pub trait AsArrays { /// Cast this collection of colors into a collection of arrays. fn as_arrays(&self) -> &A; } /// Trait for casting a mutable reference to a collection of colors into a /// mutable reference to a collection of arrays without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::AsArraysMut, Srgb}; /// /// let mut array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice_mut: &mut [_] = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let mut vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(array.as_arrays_mut(), &mut [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(slice_mut.as_arrays_mut(), &mut [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(vec.as_arrays_mut(), &mut [[64, 139, 10], [93, 18, 214]]); /// ``` pub trait AsArraysMut { /// Cast this collection of colors into a mutable collection of arrays. fn as_arrays_mut(&mut self) -> &mut A; } macro_rules! impl_as_arrays { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C, const N: usize $(, $($ty_input)+)?> AsArrays<[[T; N]]> for $owning where C: ArrayCast, { #[inline] fn as_arrays(&self) -> &[[T; N]] { into_array_slice(self.as_ref()) } } impl<'a, T, C, const N: usize $(, $($ty_input)+)?> AsArraysMut<[[T; N]]> for $owning where C: ArrayCast, { #[inline] fn as_arrays_mut(&mut self) -> &mut [[T; N]] { into_array_slice_mut(self.as_mut()) } } )* }; } impl_as_arrays!([C], [C; M] where (const M: usize)); #[cfg(feature = "alloc")] impl_as_arrays!(alloc::boxed::Box<[C]>, alloc::vec::Vec); /// Trait for casting a reference to collection of arrays into a reference to /// collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::ArraysAs, Srgb}; /// /// let array: [_; 2] = [[64, 139, 10], [93, 18, 214]]; /// let slice: &[_] = &[[64, 139, 10], [93, 18, 214]]; /// let vec: Vec<_> = vec![[64, 139, 10], [93, 18, 214]]; /// /// let colors: &[Srgb] = array.arrays_as(); /// assert_eq!(colors, &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &[Srgb] = slice.arrays_as(); /// assert_eq!(colors, &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &[Srgb] = vec.arrays_as(); /// assert_eq!(colors, &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` pub trait ArraysAs { /// Cast this collection of arrays into a collection of colors. fn arrays_as(&self) -> &C; } /// Trait for casting a mutable reference to collection of arrays into a mutable /// reference to collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::ArraysAsMut, Srgb}; /// /// let mut array: [_; 2] = [[64, 139, 10], [93, 18, 214]]; /// let slice_mut: &mut [_] = &mut [[64, 139, 10], [93, 18, 214]]; /// let mut vec: Vec<_> = vec![[64, 139, 10], [93, 18, 214]]; /// /// let colors: &mut [Srgb] = array.arrays_as_mut(); /// assert_eq!(colors, &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = slice_mut.arrays_as_mut(); /// assert_eq!(colors, &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = vec.arrays_as_mut(); /// assert_eq!(colors, &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` pub trait ArraysAsMut { /// Cast this collection of arrays into a mutable collection of colors. fn arrays_as_mut(&mut self) -> &mut C; } macro_rules! impl_arrays_as { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C, const N: usize $(, $($ty_input)+)?> ArraysAs<[C]> for $owning where C: ArrayCast, { #[inline] fn arrays_as(&self) -> &[C] { from_array_slice(self.as_ref()) } } impl<'a, T, C, const N: usize $(, $($ty_input)+)?> ArraysAsMut<[C]> for $owning where C: ArrayCast, { #[inline] fn arrays_as_mut(&mut self) -> &mut [C] { from_array_slice_mut(self.as_mut()) } } )* }; } impl_arrays_as!([[T; N]], [[T; N]; M] where (const M: usize)); #[cfg(feature = "alloc")] impl_arrays_as!(alloc::boxed::Box<[[T; N]]>, alloc::vec::Vec<[T; N]>); #[cfg(test)] mod test { use crate::Srgb; use super::{ArraysAs, ArraysAsMut, AsArrays, AsArraysMut}; #[test] fn as_arrays() { let slice: &[Srgb] = &[Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let slice_mut: &mut [Srgb] = &mut [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut slice_box: Box<[Srgb]> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)].into_boxed_slice(); let mut vec: Vec> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut array: [Srgb; 2] = [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _: &[[u8; 3]] = slice.as_arrays(); let _: &[[u8; 3]] = slice_box.as_arrays(); let _: &[[u8; 3]] = vec.as_arrays(); let _: &[[u8; 3]] = array.as_arrays(); let _: &mut [[u8; 3]] = slice_mut.as_arrays_mut(); let _: &mut [[u8; 3]] = slice_box.as_arrays_mut(); let _: &mut [[u8; 3]] = vec.as_arrays_mut(); let _: &mut [[u8; 3]] = array.as_arrays_mut(); } #[test] fn arrays_as() { let slice: &[[u8; 3]] = &[[1, 2, 3], [4, 5, 6]]; let slice_mut: &mut [[u8; 3]] = &mut [[1, 2, 3], [4, 5, 6]]; let mut slice_box: Box<[[u8; 3]]> = vec![[1, 2, 3], [4, 5, 6]].into_boxed_slice(); let mut vec: Vec<[u8; 3]> = vec![[1, 2, 3], [4, 5, 6]]; let mut array: [[u8; 3]; 2] = [[1, 2, 3], [4, 5, 6]]; let _: &[Srgb] = slice.arrays_as(); let _: &[Srgb] = slice_box.arrays_as(); let _: &[Srgb] = vec.arrays_as(); let _: &[Srgb] = array.arrays_as(); let _: &mut [Srgb] = slice_mut.arrays_as_mut(); let _: &mut [Srgb] = slice_box.arrays_as_mut(); let _: &mut [Srgb] = vec.arrays_as_mut(); let _: &mut [Srgb] = array.arrays_as_mut(); } } palette-0.7.5/src/cast/as_components_traits.rs000064400000000000000000000352121046102023000176230ustar 00000000000000use core::fmt::Debug; use super::{ into_component_slice, into_component_slice_mut, try_from_component_slice, try_from_component_slice_mut, ArrayCast, SliceCastError, }; /// Trait for casting a reference to a collection of colors into a reference to /// a collection of color components without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::AsComponents, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice: &[_] = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(array.as_components(), &[64, 139, 10, 93, 18, 214]); /// assert_eq!(slice.as_components(), &[64, 139, 10, 93, 18, 214]); /// assert_eq!(vec.as_components(), &[64, 139, 10, 93, 18, 214]); /// ``` pub trait AsComponents { /// Cast this collection of colors into a collection of color components. fn as_components(&self) -> &C; } /// Trait for casting a mutable reference to a collection of colors into a /// mutable reference to a collection of color components without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::AsComponentsMut, Srgb}; /// /// let mut array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice_mut: &mut [_] = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let mut vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(array.as_components_mut(), &mut [64, 139, 10, 93, 18, 214]); /// assert_eq!(slice_mut.as_components_mut(), &mut [64, 139, 10, 93, 18, 214]); /// assert_eq!(vec.as_components_mut(), &mut [64, 139, 10, 93, 18, 214]); /// ``` pub trait AsComponentsMut { /// Cast this collection of colors into a mutable collection of color /// components. fn as_components_mut(&mut self) -> &mut C; } macro_rules! impl_as_components { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C, const N: usize $(, $($ty_input)+)?> AsComponents<[T]> for $owning where C: ArrayCast, { #[inline] fn as_components(&self) -> &[T] { into_component_slice(self.as_ref()) } } impl<'a, T, C, const N: usize $(, $($ty_input)+)?> AsComponentsMut<[T]> for $owning where C: ArrayCast, { #[inline] fn as_components_mut(&mut self) -> &mut [T] { into_component_slice_mut(self.as_mut()) } } )* }; } impl_as_components!([C], [C; M] where (const M: usize)); #[cfg(feature = "alloc")] impl_as_components!(alloc::boxed::Box<[C]>, alloc::vec::Vec); /// Trait for trying to cast a reference to collection of color components into /// a reference to collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Errors /// /// The cast will return an error if the cast fails, such as when the length of /// the input is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::TryComponentsAs, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice: &[_] = &[64, 139, 10, 93, 18, 214]; /// let vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// let colors: Result<&[Srgb], _> = array.try_components_as(); /// assert_eq!(colors, Ok(&[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)][..])); /// /// let colors: Result<&[Srgb], _> = slice.try_components_as(); /// assert_eq!(colors, Ok(&[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)][..])); /// /// let colors: Result<&[Srgb], _> = vec.try_components_as(); /// assert_eq!(colors, Ok(&[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)][..])); /// ``` /// /// This produces an error: /// /// ``` /// use palette::{cast::TryComponentsAs, Srgb}; /// /// let components = [64, 139, 10, 93, 18]; // Not a multiple of 3 /// let colors: Result<&[Srgb], _> = components.try_components_as(); /// assert!(colors.is_err()); /// ``` pub trait TryComponentsAs { /// The error for when `try_components_as` fails to cast. type Error; /// Try to cast this collection of color components into a reference to a /// collection of colors. /// /// Return an error if the conversion can't be done, such as when the number /// of items in `self` isn't a multiple of the number of components in the /// color type. fn try_components_as(&self) -> Result<&C, Self::Error>; } /// Trait for trying to cast a mutable reference to collection of color /// components into a mutable reference to collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Errors /// /// The cast will return an error if the cast fails, such as when the length of /// the input is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::TryComponentsAsMut, Srgb}; /// /// let mut array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice_mut: &mut [_] = &mut [64, 139, 10, 93, 18, 214]; /// let mut vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// let colors: Result<&mut [Srgb], _> = array.try_components_as_mut(); /// assert_eq!(colors, Ok(&mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)][..])); /// /// let colors: Result<&mut [Srgb], _> = slice_mut.try_components_as_mut(); /// assert_eq!(colors, Ok(&mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)][..])); /// /// let colors: Result<&mut [Srgb], _> = vec.try_components_as_mut(); /// assert_eq!(colors, Ok(&mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)][..])); /// ``` /// /// This produces an error: /// /// ``` /// use palette::{cast::TryComponentsAsMut, Srgb}; /// /// let mut components = [64, 139, 10, 93, 18]; // Not a multiple of 3 /// let colors: Result<&mut [Srgb], _> = components.try_components_as_mut(); /// assert!(colors.is_err()); /// ``` pub trait TryComponentsAsMut { /// The error for when `try_components_as_mut` fails to cast. type Error; /// Try to cast this collection of color components into a mutable reference /// to a collection of colors. /// /// Return an error if the conversion can't be done, such as when the number /// of items in `self` isn't a multiple of the number of components in the /// color type. fn try_components_as_mut(&mut self) -> Result<&mut C, Self::Error>; } macro_rules! impl_try_components_as { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C, const N: usize $(, $($ty_input)+)?> TryComponentsAs<[C]> for $owning where C: ArrayCast, { type Error = SliceCastError; #[inline] fn try_components_as(&self) -> Result<&[C], Self::Error> { try_from_component_slice(self.as_ref()) } } impl<'a, T, C, const N: usize $(, $($ty_input)+)?> TryComponentsAsMut<[C]> for $owning where C: ArrayCast, { type Error = SliceCastError; #[inline] fn try_components_as_mut(&mut self) -> Result<&mut [C], Self::Error> { try_from_component_slice_mut(self.as_mut()) } } )* }; } impl_try_components_as!([T], [T; M] where (const M: usize)); #[cfg(feature = "alloc")] impl_try_components_as!(alloc::boxed::Box<[T]>, alloc::vec::Vec); /// Trait for casting a reference to collection of color components into a /// reference to collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Panics /// /// The cast will panic if the cast fails, such as when the length of the input /// is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::ComponentsAs, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice: &[_] = &[64, 139, 10, 93, 18, 214]; /// let vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// let colors: &[Srgb] = array.components_as(); /// assert_eq!(colors, &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &[Srgb] = slice.components_as(); /// assert_eq!(colors, &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &[Srgb] = vec.components_as(); /// assert_eq!(colors, &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast::ComponentsAs, Srgb}; /// /// let components = [64, 139, 10, 93, 18, 214, 0, 123]; // Not a multiple of 3 /// let colors: &[Srgb] = components.components_as(); /// ``` pub trait ComponentsAs { /// Cast this collection of color components into a reference to a /// collection of colors. /// /// ## Panics /// If the conversion can't be done, such as when the number of items in /// `self` isn't a multiple of the number of components in the color type. fn components_as(&self) -> &C; } impl ComponentsAs for T where T: TryComponentsAs + ?Sized, T::Error: Debug, C: ?Sized, { fn components_as(&self) -> &C { self.try_components_as().unwrap() } } /// Trait for casting a mutable reference to collection of color components into /// a mutable reference to collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Panics /// /// The cast will panic if the cast fails, such as when the length of the input /// is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::ComponentsAsMut, Srgb}; /// /// let mut array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice_mut: &mut [_] = &mut [64, 139, 10, 93, 18, 214]; /// let mut vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// let colors: &mut [Srgb] = array.components_as_mut(); /// assert_eq!(colors, &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = slice_mut.components_as_mut(); /// assert_eq!(colors, &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = vec.components_as_mut(); /// assert_eq!(colors, &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast::ComponentsAsMut, Srgb}; /// /// let mut components = [64, 139, 10, 93, 18, 214, 0, 123]; // Not a multiple of 3 /// let colors: &mut [Srgb] = components.components_as_mut(); /// ``` pub trait ComponentsAsMut { /// Cast this collection of color components into a mutable reference to a /// collection of colors. /// /// ## Panics /// If the conversion can't be done, such as when the number of items in /// `self` isn't a multiple of the number of components in the color type. fn components_as_mut(&mut self) -> &mut C; } impl ComponentsAsMut for T where T: TryComponentsAsMut + ?Sized, T::Error: Debug, C: ?Sized, { fn components_as_mut(&mut self) -> &mut C { self.try_components_as_mut().unwrap() } } #[cfg(test)] mod test { use crate::Srgb; use super::{ AsComponents, AsComponentsMut, ComponentsAs, ComponentsAsMut, TryComponentsAs, TryComponentsAsMut, }; #[test] fn as_components() { let slice: &[Srgb] = &[Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let slice_mut: &mut [Srgb] = &mut [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut slice_box: Box<[Srgb]> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)].into_boxed_slice(); let mut vec: Vec> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut array: [Srgb; 2] = [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _: &[u8] = slice.as_components(); let _: &[u8] = slice_box.as_components(); let _: &[u8] = vec.as_components(); let _: &[u8] = array.as_components(); let _: &mut [u8] = slice_mut.as_components_mut(); let _: &mut [u8] = slice_box.as_components_mut(); let _: &mut [u8] = vec.as_components_mut(); let _: &mut [u8] = array.as_components_mut(); } #[test] fn try_components_as() { let slice: &[u8] = &[1, 2, 3, 4, 5, 6]; let slice_mut: &mut [u8] = &mut [1, 2, 3, 4, 5, 6]; let mut slice_box: Box<[u8]> = vec![1, 2, 3, 4, 5, 6].into_boxed_slice(); let mut vec: Vec = vec![1, 2, 3, 4, 5, 6]; let mut array: [u8; 6] = [1, 2, 3, 4, 5, 6]; let _: &[Srgb] = slice.try_components_as().unwrap(); let _: &[Srgb] = slice_box.try_components_as().unwrap(); let _: &[Srgb] = vec.try_components_as().unwrap(); let _: &[Srgb] = array.try_components_as().unwrap(); let _: &mut [Srgb] = slice_mut.try_components_as_mut().unwrap(); let _: &mut [Srgb] = slice_box.try_components_as_mut().unwrap(); let _: &mut [Srgb] = vec.try_components_as_mut().unwrap(); let _: &mut [Srgb] = array.try_components_as_mut().unwrap(); } #[test] fn components_as() { let slice: &[u8] = &[1, 2, 3, 4, 5, 6]; let slice_mut: &mut [u8] = &mut [1, 2, 3, 4, 5, 6]; let mut slice_box: Box<[u8]> = vec![1, 2, 3, 4, 5, 6].into_boxed_slice(); let mut vec: Vec = vec![1, 2, 3, 4, 5, 6]; let mut array: [u8; 6] = [1, 2, 3, 4, 5, 6]; let _: &[Srgb] = slice.components_as(); let _: &[Srgb] = slice_box.components_as(); let _: &[Srgb] = vec.components_as(); let _: &[Srgb] = array.components_as(); let _: &mut [Srgb] = slice_mut.components_as_mut(); let _: &mut [Srgb] = slice_box.components_as_mut(); let _: &mut [Srgb] = vec.components_as_mut(); let _: &mut [Srgb] = array.components_as_mut(); } } palette-0.7.5/src/cast/as_uints_traits.rs000064400000000000000000000214541046102023000166030ustar 00000000000000use super::{from_uint_slice, from_uint_slice_mut, into_uint_slice, into_uint_slice_mut, UintCast}; /// Trait for casting a reference to a collection of colors into a reference to /// a collection of unsigned integers without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::AsUints, rgb::PackedArgb, Srgba}; /// /// let array: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let slice: &[PackedArgb] = &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let vec: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!(array.as_uints(), &[0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(slice.as_uints(), &[0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(vec.as_uints(), &[0xFF17C64C, 0xFF5D12D6]); /// ``` pub trait AsUints { /// Cast this collection of colors into a collection of unsigned integers. fn as_uints(&self) -> &A; } /// Trait for casting a mutable reference to a collection of colors into a /// mutable reference to a collection of unsigned integers without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::AsUintsMut, rgb::PackedArgb, Srgba}; /// /// let mut array: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let slice_mut: &mut [PackedArgb] = &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let mut vec: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!(array.as_uints_mut(), &mut [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(slice_mut.as_uints_mut(), &mut [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(vec.as_uints_mut(), &mut [0xFF17C64C, 0xFF5D12D6]); /// ``` pub trait AsUintsMut { /// Cast this collection of colors into a mutable collection of unsigned integers. fn as_uints_mut(&mut self) -> &mut A; } macro_rules! impl_as_uints { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, C $(, $($ty_input)+)?> AsUints<[C::Uint]> for $owning where C: UintCast, { #[inline] fn as_uints(&self) -> &[C::Uint] { into_uint_slice(self.as_ref()) } } impl<'a, C $(, $($ty_input)+)?> AsUintsMut<[C::Uint]> for $owning where C: UintCast, { #[inline] fn as_uints_mut(&mut self) -> &mut [C::Uint] { into_uint_slice_mut(self.as_mut()) } } )* }; } impl_as_uints!([C], [C; N] where (const N: usize)); #[cfg(feature = "alloc")] impl_as_uints!(alloc::boxed::Box<[C]>, alloc::vec::Vec); /// Trait for casting a reference to a collection of unsigned integers into a /// reference to a collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::UintsAs, rgb::PackedArgb, Srgba}; /// /// let array: [_; 2] = [0xFF17C64C, 0xFF5D12D6]; /// let slice: &[_] = &[0xFF17C64C, 0xFF5D12D6]; /// let vec: Vec<_> = vec![0xFF17C64C, 0xFF5D12D6]; /// /// let colors: &[PackedArgb] = array.uints_as(); /// assert_eq!( /// colors, /// &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// let colors: &[PackedArgb] = slice.uints_as(); /// assert_eq!( /// colors, /// &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// let colors: &[PackedArgb] = vec.uints_as(); /// assert_eq!( /// colors, /// &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// ``` pub trait UintsAs { /// Cast this collection of unsigned integers into a collection of colors. fn uints_as(&self) -> &C; } /// Trait for casting a mutable reference to a collection of unsigned integers /// into a mutable reference to a collection of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::UintsAsMut, rgb::PackedArgb, Srgba}; /// /// let mut array: [_; 2] = [0xFF17C64C, 0xFF5D12D6]; /// let slice_mut: &mut [_] = &mut [0xFF17C64C, 0xFF5D12D6]; /// let mut vec: Vec<_> = vec![0xFF17C64C, 0xFF5D12D6]; /// /// let colors: &mut [PackedArgb] = array.uints_as_mut(); /// assert_eq!( /// colors, /// &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// let colors: &mut [PackedArgb] = slice_mut.uints_as_mut(); /// assert_eq!( /// colors, /// &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// let colors: &mut [PackedArgb] = vec.uints_as_mut(); /// assert_eq!( /// colors, /// &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// ``` pub trait UintsAsMut { /// Cast this collection of unsigned integers into a mutable collection of colors. fn uints_as_mut(&mut self) -> &mut C; } macro_rules! impl_uints_as { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, C $(, $($ty_input)+)?> UintsAs<[C]> for $owning where C: UintCast, { #[inline] fn uints_as(&self) -> &[C] { from_uint_slice(self.as_ref()) } } impl<'a, C $(, $($ty_input)+)?> UintsAsMut<[C]> for $owning where C: UintCast, { #[inline] fn uints_as_mut(&mut self) -> &mut [C] { from_uint_slice_mut(self.as_mut()) } } )* }; } impl_uints_as!([C::Uint], [C::Uint; N] where (const N: usize)); #[cfg(feature = "alloc")] impl_uints_as!(alloc::boxed::Box<[C::Uint]>, alloc::vec::Vec); #[cfg(test)] mod test { use crate::{rgb::PackedRgba, Srgba}; use super::{AsUints, AsUintsMut, UintsAs, UintsAsMut}; #[test] fn as_uints() { let slice: &[PackedRgba] = &[Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let slice_mut: &mut [PackedRgba] = &mut [Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let mut slice_box: Box<[PackedRgba]> = vec![Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()].into_boxed_slice(); let mut vec: Vec = vec![Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let mut array: [PackedRgba; 2] = [Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let _: &[u32] = slice.as_uints(); let _: &[u32] = slice_box.as_uints(); let _: &[u32] = vec.as_uints(); let _: &[u32] = array.as_uints(); let _: &mut [u32] = slice_mut.as_uints_mut(); let _: &mut [u32] = slice_box.as_uints_mut(); let _: &mut [u32] = vec.as_uints_mut(); let _: &mut [u32] = array.as_uints_mut(); } #[test] fn uints_as() { let slice: &[u32] = &[0x01020304, 0x05060708]; let slice_mut: &mut [u32] = &mut [0x01020304, 0x05060708]; let mut slice_box: Box<[u32]> = vec![0x01020304, 0x05060708].into_boxed_slice(); let mut vec: Vec = vec![0x01020304, 0x05060708]; let mut array: [u32; 2] = [0x01020304, 0x05060708]; let _: &[PackedRgba] = slice.uints_as(); let _: &[PackedRgba] = slice_box.uints_as(); let _: &[PackedRgba] = vec.uints_as(); let _: &[PackedRgba] = array.uints_as(); let _: &mut [PackedRgba] = slice_mut.uints_as_mut(); let _: &mut [PackedRgba] = slice_box.uints_as_mut(); let _: &mut [PackedRgba] = vec.uints_as_mut(); let _: &mut [PackedRgba] = array.uints_as_mut(); } } palette-0.7.5/src/cast/from_into_arrays_traits.rs000064400000000000000000000356231046102023000203360ustar 00000000000000use super::{ from_array_array, from_array_slice, from_array_slice_mut, into_array_array, into_array_slice, into_array_slice_mut, ArrayCast, }; #[cfg(feature = "alloc")] use super::{from_array_slice_box, from_array_vec, into_array_slice_box, into_array_vec}; /// Trait for casting a collection of colors from a collection of arrays without /// copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::FromArrays, Srgb}; /// /// let array: [_; 2] = [[64, 139, 10], [93, 18, 214]]; /// let slice: &[_] = &[[64, 139, 10], [93, 18, 214]]; /// let slice_mut: &mut [_] = &mut [[64, 139, 10], [93, 18, 214]]; /// let vec: Vec<_> = vec![[64, 139, 10], [93, 18, 214]]; /// /// assert_eq!( /// <[Srgb; 2]>::from_arrays(array), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// <&[Srgb]>::from_arrays(slice), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// <&mut [Srgb]>::from_arrays(slice_mut), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// Vec::>::from_arrays(vec), /// vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::FromArrays, Srgb}; /// /// let array: [_; 2] = [[64, 139, 10], [93, 18, 214]]; /// let mut vec: Vec<_> = vec![[64, 139, 10], [93, 18, 214]]; /// /// assert_eq!( /// <&[Srgb]>::from_arrays(&array), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// <&mut [Srgb]>::from_arrays(&mut vec), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// ``` pub trait FromArrays { /// Cast a collection of arrays into an collection of colors. fn from_arrays(arrays: A) -> Self; } impl FromArrays<[[T; N]; M]> for [C; M] where C: ArrayCast, { #[inline] fn from_arrays(arrays: [[T; N]; M]) -> Self { from_array_array(arrays) } } macro_rules! impl_from_arrays_slice { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C, const N: usize $(, $($ty_input)+)?> FromArrays<&'a $owning> for &'a [C] where C: ArrayCast, { #[inline] fn from_arrays(arrays: &'a $owning) -> Self { from_array_slice(arrays) } } impl<'a, T, C, const N: usize $(, $($ty_input)+)?> FromArrays<&'a mut $owning> for &'a mut [C] where C: ArrayCast, { #[inline] fn from_arrays(arrays: &'a mut $owning) -> Self { from_array_slice_mut(arrays) } } )* }; } impl_from_arrays_slice!([[T; N]], [[T; N]; M] where (const M: usize)); #[cfg(feature = "alloc")] impl_from_arrays_slice!(alloc::boxed::Box<[[T; N]]>, alloc::vec::Vec<[T; N]>); #[cfg(feature = "alloc")] impl FromArrays> for alloc::boxed::Box<[C]> where C: ArrayCast, { #[inline] fn from_arrays(arrays: alloc::boxed::Box<[[T; N]]>) -> Self { from_array_slice_box(arrays) } } #[cfg(feature = "alloc")] impl FromArrays> for alloc::vec::Vec where C: ArrayCast, { #[inline] fn from_arrays(arrays: alloc::vec::Vec<[T; N]>) -> Self { from_array_vec(arrays) } } /// Trait for casting a collection of colors into a collection of arrays without /// copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::IntoArrays, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice: &[_] = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice_mut: &mut [_] = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(array.into_arrays(), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(slice.into_arrays(), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(slice_mut.into_arrays(), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(vec.into_arrays(), vec![[64, 139, 10], [93, 18, 214]]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::IntoArrays, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let mut vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!((&array).into_arrays(), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!((&mut vec).into_arrays(), [[64, 139, 10], [93, 18, 214]]); /// ``` pub trait IntoArrays { /// Cast this collection of colors into a collection of arrays. fn into_arrays(self) -> A; } impl IntoArrays<[[T; N]; M]> for [C; M] where C: ArrayCast, { #[inline] fn into_arrays(self) -> [[T; N]; M] { into_array_array(self) } } macro_rules! impl_into_arrays_slice { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C, const N: usize $(, $($ty_input)+)?> IntoArrays<&'a [[T; N]]> for &'a $owning where C: ArrayCast, { #[inline] fn into_arrays(self) -> &'a [[T; N]] { into_array_slice(self) } } impl<'a, T, C, const N: usize $(, $($ty_input)+)?> IntoArrays<&'a mut [[T; N]]> for &'a mut $owning where C: ArrayCast, { #[inline] fn into_arrays(self) -> &'a mut [[T; N]] { into_array_slice_mut(self) } } )* }; } impl_into_arrays_slice!([C], [C; M] where (const M: usize)); #[cfg(feature = "alloc")] impl_into_arrays_slice!(alloc::boxed::Box<[C]>, alloc::vec::Vec); #[cfg(feature = "alloc")] impl IntoArrays> for alloc::boxed::Box<[C]> where C: ArrayCast, { #[inline] fn into_arrays(self) -> alloc::boxed::Box<[[T; N]]> { into_array_slice_box(self) } } #[cfg(feature = "alloc")] impl IntoArrays> for alloc::vec::Vec where C: ArrayCast, { #[inline] fn into_arrays(self) -> alloc::vec::Vec<[T; N]> { into_array_vec(self) } } /// Trait for casting a collection of arrays from a collection of colors without /// copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::ArraysFrom, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice: &[_] = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice_mut: &mut [_] = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(<[_; 2]>::arrays_from(array), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(<&[_]>::arrays_from(slice), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(<&mut [_]>::arrays_from(slice_mut), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(Vec::<_>::arrays_from(vec), vec![[64, 139, 10], [93, 18, 214]]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::ArraysFrom, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let mut vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(<&[_]>::arrays_from(&array), [[64, 139, 10], [93, 18, 214]]); /// assert_eq!(<&mut [_]>::arrays_from(&mut vec), [[64, 139, 10], [93, 18, 214]]); /// ``` pub trait ArraysFrom { /// Cast a collection of colors into a collection of arrays. fn arrays_from(colors: C) -> Self; } impl ArraysFrom for T where C: IntoArrays, { #[inline] fn arrays_from(colors: C) -> Self { colors.into_arrays() } } /// Trait for casting a collection of arrays into a collection of colors /// without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::ArraysInto, Srgb}; /// /// let array: [_; 2] = [[64, 139, 10], [93, 18, 214]]; /// let slice: &[_] = &[[64, 139, 10], [93, 18, 214]]; /// let slice_mut: &mut [_] = &mut [[64, 139, 10], [93, 18, 214]]; /// let vec: Vec<_> = vec![[64, 139, 10], [93, 18, 214]]; /// /// let colors: [Srgb; 2] = array.arrays_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &[Srgb] = slice.arrays_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = slice_mut.arrays_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: Vec> = vec.arrays_into(); /// assert_eq!(colors, vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::ArraysInto, Srgb}; /// /// let array: [_; 2] = [[64, 139, 10], [93, 18, 214]]; /// let mut vec: Vec<_> = vec![[64, 139, 10], [93, 18, 214]]; /// /// let colors: &[Srgb] = (&array).arrays_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = (&mut vec).arrays_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` pub trait ArraysInto { /// Cast this collection of arrays into a collection of colors. fn arrays_into(self) -> C; } impl ArraysInto for T where C: FromArrays, { #[inline] fn arrays_into(self) -> C { C::from_arrays(self) } } #[cfg(test)] mod test { use crate::Srgb; use super::{ArraysFrom, ArraysInto, FromArrays, IntoArrays}; #[test] fn from_arrays() { let slice: &[[u8; 3]] = &[[1, 2, 3], [4, 5, 6]]; let slice_mut: &mut [[u8; 3]] = &mut [[1, 2, 3], [4, 5, 6]]; let mut array: [[u8; 3]; 2] = [[1, 2, 3], [4, 5, 6]]; let _ = <&[Srgb]>::from_arrays(slice); let _ = <&[Srgb]>::from_arrays(&array); let _ = <&mut [Srgb]>::from_arrays(slice_mut); let _ = <&mut [Srgb]>::from_arrays(&mut array); let _ = <[Srgb; 2]>::from_arrays(array); } #[cfg(feature = "alloc")] #[test] fn from_arrays_alloc() { let mut slice_box: Box<[[u8; 3]]> = vec![[1, 2, 3], [4, 5, 6]].into_boxed_slice(); let mut vec: Vec<[u8; 3]> = vec![[1, 2, 3], [4, 5, 6]]; let _ = <&[Srgb]>::from_arrays(&slice_box); let _ = <&[Srgb]>::from_arrays(&vec); let _ = <&mut [Srgb]>::from_arrays(&mut slice_box); let _ = <&mut [Srgb]>::from_arrays(&mut vec); let _ = Box::<[Srgb]>::from_arrays(slice_box); let _ = Vec::>::from_arrays(vec); } #[test] fn arrays_into() { let slice: &[[u8; 3]] = &[[1, 2, 3], [4, 5, 6]]; let slice_mut: &mut [[u8; 3]] = &mut [[1, 2, 3], [4, 5, 6]]; let mut array: [[u8; 3]; 2] = [[1, 2, 3], [4, 5, 6]]; let _: &[Srgb] = slice.arrays_into(); let _: &[Srgb] = (&array).arrays_into(); let _: &mut [Srgb] = slice_mut.arrays_into(); let _: &mut [Srgb] = (&mut array).arrays_into(); let _: [Srgb; 2] = array.arrays_into(); } #[cfg(feature = "alloc")] #[test] fn arrays_into_alloc() { let mut slice_box: Box<[[u8; 3]]> = vec![[1, 2, 3], [4, 5, 6]].into_boxed_slice(); let mut vec: Vec<[u8; 3]> = vec![[1, 2, 3], [4, 5, 6]]; let _: &[Srgb] = (&slice_box).arrays_into(); let _: &[Srgb] = (&vec).arrays_into(); let _: &mut [Srgb] = (&mut slice_box).arrays_into(); let _: &mut [Srgb] = (&mut vec).arrays_into(); let _: Box<[Srgb]> = slice_box.arrays_into(); let _: Vec> = vec.arrays_into(); } #[test] fn into_arrays() { let slice: &[Srgb] = &[Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let slice_mut: &mut [Srgb] = &mut [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut array: [Srgb; 2] = [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _: &[[u8; 3]] = slice.into_arrays(); let _: &[[u8; 3]] = (&array).into_arrays(); let _: &mut [[u8; 3]] = slice_mut.into_arrays(); let _: &mut [[u8; 3]] = (&mut array).into_arrays(); let _: [[u8; 3]; 2] = array.into_arrays(); } #[cfg(feature = "alloc")] #[test] fn into_arrays_alloc() { let mut slice_box: Box<[Srgb]> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)].into_boxed_slice(); let mut vec: Vec> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _: &[[u8; 3]] = (&slice_box).into_arrays(); let _: &[[u8; 3]] = (&vec).into_arrays(); let _: &mut [[u8; 3]] = (&mut slice_box).into_arrays(); let _: &mut [[u8; 3]] = (&mut vec).into_arrays(); let _: Box<[[u8; 3]]> = slice_box.into_arrays(); let _: Vec<[u8; 3]> = vec.into_arrays(); } #[test] fn arrays_from() { let slice: &[Srgb] = &[Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let slice_mut: &mut [Srgb] = &mut [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut array: [Srgb; 2] = [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _ = <&[[u8; 3]]>::arrays_from(slice); let _ = <&[[u8; 3]]>::arrays_from(&array); let _ = <&mut [[u8; 3]]>::arrays_from(slice_mut); let _ = <&mut [[u8; 3]]>::arrays_from(&mut array); let _ = <[[u8; 3]; 2]>::arrays_from(array); } #[cfg(feature = "alloc")] #[test] fn arrays_from_alloc() { let mut slice_box: Box<[Srgb]> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)].into_boxed_slice(); let mut vec: Vec> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _ = <&[[u8; 3]]>::arrays_from(&slice_box); let _ = <&[[u8; 3]]>::arrays_from(&vec); let _ = <&mut [[u8; 3]]>::arrays_from(&mut slice_box); let _ = <&mut [[u8; 3]]>::arrays_from(&mut vec); let _ = Box::<[[u8; 3]]>::arrays_from(slice_box); let _ = Vec::<[u8; 3]>::arrays_from(vec); } } palette-0.7.5/src/cast/from_into_components_traits.rs000064400000000000000000000620721046102023000212200ustar 00000000000000use core::{convert::Infallible, fmt::Debug}; use crate::ArrayExt; use super::{ from_component_array, into_component_array, into_component_slice, into_component_slice_mut, try_from_component_slice, try_from_component_slice_mut, ArrayCast, SliceCastError, }; #[cfg(feature = "alloc")] use super::{ into_component_slice_box, into_component_vec, try_from_component_slice_box, try_from_component_vec, BoxedSliceCastError, VecCastError, }; /// Trait for trying to cast a collection of colors from a collection of color /// components without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Errors /// /// The cast will return an error if the cast fails, such as when the length of /// the input is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::TryFromComponents, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice: &[_] = &[64, 139, 10, 93, 18, 214]; /// let slice_mut: &mut [_] = &mut [64, 139, 10, 93, 18, 214]; /// let vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// assert_eq!( /// <[Srgb; 2]>::try_from_components(array), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]) /// ); /// /// assert_eq!( /// <&[Srgb]>::try_from_components(slice), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_ref()) /// ); /// /// assert_eq!( /// <&mut [Srgb]>::try_from_components(slice_mut), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_mut()) /// ); /// /// assert_eq!( /// Vec::>::try_from_components(vec), /// Ok(vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]) /// ); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::TryFromComponents, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let mut vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// assert_eq!( /// <&[Srgb]>::try_from_components(&array), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_ref()) /// ); /// /// assert_eq!( /// <&mut [Srgb]>::try_from_components(&mut vec), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_mut()) /// ); /// ``` /// /// This produces an error: /// /// ``` /// use palette::{cast::TryFromComponents, Srgb}; /// /// let components = &[64, 139, 10, 93, 18]; // Not a multiple of 3 /// assert!(<&[Srgb]>::try_from_components(components).is_err()); /// ``` pub trait TryFromComponents: Sized { /// The error for when `try_from_components` fails to cast. type Error; /// Try to cast a collection of color components into an collection of /// colors. /// /// Return an error if the conversion can't be done, such as when the number /// of items in `components` isn't a multiple of the number of components in /// the color type. fn try_from_components(components: C) -> Result; } impl TryFromComponents<[T; N]> for [C; M] where C: ArrayCast, C::Array: ArrayExt, { type Error = Infallible; // We don't provide a `try_*` option for arrays. #[inline] fn try_from_components(components: [T; N]) -> Result { Ok(from_component_array(components)) } } macro_rules! impl_try_from_components_slice { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C $(, $($ty_input)+)?> TryFromComponents<&'a $owning> for &'a [C] where T: 'a, C: ArrayCast, C::Array: ArrayExt, { type Error = SliceCastError; #[inline] fn try_from_components(components: &'a $owning) -> Result { try_from_component_slice(components) } } impl<'a, T, C $(, $($ty_input)+)?> TryFromComponents<&'a mut $owning> for &'a mut [C] where T: 'a, C: ArrayCast, C::Array: ArrayExt, { type Error = SliceCastError; #[inline] fn try_from_components(components: &'a mut $owning) -> Result { try_from_component_slice_mut(components) } } )* }; } impl_try_from_components_slice!([T], [T; N] where (const N: usize)); #[cfg(feature = "alloc")] impl_try_from_components_slice!(alloc::boxed::Box<[T]>, alloc::vec::Vec); #[cfg(feature = "alloc")] impl TryFromComponents> for alloc::boxed::Box<[C]> where C: ArrayCast, C::Array: ArrayExt, { type Error = BoxedSliceCastError; #[inline] fn try_from_components(components: alloc::boxed::Box<[T]>) -> Result { try_from_component_slice_box(components) } } #[cfg(feature = "alloc")] impl TryFromComponents> for alloc::vec::Vec where C: ArrayCast, C::Array: ArrayExt, { type Error = VecCastError; #[inline] fn try_from_components(components: alloc::vec::Vec) -> Result { try_from_component_vec(components) } } /// Trait for casting a collection of colors from a collection of color /// components without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Panics /// /// The cast will panic if the cast fails, such as when the length of the input /// is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::FromComponents, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice: &[_] = &[64, 139, 10, 93, 18, 214]; /// let slice_mut: &mut [_] = &mut [64, 139, 10, 93, 18, 214]; /// let vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// assert_eq!( /// <[Srgb; 2]>::from_components(array), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// <&[Srgb]>::from_components(slice), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// <&mut [Srgb]>::from_components(slice_mut), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// Vec::>::from_components(vec), /// vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::FromComponents, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let mut vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// assert_eq!( /// <&[Srgb]>::from_components(&array), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// /// assert_eq!( /// <&mut [Srgb]>::from_components(&mut vec), /// [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)] /// ); /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast::FromComponents, Srgb}; /// /// let components = &[64, 139, 10, 93, 18, 214, 0, 123]; // Not a multiple of 3 /// <&[Srgb]>::from_components(components); /// ``` pub trait FromComponents { /// Cast a collection of color components into an collection of colors. /// /// ## Panics /// If the conversion can't be done, such as when the number of items in /// `components` isn't a multiple of the number of components in the color /// type. fn from_components(components: C) -> Self; } impl FromComponents for T where T: TryFromComponents, T::Error: Debug, { #[inline] fn from_components(components: C) -> Self { Self::try_from_components(components).unwrap() } } /// Trait for casting a collection of colors into a collection of color /// components without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::IntoComponents, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice: &[_] = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice_mut: &mut [_] = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(array.into_components(), [64, 139, 10, 93, 18, 214]); /// assert_eq!(slice.into_components(), [64, 139, 10, 93, 18, 214]); /// assert_eq!(slice_mut.into_components(), [64, 139, 10, 93, 18, 214]); /// assert_eq!(vec.into_components(), vec![64, 139, 10, 93, 18, 214]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::IntoComponents, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let mut vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!((&array).into_components(), [64, 139, 10, 93, 18, 214]); /// assert_eq!((&mut vec).into_components(), [64, 139, 10, 93, 18, 214]); /// ``` pub trait IntoComponents { /// Cast this collection of colors into a collection of color components. fn into_components(self) -> C; } impl IntoComponents<[T; M]> for [C; N] where C: ArrayCast, C::Array: ArrayExt, { #[inline] fn into_components(self) -> [T; M] { into_component_array(self) } } macro_rules! impl_into_components_slice { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, T, C $(, $($ty_input)+)?> IntoComponents<&'a [T]> for &'a $owning where T: 'a, C: ArrayCast, C::Array: ArrayExt, { #[inline] fn into_components(self) -> &'a [T] { into_component_slice(self) } } impl<'a, T, C $(, $($ty_input)+)?> IntoComponents<&'a mut [T]> for &'a mut $owning where T: 'a, C: ArrayCast, C::Array: ArrayExt, { #[inline] fn into_components(self) -> &'a mut [T] { into_component_slice_mut(self) } } )* }; } impl_into_components_slice!([C], [C; N] where (const N: usize)); #[cfg(feature = "alloc")] impl_into_components_slice!(alloc::boxed::Box<[C]>, alloc::vec::Vec); #[cfg(feature = "alloc")] impl IntoComponents> for alloc::boxed::Box<[C]> where C: ArrayCast, C::Array: ArrayExt, { #[inline] fn into_components(self) -> alloc::boxed::Box<[T]> { into_component_slice_box(self) } } #[cfg(feature = "alloc")] impl IntoComponents> for alloc::vec::Vec where C: ArrayCast, C::Array: ArrayExt, { #[inline] fn into_components(self) -> alloc::vec::Vec { into_component_vec(self) } } /// Trait for casting a collection of color components into a collection of /// colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::ComponentsFrom, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice: &[_] = &[Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let slice_mut: &mut [_] = &mut [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(<[_; 6]>::components_from(array), [64, 139, 10, 93, 18, 214]); /// assert_eq!(<&[_]>::components_from(slice), [64, 139, 10, 93, 18, 214]); /// assert_eq!(<&mut [_]>::components_from(slice_mut), [64, 139, 10, 93, 18, 214]); /// assert_eq!(Vec::<_>::components_from(vec), vec![64, 139, 10, 93, 18, 214]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::ComponentsFrom, Srgb}; /// /// let array: [_; 2] = [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// let mut vec: Vec<_> = vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]; /// /// assert_eq!(<&[_]>::components_from(&array), [64, 139, 10, 93, 18, 214]); /// assert_eq!(<&mut [_]>::components_from(&mut vec), [64, 139, 10, 93, 18, 214]); /// ``` pub trait ComponentsFrom { /// Cast a collection of colors into a collection of color components. fn components_from(colors: C) -> Self; } impl ComponentsFrom for T where C: IntoComponents, { #[inline] fn components_from(colors: C) -> Self { colors.into_components() } } /// Trait for trying to cast a collection of color components from a collection /// of colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Errors /// /// The cast will return an error if the cast fails, such as when the length of /// the input is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::TryComponentsInto, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice: &[_] = &[64, 139, 10, 93, 18, 214]; /// let slice_mut: &mut [_] = &mut [64, 139, 10, 93, 18, 214]; /// let vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// assert_eq!( /// array.try_components_into(), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]) /// ); /// /// assert_eq!( /// slice.try_components_into(), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_ref()) /// ); /// /// assert_eq!( /// slice_mut.try_components_into(), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_mut()) /// ); /// /// assert_eq!( /// vec.try_components_into(), /// Ok(vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]) /// ); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::TryComponentsInto, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let mut vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// assert_eq!( /// (&array).try_components_into(), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_ref()) /// ); /// /// assert_eq!( /// (&mut vec).try_components_into(), /// Ok([Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)].as_mut()) /// ); /// ``` /// /// This produces an error: /// /// ``` /// use palette::{cast::TryComponentsInto, Srgb}; /// /// let components = &[64, 139, 10, 93, 18]; // Not a multiple of 3 /// let colors: Result<&[Srgb], _> = components.try_components_into(); /// assert!(colors.is_err()); /// ``` pub trait TryComponentsInto: Sized { /// The error for when `try_into_colors` fails to cast. type Error; /// Try to cast this collection of color components into a collection of /// colors. /// /// Return an error if the conversion can't be done, such as when the number /// of items in `self` isn't a multiple of the number of components in the /// color type. fn try_components_into(self) -> Result; } /// Trait for casting a collection of color components from a collection of /// colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Panics /// /// The cast will panic if the cast fails, such as when the length of the input /// is not a multiple of the color's array length. /// /// ## Examples /// /// ``` /// use palette::{cast::ComponentsInto, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let slice: &[_] = &[64, 139, 10, 93, 18, 214]; /// let slice_mut: &mut [_] = &mut [64, 139, 10, 93, 18, 214]; /// let vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// let colors: [Srgb; 2] = array.components_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &[Srgb] = slice.components_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = slice_mut.components_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: Vec> = vec.components_into(); /// assert_eq!(colors, vec![Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::ComponentsInto, Srgb}; /// /// let array: [_; 6] = [64, 139, 10, 93, 18, 214]; /// let mut vec: Vec<_> = vec![64, 139, 10, 93, 18, 214]; /// /// let colors: &[Srgb] = (&array).components_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// /// let colors: &mut [Srgb] = (&mut vec).components_into(); /// assert_eq!(colors, [Srgb::new(64u8, 139, 10), Srgb::new(93, 18, 214)]); /// ``` /// /// This panics: /// /// ```should_panic /// use palette::{cast::ComponentsInto, Srgb}; /// /// let components = &[64, 139, 10, 93, 18, 214, 0, 123]; // Not a multiple of 3 /// let colors: &[Srgb] = components.components_into(); /// ``` pub trait ComponentsInto { /// Cast this collection of color components into a collection of colors. /// /// ## Panics /// If the conversion can't be done, such as when the number of items in /// `self` isn't a multiple of the number of components in the color type. fn components_into(self) -> C; } impl ComponentsInto for T where T: TryComponentsInto, T::Error: Debug, { #[inline] fn components_into(self) -> C { self.try_components_into().unwrap() } } impl TryComponentsInto for T where C: TryFromComponents, { type Error = C::Error; #[inline] fn try_components_into(self) -> Result { C::try_from_components(self) } } #[cfg(test)] mod test { use crate::Srgb; use super::{ ComponentsFrom, ComponentsInto, FromComponents, IntoComponents, TryComponentsInto, TryFromComponents, }; #[test] fn try_from_components() { let slice: &[u8] = &[1, 2, 3, 4, 5, 6]; let slice_mut: &mut [u8] = &mut [1, 2, 3, 4, 5, 6]; let mut array: [u8; 6] = [1, 2, 3, 4, 5, 6]; let _ = <&[Srgb]>::try_from_components(slice).unwrap(); let _ = <&[Srgb]>::try_from_components(&array).unwrap(); let _ = <&mut [Srgb]>::try_from_components(slice_mut).unwrap(); let _ = <&mut [Srgb]>::try_from_components(&mut array).unwrap(); let _ = <[Srgb; 2]>::try_from_components(array).unwrap(); } #[cfg(feature = "alloc")] #[test] fn try_from_components_alloc() { let mut slice_box: Box<[u8]> = vec![1, 2, 3, 4, 5, 6].into_boxed_slice(); let mut vec: Vec = vec![1, 2, 3, 4, 5, 6]; let _ = <&[Srgb]>::try_from_components(&slice_box).unwrap(); let _ = <&[Srgb]>::try_from_components(&vec).unwrap(); let _ = <&mut [Srgb]>::try_from_components(&mut slice_box).unwrap(); let _ = <&mut [Srgb]>::try_from_components(&mut vec).unwrap(); let _ = Box::<[Srgb]>::try_from_components(slice_box).unwrap(); let _ = Vec::>::try_from_components(vec).unwrap(); } #[test] fn try_components_into() { let slice: &[u8] = &[1, 2, 3, 4, 5, 6]; let slice_mut: &mut [u8] = &mut [1, 2, 3, 4, 5, 6]; let mut array: [u8; 6] = [1, 2, 3, 4, 5, 6]; let _: &[Srgb] = slice.try_components_into().unwrap(); let _: &[Srgb] = (&array).try_components_into().unwrap(); let _: &mut [Srgb] = slice_mut.try_components_into().unwrap(); let _: &mut [Srgb] = (&mut array).try_components_into().unwrap(); let _: [Srgb; 2] = array.try_components_into().unwrap(); } #[cfg(feature = "alloc")] #[test] fn try_components_into_alloc() { let mut slice_box: Box<[u8]> = vec![1, 2, 3, 4, 5, 6].into_boxed_slice(); let mut vec: Vec = vec![1, 2, 3, 4, 5, 6]; let _: &[Srgb] = (&slice_box).try_components_into().unwrap(); let _: &[Srgb] = (&vec).try_components_into().unwrap(); let _: &mut [Srgb] = (&mut slice_box).try_components_into().unwrap(); let _: &mut [Srgb] = (&mut vec).try_components_into().unwrap(); let _: Box<[Srgb]> = slice_box.try_components_into().unwrap(); let _: Vec> = vec.try_components_into().unwrap(); } #[test] fn from_components() { let slice: &[u8] = &[1, 2, 3, 4, 5, 6]; let slice_mut: &mut [u8] = &mut [1, 2, 3, 4, 5, 6]; let mut array: [u8; 6] = [1, 2, 3, 4, 5, 6]; let _ = <&[Srgb]>::from_components(slice); let _ = <&[Srgb]>::from_components(&array); let _ = <&mut [Srgb]>::from_components(slice_mut); let _ = <&mut [Srgb]>::from_components(&mut array); let _ = <[Srgb; 2]>::from_components(array); } #[cfg(feature = "alloc")] #[test] fn from_components_alloc() { let mut slice_box: Box<[u8]> = vec![1, 2, 3, 4, 5, 6].into_boxed_slice(); let mut vec: Vec = vec![1, 2, 3, 4, 5, 6]; let _ = <&[Srgb]>::from_components(&slice_box); let _ = <&[Srgb]>::from_components(&vec); let _ = <&mut [Srgb]>::from_components(&mut slice_box); let _ = <&mut [Srgb]>::from_components(&mut vec); let _ = Box::<[Srgb]>::from_components(slice_box); let _ = Vec::>::from_components(vec); } #[test] fn components_into() { let slice: &[u8] = &[1, 2, 3, 4, 5, 6]; let slice_mut: &mut [u8] = &mut [1, 2, 3, 4, 5, 6]; let mut array: [u8; 6] = [1, 2, 3, 4, 5, 6]; let _: &[Srgb] = slice.components_into(); let _: &[Srgb] = (&array).components_into(); let _: &mut [Srgb] = slice_mut.components_into(); let _: &mut [Srgb] = (&mut array).components_into(); let _: [Srgb; 2] = array.components_into(); } #[cfg(feature = "alloc")] #[test] fn components_into_alloc() { let mut slice_box: Box<[u8]> = vec![1, 2, 3, 4, 5, 6].into_boxed_slice(); let mut vec: Vec = vec![1, 2, 3, 4, 5, 6]; let _: &[Srgb] = (&slice_box).components_into(); let _: &[Srgb] = (&vec).components_into(); let _: &mut [Srgb] = (&mut slice_box).components_into(); let _: &mut [Srgb] = (&mut vec).components_into(); let _: Box<[Srgb]> = slice_box.components_into(); let _: Vec> = vec.components_into(); } #[test] fn into_components() { let slice: &[Srgb] = &[Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let slice_mut: &mut [Srgb] = &mut [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut array: [Srgb; 2] = [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _: &[u8] = slice.into_components(); let _: &[u8] = (&array).into_components(); let _: &mut [u8] = slice_mut.into_components(); let _: &mut [u8] = (&mut array).into_components(); let _: [u8; 6] = array.into_components(); } #[cfg(feature = "alloc")] #[test] fn into_components_alloc() { let mut slice_box: Box<[Srgb]> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)].into_boxed_slice(); let mut vec: Vec> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _: &[u8] = (&slice_box).into_components(); let _: &[u8] = (&vec).into_components(); let _: &mut [u8] = (&mut slice_box).into_components(); let _: &mut [u8] = (&mut vec).into_components(); let _: Box<[u8]> = slice_box.into_components(); let _: Vec = vec.into_components(); } #[test] fn components_from() { let slice: &[Srgb] = &[Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let slice_mut: &mut [Srgb] = &mut [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let mut array: [Srgb; 2] = [Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _ = <&[u8]>::components_from(slice); let _ = <&[u8]>::components_from(&array); let _ = <&mut [u8]>::components_from(slice_mut); let _ = <&mut [u8]>::components_from(&mut array); let _ = <[u8; 6]>::components_from(array); } #[cfg(feature = "alloc")] #[test] fn components_from_alloc() { let mut slice_box: Box<[Srgb]> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)].into_boxed_slice(); let mut vec: Vec> = vec![Srgb::new(1, 2, 3), Srgb::new(4, 5, 6)]; let _ = <&[u8]>::components_from(&slice_box); let _ = <&[u8]>::components_from(&vec); let _ = <&mut [u8]>::components_from(&mut slice_box); let _ = <&mut [u8]>::components_from(&mut vec); let _ = Box::<[u8]>::components_from(slice_box); let _ = Vec::::components_from(vec); } } palette-0.7.5/src/cast/from_into_uints_traits.rs000064400000000000000000000404751046102023000202000ustar 00000000000000use super::{ from_uint_array, from_uint_slice, from_uint_slice_mut, into_uint_array, into_uint_slice, into_uint_slice_mut, UintCast, }; #[cfg(feature = "alloc")] use super::{from_uint_slice_box, from_uint_vec, into_uint_slice_box, into_uint_vec}; /// Trait for casting a collection of colors from a collection of unsigned /// integers without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::FromUints, rgb::PackedArgb, Srgba}; /// /// let array: [_; 2] = [0xFF17C64C, 0xFF5D12D6]; /// let slice: &[_] = &[0xFF17C64C, 0xFF5D12D6]; /// let slice_mut: &mut [_] = &mut [0xFF17C64C, 0xFF5D12D6]; /// let vec: Vec<_> = vec![0xFF17C64C, 0xFF5D12D6]; /// /// assert_eq!( /// <[PackedArgb; 2]>::from_uints(array), /// [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// assert_eq!( /// <&[PackedArgb]>::from_uints(slice), /// [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// assert_eq!( /// <&mut [PackedArgb]>::from_uints(slice_mut), /// [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// assert_eq!( /// Vec::::from_uints(vec), /// vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::FromUints, rgb::PackedArgb, Srgba}; /// /// let array: [_; 2] = [0xFF17C64C, 0xFF5D12D6]; /// let mut vec: Vec<_> = vec![0xFF17C64C, 0xFF5D12D6]; /// /// assert_eq!( /// <&[PackedArgb]>::from_uints(&array), /// [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// /// assert_eq!( /// <&mut [PackedArgb]>::from_uints(&mut vec), /// [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ] /// ); /// ``` pub trait FromUints { /// Cast a collection of unsigned integers into an collection of colors. fn from_uints(uints: U) -> Self; } impl FromUints<[C::Uint; N]> for [C; N] where C: UintCast, { #[inline] fn from_uints(uints: [C::Uint; N]) -> Self { from_uint_array(uints) } } macro_rules! impl_from_uints_slice { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, C $(, $($ty_input)+)?> FromUints<&'a $owning> for &'a [C] where C: UintCast, { #[inline] fn from_uints(uints: &'a $owning) -> Self { from_uint_slice(uints) } } impl<'a, C $(, $($ty_input)+)?> FromUints<&'a mut $owning> for &'a mut [C] where C: UintCast, { #[inline] fn from_uints(uints: &'a mut $owning) -> Self { from_uint_slice_mut(uints) } } )* }; } impl_from_uints_slice!([C::Uint], [C::Uint; N] where (const N: usize)); #[cfg(feature = "alloc")] impl_from_uints_slice!(alloc::boxed::Box<[C::Uint]>, alloc::vec::Vec); #[cfg(feature = "alloc")] impl FromUints> for alloc::boxed::Box<[C]> where C: UintCast, { #[inline] fn from_uints(uints: alloc::boxed::Box<[C::Uint]>) -> Self { from_uint_slice_box(uints) } } #[cfg(feature = "alloc")] impl FromUints> for alloc::vec::Vec where C: UintCast, { #[inline] fn from_uints(uints: alloc::vec::Vec) -> Self { from_uint_vec(uints) } } /// Trait for casting a collection of colors into a collection of unsigned /// integers without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::IntoUints, rgb::PackedArgb, Srgba}; /// /// let array: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let slice: &[PackedArgb] = &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let slice_mut: &mut [PackedArgb] = &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let vec: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!(array.into_uints(), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(slice.into_uints(), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(slice_mut.into_uints(), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(vec.into_uints(), vec![0xFF17C64C, 0xFF5D12D6]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::IntoUints, rgb::PackedArgb, Srgba}; /// /// let array: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let mut vec: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!((&array).into_uints(), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!((&mut vec).into_uints(), [0xFF17C64C, 0xFF5D12D6]); /// ``` pub trait IntoUints { /// Cast this collection of colors into a collection of unsigned integers. fn into_uints(self) -> U; } impl IntoUints<[C::Uint; N]> for [C; N] where C: UintCast, { #[inline] fn into_uints(self) -> [C::Uint; N] { into_uint_array(self) } } macro_rules! impl_into_uints_slice { ($($owning:ty $(where ($($ty_input:tt)+))?),*) => { $( impl<'a, C $(, $($ty_input)+)?> IntoUints<&'a [C::Uint]> for &'a $owning where C: UintCast, { #[inline] fn into_uints(self) -> &'a [C::Uint] { into_uint_slice(self) } } impl<'a, C $(, $($ty_input)+)?> IntoUints<&'a mut [C::Uint]> for &'a mut $owning where C: UintCast, { #[inline] fn into_uints(self) -> &'a mut [C::Uint] { into_uint_slice_mut(self) } } )* }; } impl_into_uints_slice!([C], [C; M] where (const M: usize)); #[cfg(feature = "alloc")] impl_into_uints_slice!(alloc::boxed::Box<[C]>, alloc::vec::Vec); #[cfg(feature = "alloc")] impl IntoUints> for alloc::boxed::Box<[C]> where C: UintCast, { #[inline] fn into_uints(self) -> alloc::boxed::Box<[C::Uint]> { into_uint_slice_box(self) } } #[cfg(feature = "alloc")] impl IntoUints> for alloc::vec::Vec where C: UintCast, { #[inline] fn into_uints(self) -> alloc::vec::Vec { into_uint_vec(self) } } /// Trait for casting a collection of unsigned integers from a collection of /// colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::UintsFrom, rgb::PackedArgb, Srgba}; /// /// let array: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let slice: &[PackedArgb] = &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let slice_mut: &mut [PackedArgb] = &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let vec: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!(<[_; 2]>::uints_from(array), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(<&[_]>::uints_from(slice), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(<&mut [_]>::uints_from(slice_mut), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(Vec::<_>::uints_from(vec), vec![0xFF17C64C, 0xFF5D12D6]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::UintsFrom, rgb::PackedArgb, Srgba}; /// /// let array: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// let mut vec: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!(<&[_]>::uints_from(&array), [0xFF17C64C, 0xFF5D12D6]); /// assert_eq!(<&mut [_]>::uints_from(&mut vec), [0xFF17C64C, 0xFF5D12D6]); /// ``` pub trait UintsFrom { /// Cast a collection of colors into a collection of unsigned integers. fn uints_from(colors: C) -> Self; } impl UintsFrom for U where C: IntoUints, { #[inline] fn uints_from(colors: C) -> Self { colors.into_uints() } } /// Trait for casting a collection of unsigned integers into a collection of /// colors without copying. /// /// This trait is meant as a more convenient alternative to the free functions /// in [`cast`][crate::cast], to allow method chaining among other things. /// /// ## Examples /// /// ``` /// use palette::{cast::UintsInto, rgb::PackedArgb, Srgba}; /// /// let array: [_; 2] = [0xFF17C64C, 0xFF5D12D6]; /// let slice: &[_] = &[0xFF17C64C, 0xFF5D12D6]; /// let slice_mut: &mut [_] = &mut [0xFF17C64C, 0xFF5D12D6]; /// let vec: Vec<_> = vec![0xFF17C64C, 0xFF5D12D6]; /// /// let colors: [PackedArgb; 2] = array.uints_into(); /// assert_eq!(colors, [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]); /// /// let colors: &[PackedArgb] = slice.uints_into(); /// assert_eq!(colors, [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]); /// /// let colors: &mut [PackedArgb] = slice_mut.uints_into(); /// assert_eq!(colors, [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]); /// /// let colors: Vec = vec.uints_into(); /// assert_eq!(colors, vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]); /// ``` /// /// Owning types can be cast as slices, too: /// /// ``` /// use palette::{cast::UintsInto, rgb::PackedArgb, Srgba}; /// /// let array: [_; 2] = [0xFF17C64C, 0xFF5D12D6]; /// let mut vec: Vec<_> = vec![0xFF17C64C, 0xFF5D12D6]; /// /// let colors: &[PackedArgb] = (&array).uints_into(); /// assert_eq!(colors, [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]); /// /// let colors: &mut [PackedArgb] = (&mut vec).uints_into(); /// assert_eq!(colors, [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]); /// ``` pub trait UintsInto { /// Cast this collection of unsigned integers into a collection of colors. fn uints_into(self) -> C; } impl UintsInto for U where C: FromUints, { #[inline] fn uints_into(self) -> C { C::from_uints(self) } } #[cfg(test)] mod test { use crate::{rgb::PackedRgba, Srgba}; use super::{FromUints, IntoUints, UintsFrom, UintsInto}; #[test] fn from_uints() { let slice: &[u32] = &[0x01020304, 0x05060708]; let slice_mut: &mut [u32] = &mut [0x01020304, 0x05060708]; let mut array: [u32; 2] = [0x01020304, 0x05060708]; let _ = <&[PackedRgba]>::from_uints(slice); let _ = <&[PackedRgba]>::from_uints(&array); let _ = <&mut [PackedRgba]>::from_uints(slice_mut); let _ = <&mut [PackedRgba]>::from_uints(&mut array); let _ = <[PackedRgba; 2]>::from_uints(array); } #[cfg(feature = "alloc")] #[test] fn from_uints_alloc() { let mut slice_box: Box<[u32]> = vec![0x01020304, 0x05060708].into_boxed_slice(); let mut vec: Vec = vec![0x01020304, 0x05060708]; let _ = <&[PackedRgba]>::from_uints(&slice_box); let _ = <&[PackedRgba]>::from_uints(&vec); let _ = <&mut [PackedRgba]>::from_uints(&mut slice_box); let _ = <&mut [PackedRgba]>::from_uints(&mut vec); let _ = Box::<[PackedRgba]>::from_uints(slice_box); let _ = Vec::::from_uints(vec); } #[test] fn uints_into() { let slice: &[u32] = &[0x01020304, 0x05060708]; let slice_mut: &mut [u32] = &mut [0x01020304, 0x05060708]; let mut array: [u32; 2] = [0x01020304, 0x05060708]; let _: &[PackedRgba] = slice.uints_into(); let _: &[PackedRgba] = (&array).uints_into(); let _: &mut [PackedRgba] = slice_mut.uints_into(); let _: &mut [PackedRgba] = (&mut array).uints_into(); let _: [PackedRgba; 2] = array.uints_into(); } #[cfg(feature = "alloc")] #[test] fn uints_into_alloc() { let mut slice_box: Box<[u32]> = vec![0x01020304, 0x05060708].into_boxed_slice(); let mut vec: Vec = vec![0x01020304, 0x05060708]; let _: &[PackedRgba] = (&slice_box).uints_into(); let _: &[PackedRgba] = (&vec).uints_into(); let _: &mut [PackedRgba] = (&mut slice_box).uints_into(); let _: &mut [PackedRgba] = (&mut vec).uints_into(); let _: Box<[PackedRgba]> = slice_box.uints_into(); let _: Vec = vec.uints_into(); } #[test] fn into_uints() { let slice: &[PackedRgba] = &[Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let slice_mut: &mut [PackedRgba] = &mut [Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let mut array: [PackedRgba; 2] = [Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let _: &[u32] = slice.into_uints(); let _: &[u32] = (&array).into_uints(); let _: &mut [u32] = slice_mut.into_uints(); let _: &mut [u32] = (&mut array).into_uints(); let _: [u32; 2] = array.into_uints(); } #[cfg(feature = "alloc")] #[test] fn into_uints_alloc() { let mut slice_box: Box<[PackedRgba]> = vec![Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()].into_boxed_slice(); let mut vec: Vec = vec![Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let _: &[u32] = (&slice_box).into_uints(); let _: &[u32] = (&vec).into_uints(); let _: &mut [u32] = (&mut slice_box).into_uints(); let _: &mut [u32] = (&mut vec).into_uints(); let _: Box<[u32]> = slice_box.into_uints(); let _: Vec = vec.into_uints(); } #[test] fn uints_from() { let slice: &[PackedRgba] = &[Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let slice_mut: &mut [PackedRgba] = &mut [Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let mut array: [PackedRgba; 2] = [Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let _ = <&[u32]>::uints_from(slice); let _ = <&[u32]>::uints_from(&array); let _ = <&mut [u32]>::uints_from(slice_mut); let _ = <&mut [u32]>::uints_from(&mut array); let _ = <[u32; 2]>::uints_from(array); } #[cfg(feature = "alloc")] #[test] fn uints_from_alloc() { let mut slice_box: Box<[PackedRgba]> = vec![Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()].into_boxed_slice(); let mut vec: Vec = vec![Srgba::new(1, 2, 3, 4).into(), Srgba::new(5, 6, 7, 8).into()]; let _ = <&[u32]>::uints_from(&slice_box); let _ = <&[u32]>::uints_from(&vec); let _ = <&mut [u32]>::uints_from(&mut slice_box); let _ = <&mut [u32]>::uints_from(&mut vec); let _ = Box::<[u32]>::uints_from(slice_box); let _ = Vec::::uints_from(vec); } } palette-0.7.5/src/cast/packed.rs000064400000000000000000000151611046102023000146150ustar 00000000000000use core::marker::PhantomData; use crate::cast::UintCast; use super::ArrayCast; /// A color packed into a compact format, such as an unsigned integer. /// /// `Packed` implements [ArrayCast](crate::cast::ArrayCast) and /// [UintCast](crate::cast::UintCast) so it can easily be constructed from /// slices, arrays and unsigned integers. /// /// ``` /// // `PackedArgb` is an alias for `Packed`. /// use palette::{rgb::PackedArgb, cast::UintsAs}; /// /// let raw = [0x7F0080u32, 0x60BBCC]; /// let colors: &[PackedArgb] = raw.uints_as(); /// /// assert_eq!(colors.len(), 2); /// assert_eq!(colors[0].color, 0x7F0080); /// assert_eq!(colors[1].color, 0x60BBCC); /// ``` /// /// ## Packed Integer Type Represented in `u32`. /// /// A common example of a packed format is when an RGBA color is encoded as a /// hexadecimal number (such as `0x7F0080` from above). Two hexadecimal digits /// (8-bits) express each value of the Red, Green, Blue, and Alpha components in /// the RGBA color. /// /// Note that conversion from float to integer component types in Palette rounds /// to nearest even: an `Rgb` component of `0.5` will convert to `0x80`/`128`, /// not `0x7F`/`127`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Srgb, Srgba}; /// use palette::rgb::{PackedArgb, PackedRgba}; /// /// let packed: PackedArgb = Srgb::new(0.5, 0.0, 0.5).into_format().into(); /// assert_eq!(0xFF80_0080, packed.color); /// /// let unpacked: Srgba = PackedRgba::from(0xFFFF_FF80u32).into(); /// assert_relative_eq!( /// Srgba::new(1.0, 1.0, 1.0, 0.5), /// unpacked.into_format(), /// epsilon = 0.01 /// ); /// /// // By default, `Packed` uses `Argb` order for creating `Rgb` colors to make /// // entering 6-digit hex numbers more convenient /// let rgb = Srgb::from(0xFF8000); /// assert_eq!(Srgb::new(0xFF, 0x80, 0x00), rgb); /// /// let rgba = Srgba::from(0xFF80007F); /// assert_eq!(Srgba::new(0xFF, 0x80, 0x00, 0x7F), rgba); /// ``` /// /// When an `Rgb` type is packed, the alpha value will be `0xFF` in the /// corresponding `u32`. Converting from a packed color type back to an `Rgb` /// type will disregard the alpha value. #[derive(Debug, PartialEq, Eq)] #[repr(transparent)] pub struct Packed { /// The color packed into a type `P`, such as `u32` or `[u8; 4]`. pub color: P, /// The channel order for the color components in the packed data. See /// [`ComponentOrder`]. pub channel_order: PhantomData, } impl Packed { /// Transform a color value into a packed memory representation. #[inline] pub fn pack(color: C) -> Self where O: ComponentOrder, { Packed { color: O::pack(color), channel_order: PhantomData, } } /// Transform a packed color into a regular color value. #[inline] pub fn unpack(self) -> C where O: ComponentOrder, { O::unpack(self.color) } } impl Copy for Packed where P: Copy {} impl Clone for Packed where P: Clone, { #[inline] fn clone(&self) -> Self { Self { color: self.color.clone(), channel_order: PhantomData, } } } // Safety: // // `Packed` is a transparent wrapper around `[u8; N]`, which fulfills the // requirements of `ArrayCast`. unsafe impl ArrayCast for Packed { type Array = [T; N]; } // Safety: // // `Packed` is a transparent wrapper around `u8`, which fulfills the // requirements of `UintCast`. unsafe impl UintCast for Packed { type Uint = u8; } // Safety: // // `Packed` is a transparent wrapper around `u16`, which fulfills the // requirements of `UintCast`. unsafe impl UintCast for Packed { type Uint = u16; } // Safety: // // `Packed` is a transparent wrapper around `u32`, which fulfills the // requirements of `UintCast`. unsafe impl UintCast for Packed { type Uint = u32; } // Safety: // // `Packed` is a transparent wrapper around `u64`, which fulfills the // requirements of `UintCast`. unsafe impl UintCast for Packed { type Uint = u64; } // Safety: // // `Packed` is a transparent wrapper around `u128`, which fulfills the // requirements of `UintCast`. unsafe impl UintCast for Packed { type Uint = u128; } impl_array_casts!([O, T, const N: usize] Packed, [T; N]); impl_uint_casts_self!(Packed, P, where Packed: UintCast); impl_uint_casts_other!([O] Packed, u8); impl_uint_casts_other!([O] Packed, u16); impl_uint_casts_other!([O] Packed, u32); impl_uint_casts_other!([O] Packed, u64); impl_uint_casts_other!([O] Packed, u128); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Packed where P: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Packed where P: bytemuck::Pod {} /// Packs and unpacks color types with some component order. /// /// As an example, RGBA channels may be ordered as `ABGR`, `ARGB`, `BGRA`, or /// `RGBA`. pub trait ComponentOrder { /// Combine the components of a color into the packed format. fn pack(color: C) -> P; /// Split the packed color into its separate components. fn unpack(packed: P) -> C; } impl ComponentOrder for T where T: ComponentOrder, { #[inline] fn pack(color: C) -> u8 { let [packed] = T::pack(color); packed } #[inline] fn unpack(packed: u8) -> C { T::unpack([packed]) } } impl ComponentOrder for T where T: ComponentOrder, { #[inline] fn pack(color: C) -> u16 { u16::from_be_bytes(T::pack(color)) } #[inline] fn unpack(packed: u16) -> C { T::unpack(packed.to_be_bytes()) } } impl ComponentOrder for T where T: ComponentOrder, { #[inline] fn pack(color: C) -> u32 { u32::from_be_bytes(T::pack(color)) } #[inline] fn unpack(packed: u32) -> C { T::unpack(packed.to_be_bytes()) } } impl ComponentOrder for T where T: ComponentOrder, { #[inline] fn pack(color: C) -> u64 { u64::from_be_bytes(T::pack(color)) } #[inline] fn unpack(packed: u64) -> C { T::unpack(packed.to_be_bytes()) } } impl ComponentOrder for T where T: ComponentOrder, { #[inline] fn pack(color: C) -> u128 { u128::from_be_bytes(T::pack(color)) } #[inline] fn unpack(packed: u128) -> C { T::unpack(packed.to_be_bytes()) } } palette-0.7.5/src/cast/uint.rs000064400000000000000000000377171046102023000143600ustar 00000000000000use core::mem::{transmute_copy, ManuallyDrop}; /// Marker trait for types that can be represented as an unsigned integer. /// /// A type that implements this trait is assumed to have the exact same memory /// layout and representation as an unsigned integer, with the current compile /// target's endianness. This implies a couple of useful properties: /// /// * Casting between `T` and `T::Uint` is free and will (or should) be /// optimized away. /// * `[T]` can be cast to and from `[T::Uint]`. /// /// This allows a number of common and useful optimizations, including casting /// buffers and reusing memory. It does however come with some strict /// requirements. /// /// ## Safety /// /// * The type must be inhabited (eg: no /// [Infallible](std::convert::Infallible)). /// * The type must allow any bit pattern (eg: either no requirements or some /// ability to recover from invalid values). /// * The type must be either a wrapper around `Self::Uint` or be safe to transmute to and from `Self::Uint`. /// * The type must not contain any internal padding. /// * The type must be `repr(C)` or `repr(transparent)`. /// * The type must have the same size and alignment as `Self::Uint`. /// /// Note also that the type is assumed to not implement `Drop`. This will /// rarely, if ever, be an issue. The requirements above ensures that the /// underlying field types stay the same and will be dropped. pub unsafe trait UintCast { /// An unsigned integer with the same size as `Self`. type Uint; } /// Cast from a color type to an unsigned integer. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let color: PackedArgb = Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(); /// assert_eq!(cast::into_uint(color), 0xFF17C64C); /// ``` /// /// It's also possible to use `From` and `Into` when casting built-in types: /// /// ``` /// use palette::Srgba; /// /// let color = Srgba::new(23u8, 198, 76, 255); /// /// // Integers implement `Into`: /// let uint1: u32 = color.into(); /// /// // Integers implement `From`: /// let uint2 = u32::from(color); /// ``` #[inline] pub fn into_uint(color: T) -> T::Uint where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // assert, ensures that transmuting `T` into `T::Uint` is safe. unsafe { transmute_copy(&ManuallyDrop::new(color)) } } /// Cast from an unsigned integer to a color type. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let color: PackedArgb = Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(); /// assert_eq!(cast::from_uint::(0xFF17C64C), color); /// ``` /// /// It's also possible to use `From` and `Into` when casting built-in types: /// /// ``` /// use palette::Srgba; /// /// let uint = 0xFF17C64C; /// /// // Integers implement `Into`: /// let color1: Srgba = uint.into(); /// /// // Colors implement `From`: /// let color2 = Srgba::from(uint); /// ``` #[inline] pub fn from_uint(uint: T::Uint) -> T where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // assert, ensures that transmuting `T::Uint` into `T` is safe. unsafe { transmute_copy(&ManuallyDrop::new(uint)) } } /// Cast from a color type reference to an unsigned integer reference. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let color: PackedArgb = Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(); /// assert_eq!(cast::into_uint_ref(&color), &0xFF17C64C); /// ``` #[inline] pub fn into_uint_ref(value: &T) -> &T::Uint where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let value: *const T = value; // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Uint` is safe. unsafe { &*value.cast::() } } /// Cast from an unsigned integer reference to a color type reference. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let color: PackedArgb = Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(); /// assert_eq!(cast::from_uint_ref::(&0xFF17C64C), &color); /// ``` #[inline] pub fn from_uint_ref(value: &T::Uint) -> &T where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let value: *const T::Uint = value; // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T::Uint` as `T` is safe. unsafe { &*value.cast::() } } /// Cast from a mutable color type reference to a mutable unsigned integer reference. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let mut color: PackedArgb = Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(); /// assert_eq!(cast::into_uint_mut(&mut color), &mut 0xFF17C64C); /// ``` #[inline] pub fn into_uint_mut(value: &mut T) -> &mut T::Uint where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let value: *mut T = value; // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Uint` is safe. unsafe { &mut *value.cast::() } } /// Cast from a mutable unsigned integer reference to a mutable color type reference. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let mut color: PackedArgb = Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(); /// assert_eq!(cast::from_uint_mut::(&mut 0xFF17C64C), &mut color); /// ``` #[inline] pub fn from_uint_mut(value: &mut T::Uint) -> &mut T where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let value: *mut T::Uint = value; // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T::Uint` as `T` is safe. unsafe { &mut *value.cast::() } } /// Cast from an array of colors to an array of unsigned integers. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// assert_eq!(cast::into_uint_array(colors), [0xFF17C64C, 0xFF5D12D6]) /// ``` #[inline] pub fn into_uint_array(values: [T; N]) -> [T::Uint; N] where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures transmuting `T` into `T::Uint` is safe. // The length is the same because the size is the same. unsafe { transmute_copy(&ManuallyDrop::new(values)) } } /// Cast from an array of unsigned integers to an array of colors. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: [PackedArgb; 2] = [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// assert_eq!(cast::from_uint_array::([0xFF17C64C, 0xFF5D12D6]), colors) /// ``` #[inline] pub fn from_uint_array(values: [T::Uint; N]) -> [T; N] where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures transmuting `T::Uint` into `T` is safe. // The length is the same because the size is the same. unsafe { transmute_copy(&ManuallyDrop::new(values)) } } /// Cast from a slice of colors to a slice of unsigned integers. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: &[PackedArgb] = &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// assert_eq!(cast::into_uint_slice(colors), &[0xFF17C64C, 0xFF5D12D6]) /// ``` #[inline] pub fn into_uint_slice(values: &[T]) -> &[T::Uint] where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Uint` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts(values.as_ptr().cast::(), values.len()) } } /// Cast from a slice of unsigned integers to a slice of colors. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: &[PackedArgb] = &[ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// assert_eq!(cast::from_uint_slice::(&[0xFF17C64C, 0xFF5D12D6]), colors) /// ``` #[inline] pub fn from_uint_slice(values: &[T::Uint]) -> &[T] where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T::Uint` as `T` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts(values.as_ptr().cast::(), values.len()) } } /// Cast from a mutable slice of colors to a mutable slice of unsigned integers. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: &mut [PackedArgb] = &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// assert_eq!(cast::into_uint_slice_mut(colors), &mut [0xFF17C64C, 0xFF5D12D6]) /// ``` #[inline] pub fn into_uint_slice_mut(values: &mut [T]) -> &mut [T::Uint] where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Uint` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts_mut(values.as_mut_ptr().cast::(), values.len()) } } /// Cast from a mutable slice of unsigned integers to a mutable slice of colors. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: &mut [PackedArgb] = &mut [ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// assert_eq!(cast::from_uint_slice_mut::(&mut [0xFF17C64C, 0xFF5D12D6]), colors) /// ``` #[inline] pub fn from_uint_slice_mut(values: &mut [T::Uint]) -> &mut [T] where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T::Uint` as `T` is safe. // The length is the same because the size is the same. unsafe { core::slice::from_raw_parts_mut(values.as_mut_ptr().cast::(), values.len()) } } /// Cast from a boxed slice of colors to a boxed slice of unsigned integers. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: Box<[PackedArgb]> = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ].into_boxed_slice(); /// /// assert_eq!( /// cast::into_uint_slice_box(colors), /// vec![0xFF17C64C, 0xFF5D12D6].into_boxed_slice() /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn into_uint_slice_box(values: alloc::boxed::Box<[T]>) -> alloc::boxed::Box<[T::Uint]> where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let raw: *mut [T::Uint] = into_uint_slice_mut(alloc::boxed::Box::leak(values)); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Uint` is safe. unsafe { alloc::boxed::Box::from_raw(raw) } } /// Cast from a boxed slice of unsigned integers to a boxed slice of colors. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: Box<[PackedArgb]> = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ].into_boxed_slice(); /// /// assert_eq!( /// cast::from_uint_slice_box(vec![0xFF17C64C, 0xFF5D12D6].into_boxed_slice()), /// colors /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn from_uint_slice_box(values: alloc::boxed::Box<[T::Uint]>) -> alloc::boxed::Box<[T]> where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let raw: *mut [T] = from_uint_slice_mut(alloc::boxed::Box::leak(values)); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T::Uint` as `T` is safe. unsafe { alloc::boxed::Box::from_raw(raw) } } /// Cast from a `Vec` of colors to a `Vec` of unsigned integers. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!( /// cast::into_uint_vec(colors), /// vec![0xFF17C64C, 0xFF5D12D6] /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn into_uint_vec(values: alloc::vec::Vec) -> alloc::vec::Vec where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let mut values = ManuallyDrop::new(values); let raw = values.as_mut_ptr(); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T` as `T::Uint` is safe. // Length and capacity are the same because the size is the same. unsafe { alloc::vec::Vec::from_raw_parts(raw.cast::(), values.len(), values.capacity()) } } /// Cast from a `Vec` of unsigned integers to a `Vec` of colors. /// /// ``` /// use palette::{cast, rgb::PackedArgb, Srgba}; /// /// let colors: Vec = vec![ /// Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// Srgba::new(0x5D, 0x12, 0xD6, 0xFF).into() /// ]; /// /// assert_eq!( /// cast::from_uint_vec::(vec![0xFF17C64C, 0xFF5D12D6]), /// colors /// ) /// ``` #[cfg(feature = "alloc")] #[inline] pub fn from_uint_vec(values: alloc::vec::Vec) -> alloc::vec::Vec where T: UintCast, { assert_eq!(core::mem::size_of::(), core::mem::size_of::()); assert_eq!(core::mem::align_of::(), core::mem::align_of::()); let mut values = ManuallyDrop::new(values); let raw = values.as_mut_ptr(); // Safety: The requirements of implementing `UintCast`, as well as the size // and alignment asserts, ensures that reading `T::Uint` as `T` is safe. // Length and capacity are the same because the size is the same. unsafe { alloc::vec::Vec::from_raw_parts(raw.cast::(), values.len(), values.capacity()) } } palette-0.7.5/src/cast.rs000064400000000000000000000150741046102023000133710ustar 00000000000000//! Traits and functions for casting colors to and from other data types. //! //! The functions and traits in this module cast without changing the underlying //! data. See the [`convert`](crate::convert) module for how to convert between //! color spaces. //! //! # Traits or Functions? //! //! This module provides both a set of traits ([`FromComponents`], //! [`IntoUints`], etc.) and a set of functions ([`from_component_slice`], //! [`into_uint_array`], etc.) that effectively implement the same //! functionality. The traits are all implemented using the functions, so they //! can be seen as bits of more implicit syntax sugar for the more explicit //! functions. //! //! A general recommendation is to use the traits, since they provide mostly //! better ergonomics. //! //! # Arrays and Slices //! //! Types that implement [`ArrayCast`] can be cast to and from arrays and slices //! with little to no overhead. This makes it easy to work with image buffers //! and types from other crates without having to copy the data first. //! //! Casting can be either be done with the free functions in this module, or //! using the helper traits. //! //! ## Casting Arrays //! //! Arrays can be type checked to have the correct size at compile time, making //! casting free after optimization has removed the overhead from asserts. The //! same is true for arrays in slices and `Vec`s, because the length stays the //! same after casting. //! //! ``` //! use palette::{cast::{self, ArraysAsMut}, Srgb, IntoColor}; //! //! let color = cast::from_array::>([23, 198, 76]).into_linear(); //! // Note: `Srgb::::from([23, 198, 76])` works too. //! //! let mut buffer = [[64, 139, 10], [93, 18, 214]]; //! let color_buffer: &mut [Srgb] = buffer.arrays_as_mut(); //! //! for destination in color_buffer { //! let linear_dst = destination.into_linear::(); //! *destination = (linear_dst + color).into_encoding(); //! } //! ``` //! //! Trying to cast a single array of the wrong size will not compile: //! //! ```compile_fail //! use palette::{cast, Srgb}; //! //! let color = cast::from_array::>([23, 198]); // Too few components. //! ``` //! //! ## Casting Component Buffers //! //! This is a common situation is image processing, where you have an image //! buffer, such as `&mut [u8]`, `&mut [f32]`, `Vec` or `Vec`, that you //! want to work with as colors. This buffer may, for example, be the content of //! an image file or shared with the GPU. //! //! The downside, compared to having fixed size arrays, is that the length //! cannot be statically known to be a multiple of the color type's array //! length. This adds a bit of error handling overhead, as well as for dividing //! or multiplying the length. //! //! ``` //! use palette::{cast::{self, TryComponentsAs}, Srgb}; //! //! let correct_buffer = [64, 139, 10, 93, 18, 214]; //! let should_be_ok: Result<&[Srgb], _> = correct_buffer.try_components_as(); //! assert!(should_be_ok.is_ok()); //! //! let incorrect_buffer = [64, 139, 10, 93, 18, 214, 198, 76]; //! let should_be_err: Result<&[Srgb], _> = incorrect_buffer.try_components_as(); //! assert!(should_be_err.is_err()); //! ``` //! //! An alternative, for when the length can be trusted to be correct, is to use //! methods without the `try_*` prefix, such as `ComponentsAs::components_as`, //! or the `from_component_*` functions, that panic on error. //! //! This works: //! //! ``` //! use palette::{cast::ComponentsAs, Srgb}; //! //! let correct_buffer = [64, 139, 10, 93, 18, 214]; //! let color_buffer: &[Srgb] = correct_buffer.components_as(); //! ``` //! //! But this panics: //! //! ```should_panic //! use palette::{cast::ComponentsAs, Srgb}; //! //! let incorrect_buffer = [64, 139, 10, 93, 18, 214, 198, 76]; //! let color_buffer: &[Srgb] = incorrect_buffer.components_as(); //! ``` //! //! ## Casting Single Colors //! //! The built-in color types implement `AsRef`, `AsMut`, `From`, `Into`, //! `TryFrom` and `TryInto` in addition to `ArrayCast` for convenient casting of //! single colors: //! //! ``` //! use core::convert::TryFrom; //! use palette::Srgb; //! //! let color = Srgb::from([23u8, 198, 76]); //! let array: [u8; 3] = color.into(); //! //! let slice: &[u8] = color.as_ref(); //! assert!(<&Srgb>::try_from(slice).is_ok()); //! //! let short_slice: &[f32] = &[0.1, 0.5]; //! assert!(<&Srgb>::try_from(short_slice).is_err()); // Too few components. //! ``` //! //! ## Component Order //! //! The component order in an array or slice is not always the same as in the //! color types. For example, a byte buffer that is encoded as ARGB will not //! cast to correct `Rgba` values. The components can be reordered after casting //! by using the [`Packed`] wrapper as an intermediate representation. //! //! ``` //! // `PackedArgb` is an alias for `Packed`. //! use palette::{rgb::PackedArgb, cast::ComponentsAs, Srgba}; //! //! let components = [1.0f32, 0.8, 0.2, 0.3, 1.0, 0.5, 0.7, 0.6]; //! let colors: &[PackedArgb<_>] = components.components_as(); //! //! // Notice how the alpha values have moved from the beginning to the end: //! assert_eq!(Srgba::from(colors[0]), Srgba::new(0.8, 0.2, 0.3, 1.0)); //! assert_eq!(Srgba::from(colors[1]), Srgba::new(0.5, 0.7, 0.6, 1.0)); //! ``` //! //! # Unsigned Integers //! //! Types that implement [`UintCast`] can be cast to and from unsigned integers //! of the same size. It's a bit more limited than slices and arrays but it's //! useful for common patterns like representing RGBA values as hexadecimal //! unsigned integers. //! //! The [`Packed`] wrapper can be used as an intermediate format to make //! unpacking the values as simple as `from` or `into`. It's also possible to //! choose a channel order to be something other than what the default `From` //! implementations would use. //! //! ``` //! // `PackedArgb` is an alias for `Packed`. //! use palette::{rgb::PackedArgb, cast::UintsAs, Srgba}; //! //! let raw = [0xFF7F0080u32, 0xFF60BBCC]; //! let colors: &[PackedArgb] = raw.uints_as(); //! //! assert_eq!(colors.len(), 2); //! assert_eq!(Srgba::from(colors[0]), Srgba::new(0x7F, 0x00, 0x80, 0xFF)); //! assert_eq!(Srgba::from(colors[1]), Srgba::new(0x60, 0xBB, 0xCC, 0xFF)); //! ``` mod array; mod as_arrays_traits; mod as_components_traits; mod as_uints_traits; mod from_into_arrays_traits; mod from_into_components_traits; mod from_into_uints_traits; mod packed; mod uint; pub use self::{ array::*, as_arrays_traits::*, as_components_traits::*, as_uints_traits::*, from_into_arrays_traits::*, from_into_components_traits::*, from_into_uints_traits::*, packed::*, uint::*, }; palette-0.7.5/src/chromatic_adaptation.rs000064400000000000000000000262101046102023000166060ustar 00000000000000//! Convert colors from one reference white point to another //! //! Chromatic adaptation is the human visual system’s ability to adjust to //! changes in illumination in order to preserve the appearance of object //! colors. It is responsible for the stable appearance of object colours //! despite the wide variation of light which might be reflected from an object //! and observed by our eyes. //! //! This library provides three methods for chromatic adaptation Bradford (which //! is the default), VonKries and XyzScaling //! //! ``` //! use palette::Xyz; //! use palette::white_point::{A, C}; //! use palette::chromatic_adaptation::AdaptInto; //! //! //! let a = Xyz::::new(0.315756, 0.162732, 0.015905); //! //! //Will convert Xyz to Xyz using Bradford chromatic adaptation //! let c: Xyz = a.adapt_into(); //! //! //Should print {x: 0.257963, y: 0.139776,z: 0.058825} //! println!("{:?}", c) //! ``` use crate::{ convert::{FromColorUnclamped, IntoColorUnclamped}, matrix::{multiply_3x3, multiply_xyz, Mat3}, num::{Arithmetics, Real, Zero}, white_point::{Any, WhitePoint}, Xyz, }; /// Chromatic adaptation methods implemented in the library pub enum Method { /// Bradford chromatic adaptation method Bradford, /// VonKries chromatic adaptation method VonKries, /// XyzScaling chromatic adaptation method XyzScaling, } /// Holds the matrix coefficients for the chromatic adaptation methods pub struct ConeResponseMatrices { ///3x3 matrix for the cone response domains pub ma: Mat3, ///3x3 matrix for the inverse of the cone response domains pub inv_ma: Mat3, } /// Generates a conversion matrix to convert the Xyz tristimulus values from /// one illuminant to another (`source_wp` to `destination_wp`) pub trait TransformMatrix where T: Zero + Arithmetics + Clone, { /// Get the cone response functions for the chromatic adaptation method #[must_use] fn get_cone_response(&self) -> ConeResponseMatrices; /// Generates a 3x3 transformation matrix to convert color from one /// reference white point to another with the given cone_response #[must_use] fn generate_transform_matrix( &self, source_wp: Xyz, destination_wp: Xyz, ) -> Mat3 { let adapt = self.get_cone_response(); let resp_src = multiply_xyz(adapt.ma.clone(), source_wp); let resp_dst = multiply_xyz(adapt.ma.clone(), destination_wp); #[rustfmt::skip] let resp = [ resp_dst.x / resp_src.x, T::zero(), T::zero(), T::zero(), resp_dst.y / resp_src.y, T::zero(), T::zero(), T::zero(), resp_dst.z / resp_src.z, ]; let tmp = multiply_3x3(resp, adapt.ma); multiply_3x3(adapt.inv_ma, tmp) } } impl TransformMatrix for Method where T: Real + Zero + Arithmetics + Clone, { #[rustfmt::skip] #[inline] fn get_cone_response(&self) -> ConeResponseMatrices { match *self { Method::Bradford => { ConeResponseMatrices:: { ma: [ T::from_f64(0.8951000), T::from_f64(0.2664000), T::from_f64(-0.1614000), T::from_f64(-0.7502000), T::from_f64(1.7135000), T::from_f64(0.0367000), T::from_f64(0.0389000), T::from_f64(-0.0685000), T::from_f64(1.0296000) ], inv_ma: [ T::from_f64(0.9869929), T::from_f64(-0.1470543), T::from_f64(0.1599627), T::from_f64(0.4323053), T::from_f64(0.5183603), T::from_f64(0.0492912), T::from_f64(-0.0085287), T::from_f64(0.0400428), T::from_f64(0.9684867) ], } } Method::VonKries => { ConeResponseMatrices:: { ma: [ T::from_f64(0.4002400), T::from_f64(0.7076000), T::from_f64(-0.0808100), T::from_f64(-0.2263000), T::from_f64(1.1653200), T::from_f64(0.0457000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(0.9182200) ], inv_ma: [ T::from_f64(1.8599364), T::from_f64(-1.1293816), T::from_f64(0.2198974), T::from_f64(0.3611914), T::from_f64(0.6388125), T::from_f64(-0.0000064), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0890636) ], } } Method::XyzScaling => { ConeResponseMatrices:: { ma: [ T::from_f64(1.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0000000) ], inv_ma: [ T::from_f64(1.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0000000) ], } } } } } /// Trait to convert color from one reference white point to another /// /// Converts a color from the source white point (Swp) to the destination white /// point (Dwp). Uses the bradford method for conversion by default. pub trait AdaptFrom: Sized where T: Real + Zero + Arithmetics + Clone, Swp: WhitePoint, Dwp: WhitePoint, { /// Convert the source color to the destination color using the bradford /// method by default. #[must_use] #[inline] fn adapt_from(color: S) -> Self { Self::adapt_from_using(color, Method::Bradford) } /// Convert the source color to the destination color using the specified /// method. #[must_use] fn adapt_from_using>(color: S, method: M) -> Self; } impl AdaptFrom for D where T: Real + Zero + Arithmetics + Clone, Swp: WhitePoint, Dwp: WhitePoint, S: IntoColorUnclamped>, D: FromColorUnclamped>, { #[inline] fn adapt_from_using>(color: S, method: M) -> D { let src_xyz = color.into_color_unclamped().with_white_point(); let transform_matrix = method.generate_transform_matrix(Swp::get_xyz(), Dwp::get_xyz()); let dst_xyz = multiply_xyz(transform_matrix, src_xyz); D::from_color_unclamped(dst_xyz.with_white_point()) } } /// Trait to convert color with one reference white point into another /// /// Converts a color with the source white point (Swp) into the destination /// white point (Dwp). Uses the bradford method for conversion by default. pub trait AdaptInto: Sized where T: Real + Zero + Arithmetics + Clone, Swp: WhitePoint, Dwp: WhitePoint, { /// Convert the source color to the destination color using the bradford /// method by default. #[must_use] #[inline] fn adapt_into(self) -> D { self.adapt_into_using(Method::Bradford) } /// Convert the source color to the destination color using the specified /// method. #[must_use] fn adapt_into_using>(self, method: M) -> D; } impl AdaptInto for S where T: Real + Zero + Arithmetics + Clone, Swp: WhitePoint, Dwp: WhitePoint, D: AdaptFrom, { #[inline] fn adapt_into_using>(self, method: M) -> D { D::adapt_from_using(self, method) } } #[cfg(feature = "approx")] #[cfg(test)] mod test { use super::{AdaptFrom, AdaptInto, Method, TransformMatrix}; use crate::white_point::{WhitePoint, A, C, D50, D65}; use crate::Xyz; #[test] fn d65_to_d50_matrix_xyz_scaling() { let expected = [ 1.0144665, 0.0000000, 0.0000000, 0.0000000, 1.0000000, 0.0000000, 0.0000000, 0.0000000, 0.7578869, ]; let xyz_scaling = Method::XyzScaling; let computed = xyz_scaling.generate_transform_matrix(D65::get_xyz(), D50::get_xyz()); for (e, c) in expected.iter().zip(computed.iter()) { assert_relative_eq!(e, c, epsilon = 0.0001) } } #[test] fn d65_to_d50_matrix_von_kries() { let expected = [ 1.0160803, 0.0552297, -0.0521326, 0.0060666, 0.9955661, -0.0012235, 0.0000000, 0.0000000, 0.7578869, ]; let von_kries = Method::VonKries; let computed = von_kries.generate_transform_matrix(D65::get_xyz(), D50::get_xyz()); for (e, c) in expected.iter().zip(computed.iter()) { assert_relative_eq!(e, c, epsilon = 0.0001) } } #[test] fn d65_to_d50_matrix_bradford() { let expected = [ 1.0478112, 0.0228866, -0.0501270, 0.0295424, 0.9904844, -0.0170491, -0.0092345, 0.0150436, 0.7521316, ]; let bradford = Method::Bradford; let computed = bradford.generate_transform_matrix(D65::get_xyz(), D50::get_xyz()); for (e, c) in expected.iter().zip(computed.iter()) { assert_relative_eq!(e, c, epsilon = 0.0001) } } #[test] fn chromatic_adaptation_from_a_to_c() { let input_a = Xyz::::new(0.315756, 0.162732, 0.015905); let expected_bradford = Xyz::::new(0.257963, 0.139776, 0.058825); let expected_vonkries = Xyz::::new(0.268446, 0.159139, 0.052843); let expected_xyz_scaling = Xyz::::new(0.281868, 0.162732, 0.052844); let computed_bradford: Xyz = Xyz::adapt_from(input_a); assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001); let computed_vonkries: Xyz = Xyz::adapt_from_using(input_a, Method::VonKries); assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001); let computed_xyz_scaling: Xyz = Xyz::adapt_from_using(input_a, Method::XyzScaling); assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001); } #[test] fn chromatic_adaptation_into_a_to_c() { let input_a = Xyz::::new(0.315756, 0.162732, 0.015905); let expected_bradford = Xyz::::new(0.257963, 0.139776, 0.058825); let expected_vonkries = Xyz::::new(0.268446, 0.159139, 0.052843); let expected_xyz_scaling = Xyz::::new(0.281868, 0.162732, 0.052844); let computed_bradford: Xyz = input_a.adapt_into(); assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001); let computed_vonkries: Xyz = input_a.adapt_into_using(Method::VonKries); assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001); let computed_xyz_scaling: Xyz = input_a.adapt_into_using(Method::XyzScaling); assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001); } } palette-0.7.5/src/color_difference.rs000064400000000000000000000565111046102023000157300ustar 00000000000000//! Algorithms for calculating the difference between colors. //! //! ## Selecting an algorithm //! //! Different distance/difference algorithms and formulas are good for different //! situations. Some are faster but less accurate and some may only be suitable //! for certain color spaces. This table may help navigating the options a bit //! by summarizing the difference between the traits in this module. //! //! **Disclaimer:** _This is not an actual benchmark! It's always best to test //! and evaluate the differences in an actual application, when possible._ //! //! Property explanations: //! - **Complexity:** Low complexity options are generally faster than high //! complexity options. //! - **Accuracy:** How the numerical difference compares to the perceived //! difference. May differ with the color space. //! //! | Trait | Complexity | Accuracy | Notes | //! |-------|------------|----------|-------| //! | [`Ciede2000`] | High | High for small differences, lower for large differences | The de-facto standard, but requires complex calculations to compensate for increased errors in certain areas of the CIE L\*a\*b\* (CIELAB) space. //! | [`ImprovedCiede2000`] | High | High for small differences, lower for large differences | A general improvement of [`Ciede2000`], using a formula by Huang et al. //! | [`DeltaE`] | Usually low | Medium to high | The formula differs between color spaces and may not always be the best. Other formulas, such as [`Ciede2000`], may be preferred for some spaces. //! | [`ImprovedDeltaE`] | Usually low | Medium to high | A general improvement of [`DeltaE`], using a formula by Huang et al. //! | [`EuclideanDistance`] | Low | Medium to high for perceptually uniform spaces, otherwise low | Can be good enough for perceptually uniform spaces or as a "quick and dirty" check. //! | [`HyAb`] | Low | High accuracy for medium to large differences. Less accurate than CIEDE2000 for small differences, but still performs well and is much less computationally expensive. | Similar to Euclidean distance, but separates lightness and chroma more. Limited to Cartesian spaces with a lightness axis and a chroma plane. //! | [`Wcag21RelativeContrast`] | Low | Low and only compares lightness | Meant for checking contrasts in computer graphics (such as between text and background colors), assuming sRGB. Mostly useful as a hint or for checking WCAG 2.1 compliance, considering the criticism it has received. use core::ops::{Add, BitAnd, BitOr, Div, Mul}; use crate::{ angle::RealAngle, bool_mask::{HasBoolMask, LazySelect}, convert::IntoColorUnclamped, num::{ Abs, Arithmetics, Exp, Hypot, MinMax, One, PartialCmp, Powf, Powi, Real, Sqrt, Trigonometry, Zero, }, white_point::D65, Lab, Lch, LinLuma, }; /// A trait for calculating the color difference between two colors. #[deprecated( since = "0.7.2", note = "replaced by `palette::color_difference::Ciede2000`" )] pub trait ColorDifference { /// The type of the calculated color difference. type Scalar; /// Return the difference or distance between two colors. #[must_use] fn get_color_difference(self, other: Self) -> Self::Scalar; } /// Calculate the CIEDE2000 Δ*E\** (Delta E) color difference between two /// colors. /// /// CIEDE2000 is a formula by the CIE that calculates a distance metric, Δ*E\** /// (also known as Delta E), as an estimate of perceived color distance or /// difference. CIEDE2000 is an improvement over Δ*E* (see [`DeltaE`]) for CIE /// L\*a\*b\* and CIE L\*C\*h° (see [`Lab`] and [`Lch`]). /// /// There is a "just noticeable difference" between two colors when the Δ*E\** /// is roughly greater than 1. Thus, the color difference is more suited for /// calculating small distances between colors as opposed to large differences. #[doc(alias = "ColorDifference")] pub trait Ciede2000 { /// The type for the Δ*E\** (Delta E). type Scalar; /// Calculate the CIEDE2000 Δ*E\** (Delta E) color difference between `self` and `other`. #[must_use] fn difference(self, other: Self) -> Self::Scalar; } /// Calculate the CIEDE2000 Δ*E'* (improved IEDE2000 Δ*E\**) color difference. /// /// The "improved CIEDE2000" uses the output of [`Ciede2000`] and enhances it /// according to *Power functions improving the performance of color-difference /// formulas* by Huang et al. pub trait ImprovedCiede2000: Ciede2000 { /// Calculate the CIEDE2000 Δ*E'* (improved IEDE2000 Δ*E\**) color /// difference between `self` and `other`. #[must_use] fn improved_difference(self, other: Self) -> Self::Scalar; } impl ImprovedCiede2000 for C where C: Ciede2000, C::Scalar: Real + Mul + Powf, { #[inline] fn improved_difference(self, other: Self) -> Self::Scalar { // Coefficients from "Power functions improving the performance of // color-difference formulas" by Huang et al. // https://opg.optica.org/oe/fulltext.cfm?uri=oe-23-1-597&id=307643 C::Scalar::from_f64(1.43) * self.difference(other).powf(C::Scalar::from_f64(0.7)) } } /// Container of components necessary to calculate CIEDE color difference pub(crate) struct LabColorDiff { /// Lab color lightness pub l: T, /// Lab color a* value pub a: T, /// Lab color b* value pub b: T, /// Lab color chroma value pub chroma: T, } impl From> for LabColorDiff where T: Hypot + Clone, { #[inline] fn from(color: Lab) -> Self { // Color difference calculation requires Lab and chroma components. This // function handles the conversion into those components which are then // passed to `get_ciede_difference()` where calculation is completed. LabColorDiff { l: color.l, a: color.a.clone(), b: color.b.clone(), chroma: color.a.hypot(color.b), } } } impl From> for LabColorDiff where T: Clone, Lch: IntoColorUnclamped>, { #[inline] fn from(color: Lch) -> Self { let chroma = color.chroma.clone(); let Lab { l, a, b, .. } = color.into_color_unclamped(); LabColorDiff { l, a, b, chroma } } } /// Calculate the CIEDE2000 color difference for two colors in Lab color space. /// There is a "just noticeable difference" between two colors when the delta E /// is roughly greater than 1. Thus, the color difference is more suited for /// calculating small distances between colors as opposed to large differences. #[rustfmt::skip] pub(crate) fn get_ciede2000_difference(this: LabColorDiff, other: LabColorDiff) -> T where T: Real + RealAngle + One + Zero + Trigonometry + Abs + Sqrt + Powi + Exp + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect + BitAnd + BitOr { let c_bar = (this.chroma + other.chroma) / T::from_f64(2.0); let c_bar_pow_seven = c_bar.powi(7); let twenty_five_pow_seven = T::from_f64(6103515625.0); let pi_over_180 = T::from_f64(core::f64::consts::PI / 180.0); let g = T::from_f64(0.5) * (T::one() - (c_bar_pow_seven.clone() / (c_bar_pow_seven + &twenty_five_pow_seven)).sqrt()); let a_one_prime = this.a * (T::one() + &g); let a_two_prime = other.a * (T::one() + g); let c_one_prime = (a_one_prime.clone() * &a_one_prime + this.b.clone() * &this.b).sqrt(); let c_two_prime = (a_two_prime.clone() * &a_two_prime + other.b.clone() * &other.b).sqrt(); let calc_h_prime = |b: T, a_prime: T| -> T { lazy_select! { if b.eq(&T::zero()) & a_prime.eq(&T::zero()) => T::zero(), else => { let result = T::radians_to_degrees(b.atan2(a_prime)); lazy_select! { if result.lt(&T::zero()) => result.clone() + T::from_f64(360.0), else => result.clone(), } }, } }; let h_one_prime = calc_h_prime(this.b, a_one_prime); let h_two_prime = calc_h_prime(other.b, a_two_prime); let h_prime_diff = h_two_prime.clone() - &h_one_prime; let h_prime_abs_diff = h_prime_diff.clone().abs(); let delta_h_prime: T = lazy_select! { if c_one_prime.eq(&T::zero()) | c_two_prime.eq(&T::zero()) => T::zero(), if h_prime_abs_diff.lt_eq(&T::from_f64(180.0)) => h_prime_diff.clone(), if h_two_prime.lt_eq(&h_one_prime) => h_prime_diff.clone() + T::from_f64(360.0), else => h_prime_diff.clone() - T::from_f64(360.0), }; let delta_big_h_prime = T::from_f64(2.0) * (c_one_prime.clone() * &c_two_prime).sqrt() * (delta_h_prime / T::from_f64(2.0) * &pi_over_180).sin(); let h_prime_sum = h_one_prime + h_two_prime; let h_bar_prime = lazy_select! { if c_one_prime.eq(&T::zero()) | c_two_prime.eq(&T::zero()) => h_prime_sum.clone(), if h_prime_abs_diff.gt(&T::from_f64(180.0)) => { (h_prime_sum.clone() + T::from_f64(360.0)) / T::from_f64(2.0) }, else => h_prime_sum.clone() / T::from_f64(2.0), }; let l_bar = (this.l.clone() + &other.l) / T::from_f64(2.0); let c_bar_prime = (c_one_prime.clone() + &c_two_prime) / T::from_f64(2.0); let t: T = T::one() - T::from_f64(0.17) * ((h_bar_prime.clone() - T::from_f64(30.0)) * &pi_over_180).cos() + T::from_f64(0.24) * ((h_bar_prime.clone() * T::from_f64(2.0)) * &pi_over_180).cos() + T::from_f64(0.32) * ((h_bar_prime.clone() * T::from_f64(3.0) + T::from_f64(6.0)) * &pi_over_180).cos() - T::from_f64(0.20) * ((h_bar_prime.clone() * T::from_f64(4.0) - T::from_f64(63.0)) * &pi_over_180).cos(); let s_l = T::one() + ((T::from_f64(0.015) * (l_bar.clone() - T::from_f64(50.0)) * (l_bar.clone() - T::from_f64(50.0))) / ((l_bar.clone() - T::from_f64(50.0)) * (l_bar - T::from_f64(50.0)) + T::from_f64(20.0)).sqrt()); let s_c = T::one() + T::from_f64(0.045) * &c_bar_prime; let s_h = T::one() + T::from_f64(0.015) * &c_bar_prime * t; let delta_theta = T::from_f64(30.0) * (-(((h_bar_prime.clone() - T::from_f64(275.0)) / T::from_f64(25.0)) * ((h_bar_prime - T::from_f64(275.0)) / T::from_f64(25.0)))) .exp(); let c_bar_prime_pow_seven = c_bar_prime.powi(7); let r_c: T = T::from_f64(2.0) * (c_bar_prime_pow_seven.clone() / (c_bar_prime_pow_seven + twenty_five_pow_seven)).sqrt(); let r_t = -r_c * (T::from_f64(2.0) * delta_theta * pi_over_180).sin(); let k_l = T::one(); let k_c = T::one(); let k_h = T::one(); let delta_l_prime = other.l - this.l; let delta_c_prime = c_two_prime - c_one_prime; ((delta_l_prime.clone() / (k_l.clone() * &s_l)) * (delta_l_prime / (k_l * s_l)) + (delta_c_prime.clone() / (k_c.clone() * &s_c)) * (delta_c_prime.clone() / (k_c.clone() * &s_c)) + (delta_big_h_prime.clone() / (k_h.clone() * &s_h)) * (delta_big_h_prime.clone() / (k_h.clone() * &s_h)) + (r_t * delta_c_prime * delta_big_h_prime) / (k_c * s_c * k_h * s_h)) .sqrt() } /// Calculate the distance between two colors as if they were coordinates in /// Euclidean space. /// /// Euclidean distance is not always a good measurement of visual color /// difference, depending on the color space. Some spaces, like /// [`Lab`][crate::Lab] and [`Oklab`][crate::Oklab], will give a fairly uniform /// result, while other spaces, such as [`Rgb`][crate::rgb::Rgb], will give much /// less uniform results. Despite that, it's still appropriate for some /// applications. pub trait EuclideanDistance: Sized { /// The type for the distance value. type Scalar; /// Calculate the Euclidean distance from `self` to `other`. #[must_use] fn distance(self, other: Self) -> Self::Scalar where Self::Scalar: Sqrt, { self.distance_squared(other).sqrt() } /// Calculate the squared Euclidean distance from `self` to `other`. /// /// This is typically a faster option than [`Self::distance`] for some /// cases, such as when comparing two distances. #[must_use] fn distance_squared(self, other: Self) -> Self::Scalar; } /// Calculate and check the WCAG 2.1 relative contrast and relative luminance. /// /// W3C's Web Content Accessibility Guidelines (WCAG) 2.1 suggest a method to /// calculate accessible contrast ratios of text and background colors for those /// with low vision or color vision deficiencies, and for contrast of colors /// used in user interface graphics objects. /// /// These criteria come with a couple of caveats: /// * sRGB is assumed as the presentation color space, which is why it's only /// implemented for a limited set of [`Rgb`][crate::rgb::Rgb] and /// [`Luma`][crate::Luma] spaces. /// * The contrast ratio is not considered entirely consistent with the /// perceived contrast. WCAG 3.x is supposed to provide a better measurement. /// /// Because of the inconsistency with perceived contrast, these methods are more /// suitable as hints and for mechanical verification of standards compliance, /// than for accurate analysis. Remember to not only rely on the numbers, but to /// also test your interfaces with actual people in actual situations for the /// best results. /// /// The following example checks the contrast ratio of two colors in sRGB /// format: /// /// ```rust /// use std::str::FromStr; /// use palette::{Srgb, color_difference::Wcag21RelativeContrast}; /// # fn main() -> Result<(), palette::rgb::FromHexError> { /// /// // the rustdoc "DARK" theme background and text colors /// let background: Srgb = Srgb::from(0x353535).into_format(); /// let foreground = Srgb::from_str("#ddd")?.into_format(); /// /// assert!(background.has_enhanced_contrast_text(foreground)); /// # Ok(()) /// # } /// ``` pub trait Wcag21RelativeContrast: Sized { /// The scalar type used for luminance and contrast. type Scalar: Real + Add + Div + PartialCmp + MinMax; /// Returns the WCAG 2.1 [relative /// luminance](https://www.w3.org/TR/WCAG21/#dfn-relative-luminance) of /// `self`. /// /// The relative luminance is a value between 0 and 1, where 0 is the /// darkest black and 1 is the lightest white. This is the same as clamped /// [`LinLuma`], meaning that the typical implementation of this method /// would be `self.into_color()`. #[must_use] fn relative_luminance(self) -> LinLuma; /// Returns the WCAG 2.1 relative luminance contrast between `self` and /// `other`. /// /// A return value of, for example, 4 represents a contrast ratio of 4:1 /// between the lightest and darkest of the two colors. The range is from /// 1:1 to 21:1, and a higher contrast ratio is generally desirable. /// /// This method is independent of the order of the colors, so /// `a.relative_contrast(b)` and `b.relative_contrast(a)` would return the /// same value. #[must_use] #[inline] fn relative_contrast(self, other: Self) -> Self::Scalar { let (min_luma, max_luma) = self .relative_luminance() .luma .min_max(other.relative_luminance().luma); (Self::Scalar::from_f64(0.05) + max_luma) / (Self::Scalar::from_f64(0.05) + min_luma) } /// Verify the contrast between two colors satisfies SC 1.4.3. Contrast is /// at least 4.5:1 (Level AA). /// /// This applies for meaningful text, such as body text. Font sizes of 18 /// points or lager, or 14 points when bold, are considered large and can be /// checked with /// [`has_min_contrast_large_text`][Wcag21RelativeContrast::has_min_contrast_large_text] /// instead. /// /// [Success Criterion 1.4.3 Contrast (Minimum) (Level /// AA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum) #[must_use] #[inline] fn has_min_contrast_text(self, other: Self) -> ::Mask { self.relative_contrast(other) .gt_eq(&Self::Scalar::from_f64(4.5)) } /// Verify the contrast between two colors satisfies SC 1.4.3 for large /// text. Contrast is at least 3:1 (Level AA). /// /// This applies for meaningful large text, such as headings. Font sizes of /// 18 points or lager, or 14 points when bold, are considered large. /// /// [Success Criterion 1.4.3 Contrast (Minimum) (Level /// AA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum) #[must_use] #[inline] fn has_min_contrast_large_text(self, other: Self) -> ::Mask { self.relative_contrast(other) .gt_eq(&Self::Scalar::from_f64(3.0)) } /// Verify the contrast between two colors satisfies SC 1.4.6. Contrast is /// at least 7:1 (Level AAA). /// /// This applies for meaningful text, such as body text. Font sizes of 18 /// points or lager, or 14 points when bold, are considered large and can be /// checked with /// [`has_enhanced_contrast_large_text`][Wcag21RelativeContrast::has_enhanced_contrast_large_text] /// instead. /// /// [Success Criterion 1.4.6 Contrast (Enhanced) (Level /// AAA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced) #[must_use] #[inline] fn has_enhanced_contrast_text(self, other: Self) -> ::Mask { self.relative_contrast(other) .gt_eq(&Self::Scalar::from_f64(7.0)) } /// Verify the contrast between two colors satisfies SC 1.4.6 for large /// text. Contrast is at least 4.5:1 (Level AAA). /// /// This applies for meaningful large text, such as headings. Font sizes of /// 18 points or lager, or 14 points when bold, are considered large. /// /// [Success Criterion 1.4.6 Contrast (Enhanced) (Level /// AAA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced) #[must_use] #[inline] fn has_enhanced_contrast_large_text(self, other: Self) -> ::Mask { self.relative_contrast(other) .gt_eq(&Self::Scalar::from_f64(4.5)) } /// Verify the contrast between two colors satisfies SC 1.4.11 for graphical /// objects. Contrast is at least 3:1 (Level AA). /// /// This applies for any graphical object that aren't text, such as /// meaningful images and interactive user interface elements. /// /// [Success Criterion 1.4.11 Non-text Contrast (Level /// AA)](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html) #[must_use] #[inline] fn has_min_contrast_graphics(self, other: Self) -> ::Mask { self.relative_contrast(other) .gt_eq(&Self::Scalar::from_f64(3.0)) } } /// Calculate a combination of Euclidean and Manhattan/City-block distance /// between two colors. /// /// The HyAB distance was suggested as an alternative to CIEDE2000 for large /// color differences in [Distance metrics for very large color /// differences](http://markfairchild.org/PDFs/PAP40.pdf) (in [Color Res Appl. /// 2019;1–16](https://doi.org/10.1002/col.22451)) by Saeedeh Abasi, Mohammad /// Amani Tehran and Mark D. Fairchild. It's originally meant for [CIE L\*a\*b\* /// (CIELAB)][crate::Lab], but this trait is also implemented for other color /// spaces that have similar semantics, although **without the same quality /// guarantees**. /// /// The hybrid distance is the sum of the absolute lightness difference and the /// distance on the chroma plane. This makes the lightness and chroma /// differences more independent from each other, which is meant to correspond /// more to how humans perceive the two qualities. pub trait HyAb { /// The type for the distance value. type Scalar; /// Calculate the hybrid distance between `self` and `other`. /// /// This returns the sum of the absolute lightness difference and the /// distance on the chroma plane. #[must_use] fn hybrid_distance(self, other: Self) -> Self::Scalar; } /// Calculate the Δ*E* color difference between two colors. /// /// This represents the original Δ*E* formula for a color space. It's often a /// Euclidean distance for perceptually uniform color spaces and may not always /// be the best option. See the [`color_difference`](self) module for more /// details and options. pub trait DeltaE { /// The type for the distance value. type Scalar; /// Calculate the Δ*E* color difference metric for `self` and `other`, /// according to the color space's specification. #[must_use] fn delta_e(self, other: Self) -> Self::Scalar; } /// Calculate the Δ*E'* (improved Δ*E*) color difference between two colors. /// /// The Δ*E'* uses the output of [`DeltaE`] and enhances it according to *Power /// functions improving the performance of color-difference formulas* by Huang /// et al. Only spaces with specified coefficients implement this trait. pub trait ImprovedDeltaE: DeltaE { /// Calculate the Δ*E'* (improved Δ*E*) color difference metric for `self` /// and `other`, according to the color space's specification and later /// improvements by Huang et al. #[must_use] fn improved_delta_e(self, other: Self) -> Self::Scalar; } #[cfg(feature = "approx")] #[cfg(test)] mod test { use core::str::FromStr; use super::{HyAb, Wcag21RelativeContrast}; use crate::{FromColor, Lab, Srgb}; #[test] fn relative_contrast() { let white = Srgb::new(1.0f32, 1.0, 1.0); let black = Srgb::new(0.0, 0.0, 0.0); assert_relative_eq!(white.relative_contrast(white), 1.0); assert_relative_eq!(white.relative_contrast(black), 21.0); assert_relative_eq!( white.relative_contrast(black), black.relative_contrast(white) ); let c1 = Srgb::from_str("#600").unwrap().into_format(); assert_relative_eq!(c1.relative_contrast(white), 13.41, epsilon = 0.01); assert_relative_eq!(c1.relative_contrast(black), 1.56, epsilon = 0.01); assert!(c1.has_min_contrast_text(white)); assert!(c1.has_min_contrast_large_text(white)); assert!(c1.has_enhanced_contrast_text(white)); assert!(c1.has_enhanced_contrast_large_text(white)); assert!(c1.has_min_contrast_graphics(white)); assert!(!c1.has_min_contrast_text(black)); assert!(!c1.has_min_contrast_large_text(black)); assert!(!c1.has_enhanced_contrast_text(black)); assert!(!c1.has_enhanced_contrast_large_text(black)); assert!(!c1.has_min_contrast_graphics(black)); let c1 = Srgb::from_str("#066").unwrap().into_format(); assert_relative_eq!(c1.relative_contrast(white), 6.79, epsilon = 0.01); assert_relative_eq!(c1.relative_contrast(black), 3.09, epsilon = 0.01); let c1 = Srgb::from_str("#9f9").unwrap().into_format(); assert_relative_eq!(c1.relative_contrast(white), 1.22, epsilon = 0.01); assert_relative_eq!(c1.relative_contrast(black), 17.11, epsilon = 0.01); } #[test] fn hyab() { // From https://github.com/Evercoder/culori/blob/cd1fe08a12fa9ddfcf6b2e82914733d23ac117d0/test/difference.test.js#L186 let red = Lab::<_, f64>::from_color(Srgb::from(0xff0000).into_linear()); let green = Lab::<_, f64>::from_color(Srgb::from(0x008000).into_linear()); assert_relative_eq!( red.hybrid_distance(green), 139.93576718451553, epsilon = 0.000001 ); } } palette-0.7.5/src/convert/from_into_color.rs000064400000000000000000000106221046102023000173030ustar 00000000000000use crate::Clamp; #[cfg(feature = "alloc")] use crate::cast::{self, ArrayCast}; use super::FromColorUnclamped; ///A trait for converting one color from another, in a possibly lossy way. /// /// `U: FromColor` is implemented for every type `U: FromColorUnclamped + /// Clamp`, as well as for `Vec` and `Box<[T]>` where `T` and `U` have the /// same memory layout. /// /// See [`FromColorUnclamped`](crate::convert::FromColorUnclamped) for a /// lossless version of this trait. See /// [`TryFromColor`](crate::convert::TryFromColor) for a trait that gives an /// error when the result is out of bounds. /// /// # The Difference Between FromColor and From /// /// The conversion traits, including `FromColor`, were added to gain even more /// flexibility than what `From` and the other standard library traits can give. /// There are a few subtle, but important, differences in their semantics: /// /// * `FromColor` and `IntoColor` are allowed to be lossy, meaning converting `A /// -> B -> A` may result in a different value than the original. This applies /// to `A -> A` as well. /// * `From` and `Into` are blanket implemented, while /// `FromColor` and `IntoColor` have to be manually implemented. /// This allows additional flexibility, such as allowing implementing /// `FromColor> for Rgb`. /// * Implementing `FromColorUnclamped`, /// [`IsWithinBounds`](crate::IsWithinBounds) and [`Clamp`] is enough to get /// all the other conversion traits, while `From` and `Into` would not be /// possible to blanket implement in the same way. This also reduces the work /// that needs to be done by macros. /// /// See the [`convert`](crate::convert) module for how to implement /// `FromColorUnclamped` for custom colors. pub trait FromColor: Sized { /// Convert from T with values clamped to the color defined bounds. /// /// ``` /// use palette::{IsWithinBounds, FromColor, Lch, Srgb}; /// /// let rgb = Srgb::from_color(Lch::new(50.0f32, 100.0, -175.0)); /// assert!(rgb.is_within_bounds()); /// ``` #[must_use] fn from_color(t: T) -> Self; } impl FromColor for U where U: FromColorUnclamped + Clamp, { #[inline] fn from_color(t: T) -> Self { Self::from_color_unclamped(t).clamp() } } #[cfg(feature = "alloc")] impl FromColor> for alloc::vec::Vec where T: ArrayCast, U: ArrayCast + FromColor, { /// Convert all colors in place, without reallocating. /// /// ``` /// use palette::{convert::FromColor, SaturateAssign, Srgb, Lch}; /// /// let srgb = vec![Srgb::new(0.8f32, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)]; /// let mut lch = Vec::::from_color(srgb); /// /// lch.saturate_assign(0.1); /// /// let srgb = Vec::::from_color(lch); /// ``` #[inline] fn from_color(color: alloc::vec::Vec) -> Self { cast::map_vec_in_place(color, U::from_color) } } #[cfg(feature = "alloc")] impl FromColor> for alloc::boxed::Box<[U]> where T: ArrayCast, U: ArrayCast + FromColor, { /// Convert all colors in place, without reallocating. /// /// ``` /// use palette::{convert::FromColor, SaturateAssign, Srgb, Lch}; /// /// let srgb = vec![Srgb::new(0.8f32, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)].into_boxed_slice(); /// let mut lch = Box::<[Lch]>::from_color(srgb); /// /// lch.saturate_assign(0.1); /// /// let srgb = Box::<[Srgb]>::from_color(lch); /// ``` #[inline] fn from_color(color: alloc::boxed::Box<[T]>) -> Self { cast::map_slice_box_in_place(color, U::from_color) } } /// A trait for converting a color into another, in a possibly lossy way. /// /// `U: IntoColor` is implemented for every type `T: FromColor`. /// /// See [`FromColor`](crate::convert::FromColor) for more details. pub trait IntoColor: Sized { /// Convert into T with values clamped to the color defined bounds /// /// ``` /// use palette::{IsWithinBounds, IntoColor, Lch, Srgb}; /// /// let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).into_color(); /// assert!(rgb.is_within_bounds()); /// ``` #[must_use] fn into_color(self) -> T; } impl IntoColor for T where U: FromColor, { #[inline] fn into_color(self) -> U { U::from_color(self) } } palette-0.7.5/src/convert/from_into_color_mut.rs000064400000000000000000000246011046102023000201720ustar 00000000000000use core::{ marker::PhantomData, ops::{Deref, DerefMut}, }; use crate::cast::{self, ArrayCast}; use super::{FromColor, FromColorUnclampedMut, FromColorUnclampedMutGuard, IntoColor}; /// Temporarily convert colors in place. /// /// It allows colors to be converted without using more additional memory than /// what is necessary for the conversion, itself. The conversion will however /// have to be reverted at some point, since the memory space is borrowed and /// has to be restored to its original format. This is enforced by a scope guard /// that does the opposite conversion when it's dropped. /// /// See also [`IntoColorMut`] and [`FromColorUnclampedMut`]. /// /// ``` /// use palette::{FromColorMut, ShiftHueAssign, Srgb, Hsv}; /// /// let mut rgb = [ /// Srgb::new(1.0, 0.0, 0.0), /// Srgb::new(0.0, 1.0, 0.0), /// Srgb::new(0.0, 0.0, 1.0), /// ]; /// /// { /// let mut hsv = <[Hsv]>::from_color_mut(&mut rgb); /// /// // All of the colors in `rgb` have been converted to `Hsv`: /// assert_eq!( /// *hsv, /// [ /// Hsv::new(0.0, 1.0, 1.0), /// Hsv::new(120.0, 1.0, 1.0), /// Hsv::new(240.0, 1.0, 1.0), /// ] /// ); /// /// hsv.shift_hue_assign(60.0); /// /// } // The guard is dropped here and the colors are restored to `Srgb`. /// /// // Notice how the colors in `rgb` have changed: /// assert_eq!( /// rgb, /// [ /// Srgb::new(1.0, 1.0, 0.0), /// Srgb::new(0.0, 1.0, 1.0), /// Srgb::new(1.0, 0.0, 1.0), /// ] /// ); /// ``` /// /// The scope guard, [`FromColorMutGuard`], has a few extra methods that can /// make multiple conversion steps more efficient. One of those is /// [`FromColorMutGuard::then_into_color_mut`], which works like /// [`IntoColorMut::into_color_mut`], but does not add an extra step when /// restoring to the original color type. This example will convert `Srgb → Hsv /// → Hsl → Srgb` instead of `Srgb → Hsv → Hsl → Hsv → Srgb`: /// /// ``` /// use palette::{FromColorMut, ShiftHueAssign, LightenAssign, Srgb, Hsv, Hsl}; /// /// let mut rgb = [ /// Srgb::new(1.0, 0.0, 0.0), /// Srgb::new(0.0, 1.0, 0.0), /// Srgb::new(0.0, 0.0, 1.0), /// ]; /// /// { /// let mut hsv = <[Hsv]>::from_color_mut(&mut rgb); /// hsv.shift_hue_assign(60.0); /// /// let mut hsl = hsv.then_into_color_mut::<[Hsl]>(); /// hsl.lighten_assign(0.5); /// /// } // `then_into_color_mut` makes the guard restore directly to `Srgb` here. /// /// // Notice how the colors in `rgb` have changed: /// assert_eq!( /// rgb, /// [ /// Srgb::new(1.0, 1.0, 0.5), /// Srgb::new(0.5, 1.0, 1.0), /// Srgb::new(1.0, 0.5, 1.0), /// ] /// ); /// ``` /// /// # Note /// /// The reused memory space could end up with unexpected values if the /// conversion panics or if the scope guard's `drop` function doesn't run. The /// default implementations of `FromColorMut` uses [`ArrayCast`], which is only /// implemented for color types that can safely accept and recover from any /// value. Other color types will have to provide their own implementations that /// can handle this case. pub trait FromColorMut where T: ?Sized + FromColorMut, { /// Temporarily convert from another color type in place. /// /// This reuses the memory space, and the returned scope guard will restore /// the converted colors to their original type when it's dropped. #[must_use] fn from_color_mut(color: &mut T) -> FromColorMutGuard; } impl FromColorMut for T where T: FromColor + ArrayCast + Clone, U: FromColor + ArrayCast + Clone, { #[inline] fn from_color_mut(color: &mut U) -> FromColorMutGuard { let color_clone = color.clone(); let result: &mut T = cast::from_array_mut(cast::into_array_mut(color)); *result = color_clone.into_color(); FromColorMutGuard { current: Some(result), original: PhantomData, } } } impl FromColorMut<[U]> for [T] where T: FromColorMut + ArrayCast + ?Sized, U: FromColorMut + ArrayCast + ?Sized, { #[inline] fn from_color_mut(colors: &mut [U]) -> FromColorMutGuard { for color in &mut *colors { // Forgetting the guard leaves the colors in the converted state. core::mem::forget(T::from_color_mut(color)); } FromColorMutGuard { current: Some(cast::from_array_slice_mut(cast::into_array_slice_mut( colors, ))), original: PhantomData, } } } /// Temporarily convert colors in place. The `Into` counterpart to /// [`FromColorMut`]. /// /// See [`FromColorMut`] for more details and examples. /// /// ``` /// use palette::{IntoColorMut, ShiftHueAssign, Srgb, Hsv}; /// /// let mut rgb = [ /// Srgb::new(1.0, 0.0, 0.0), /// Srgb::new(0.0, 1.0, 0.0), /// Srgb::new(0.0, 0.0, 1.0), /// ]; /// /// { /// let hsv: &mut [Hsv] = &mut rgb.into_color_mut(); // The guard is coerced into a slice. /// /// // All of the colors in `rgb` have been converted to `Hsv`: /// assert_eq!( /// hsv, /// [ /// Hsv::new(0.0, 1.0, 1.0), /// Hsv::new(120.0, 1.0, 1.0), /// Hsv::new(240.0, 1.0, 1.0), /// ] /// ); /// /// hsv.shift_hue_assign(60.0); /// /// } // The guard is dropped here and the colors are restored to `Srgb`. /// /// // Notice how the colors in `rgb` have changed: /// assert_eq!( /// rgb, /// [ /// Srgb::new(1.0, 1.0, 0.0), /// Srgb::new(0.0, 1.0, 1.0), /// Srgb::new(1.0, 0.0, 1.0), /// ] /// ); /// ``` pub trait IntoColorMut: FromColorMut where T: ?Sized + FromColorMut, { /// Temporarily convert to another color type in place. /// /// This reuses the memory space, and the returned scope guard will restore /// the converted colors to their original type when it's dropped. #[allow(clippy::wrong_self_convention)] #[must_use] fn into_color_mut(&mut self) -> FromColorMutGuard; } impl IntoColorMut for U where T: FromColorMut + ?Sized, U: FromColorMut + ?Sized, { #[inline] fn into_color_mut(&mut self) -> FromColorMutGuard { T::from_color_mut(self) } } /// A scope guard that restores the guarded colors to their original type when /// dropped. #[repr(transparent)] pub struct FromColorMutGuard<'a, T, U> where T: FromColorMut + ?Sized, U: FromColorMut + ?Sized, { // `Option` lets us move out without triggering `Drop`. pub(super) current: Option<&'a mut T>, pub(super) original: PhantomData<&'a mut U>, } impl<'a, T, U> FromColorMutGuard<'a, T, U> where T: FromColorMut + ?Sized, U: FromColorMut + ?Sized, { /// Convert the colors to another type and replace this guard. /// /// The colors will not be converted back to the current color type before /// being restored, as opposed to when `into_color_mut` is called. Instead, /// they are restored directly to their original type. #[must_use] #[inline] pub fn then_into_color_mut(mut self) -> FromColorMutGuard<'a, C, U> where T: FromColorMut, C: FromColorMut + FromColorMut + ?Sized, U: FromColorMut, { FromColorMutGuard { current: self .current .take() .map(C::from_color_mut) .and_then(|mut guard| guard.current.take()), original: PhantomData, } } /// Convert the colors to another type, without clamping, and replace this /// guard. /// /// The colors will not be converted back to the current color type before /// being restored, as opposed to when `into_color_unclamped_mut` is called. /// Instead, they are restored directly to their original type. #[must_use] #[inline] pub fn then_into_color_unclamped_mut(mut self) -> FromColorUnclampedMutGuard<'a, C, U> where T: FromColorUnclampedMut, C: FromColorUnclampedMut + FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut, { FromColorUnclampedMutGuard { current: self .current .take() .map(C::from_color_unclamped_mut) .and_then(|mut guard| guard.current.take()), original: PhantomData, } } /// Replace this guard with a guard that does not clamp the colors after restoring. #[must_use] #[inline] pub fn into_unclamped_guard(mut self) -> FromColorUnclampedMutGuard<'a, T, U> where T: FromColorUnclampedMut, U: FromColorUnclampedMut, { FromColorUnclampedMutGuard { current: self.current.take(), original: PhantomData, } } /// Immediately restore the colors to their original type. /// /// This happens automatically when the guard is dropped, but there may be /// situations where it's better or more convenient to call `restore` /// directly. #[inline] pub fn restore(mut self) -> &'a mut U { let restored = self .current .take() .map(U::from_color_mut) .and_then(|mut guard| guard.current.take()); if let Some(restored) = restored { restored } else { unreachable!() } } } impl<'a, T, U> Deref for FromColorMutGuard<'a, T, U> where T: FromColorMut + ?Sized, U: FromColorMut + ?Sized, { type Target = T; #[inline] fn deref(&self) -> &Self::Target { if let Some(current) = self.current.as_ref() { current } else { unreachable!() } } } impl<'a, T, U> DerefMut for FromColorMutGuard<'a, T, U> where T: FromColorMut + ?Sized, U: FromColorMut + ?Sized, { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { if let Some(current) = self.current.as_mut() { current } else { unreachable!() } } } impl<'a, T, U> Drop for FromColorMutGuard<'a, T, U> where T: FromColorMut + ?Sized, U: FromColorMut + ?Sized, { #[inline] fn drop(&mut self) { // Forgetting the guard leaves the colors in the converted state. core::mem::forget(self.current.take().map(U::from_color_mut)); } } palette-0.7.5/src/convert/from_into_color_unclamped.rs000064400000000000000000000064631046102023000213430ustar 00000000000000pub use palette_derive::FromColorUnclamped; #[cfg(feature = "alloc")] use crate::cast::{self, ArrayCast}; /// A trait for unchecked conversion of one color from another. /// /// See [`FromColor`](crate::convert::FromColor) for a lossy version of this trait. /// See [`TryFromColor`](crate::convert::TryFromColor) for a trait that gives an error when the result /// is out of bounds. /// /// See the [`convert`](crate::convert) module for how to implement `FromColorUnclamped` for /// custom colors. pub trait FromColorUnclamped: Sized { /// Convert from T. The resulting color might be invalid in its color space. /// /// ``` /// use palette::convert::FromColorUnclamped; /// use palette::{IsWithinBounds, Lch, Srgb}; /// /// let rgb = Srgb::from_color_unclamped(Lch::new(50.0f32, 100.0, -175.0)); /// assert!(!rgb.is_within_bounds()); /// ``` #[must_use] fn from_color_unclamped(val: T) -> Self; } #[cfg(feature = "alloc")] impl FromColorUnclamped> for alloc::vec::Vec where T: ArrayCast, U: ArrayCast + FromColorUnclamped, { /// Convert all colors in place, without reallocating. /// /// ``` /// use palette::{convert::FromColorUnclamped, SaturateAssign, Srgb, Lch}; /// /// let srgb = vec![Srgb::new(0.8f32, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)]; /// let mut lch = Vec::::from_color_unclamped(srgb); /// /// lch.saturate_assign(0.1); /// /// let srgb = Vec::::from_color_unclamped(lch); /// ``` #[inline] fn from_color_unclamped(color: alloc::vec::Vec) -> Self { cast::map_vec_in_place(color, U::from_color_unclamped) } } #[cfg(feature = "alloc")] impl FromColorUnclamped> for alloc::boxed::Box<[U]> where T: ArrayCast, U: ArrayCast + FromColorUnclamped, { /// Convert all colors in place, without reallocating. /// /// ``` /// use palette::{convert::FromColorUnclamped, SaturateAssign, Srgb, Lch}; /// /// let srgb = vec![Srgb::new(0.8f32, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)].into_boxed_slice(); /// let mut lch = Box::<[Lch]>::from_color_unclamped(srgb); /// /// lch.saturate_assign(0.1); /// /// let srgb = Box::<[Srgb]>::from_color_unclamped(lch); /// ``` #[inline] fn from_color_unclamped(color: alloc::boxed::Box<[T]>) -> Self { cast::map_slice_box_in_place(color, U::from_color_unclamped) } } /// A trait for unchecked conversion of a color into another. /// /// `U: IntoColorUnclamped` is implemented for every type `T: FromColorUnclamped`. /// /// See [`FromColorUnclamped`](crate::convert::FromColorUnclamped) for more details. pub trait IntoColorUnclamped: Sized { /// Convert into T. The resulting color might be invalid in its color space /// /// ``` /// use palette::convert::IntoColorUnclamped; /// use palette::{IsWithinBounds, Lch, Srgb}; /// ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).into_color_unclamped(); ///assert!(!rgb.is_within_bounds()); ///``` #[must_use] fn into_color_unclamped(self) -> T; } impl IntoColorUnclamped for T where U: FromColorUnclamped, { #[inline] fn into_color_unclamped(self) -> U { U::from_color_unclamped(self) } } palette-0.7.5/src/convert/from_into_color_unclamped_mut.rs000064400000000000000000000257751046102023000222370ustar 00000000000000use core::{ marker::PhantomData, ops::{Deref, DerefMut}, }; use crate::cast::{self, ArrayCast}; use super::{FromColorMut, FromColorMutGuard, FromColorUnclamped, IntoColorUnclamped}; /// Temporarily convert colors in place, without clamping. /// /// It allows colors to be converted without using more additional memory than /// what is necessary for the conversion, itself. The conversion will however /// have to be reverted at some point, since the memory space is borrowed and /// has to be restored to its original format. This is enforced by a scope guard /// that does the opposite conversion when it's dropped. /// /// See also [`IntoColorUnclampedMut`] and [`FromColorMut`]. /// /// ``` /// use palette::{convert::FromColorUnclampedMut, ShiftHueAssign, Srgb, Hsv}; /// /// let mut rgb = [ /// Srgb::new(1.0, 0.0, 0.0), /// Srgb::new(0.0, 1.0, 0.0), /// Srgb::new(0.0, 0.0, 1.0), /// ]; /// /// { /// let mut hsv = <[Hsv]>::from_color_unclamped_mut(&mut rgb); /// /// // All of the colors in `rgb` have been converted to `Hsv`: /// assert_eq!( /// *hsv, /// [ /// Hsv::new(0.0, 1.0, 1.0), /// Hsv::new(120.0, 1.0, 1.0), /// Hsv::new(240.0, 1.0, 1.0), /// ] /// ); /// /// hsv.shift_hue_assign(60.0); /// /// } // The guard is dropped here and the colors are restored to `Srgb`. /// /// // Notice how the colors in `rgb` have changed: /// assert_eq!( /// rgb, /// [ /// Srgb::new(1.0, 1.0, 0.0), /// Srgb::new(0.0, 1.0, 1.0), /// Srgb::new(1.0, 0.0, 1.0), /// ] /// ); /// ``` /// /// The scope guard, [`FromColorUnclampedMutGuard`], has a few extra methods /// that can make multiple conversion steps more efficient. One of those is /// [`FromColorUnclampedMutGuard::then_into_color_unclamped_mut`], which works /// like [`IntoColorUnclampedMut::into_color_unclamped_mut`], but does not add /// an extra step when restoring to the original color type. This example will /// convert `Srgb → Hsv → Hsl → Srgb` instead of `Srgb → Hsv → Hsl → Hsv → /// Srgb`: /// /// ``` /// use palette::{convert::FromColorUnclampedMut, ShiftHueAssign, LightenAssign, Srgb, Hsv, Hsl}; /// /// let mut rgb = [ /// Srgb::new(1.0, 0.0, 0.0), /// Srgb::new(0.0, 1.0, 0.0), /// Srgb::new(0.0, 0.0, 1.0), /// ]; /// /// { /// let mut hsv = <[Hsv]>::from_color_unclamped_mut(&mut rgb); /// hsv.shift_hue_assign(60.0); /// /// let mut hsl = hsv.then_into_color_unclamped_mut::<[Hsl]>(); /// hsl.lighten_assign(0.5); /// /// } // `then_into_color_unclamped_mut` makes the guard restore directly to `Srgb` here. /// /// // Notice how the colors in `rgb` have changed: /// assert_eq!( /// rgb, /// [ /// Srgb::new(1.0, 1.0, 0.5), /// Srgb::new(0.5, 1.0, 1.0), /// Srgb::new(1.0, 0.5, 1.0), /// ] /// ); /// ``` /// /// # Note /// /// The reused memory space could end up with unexpected values if the /// conversion panics or if the scope guard's `drop` function doesn't run. The /// default implementations of `FromColorUnclampedMut` uses [`ArrayCast`], which /// is only implemented for color types that can safely accept and recover from /// any value. Other color types will have to provide their own implementations /// that can handle this case. pub trait FromColorUnclampedMut where T: ?Sized + FromColorUnclampedMut, { /// Temporarily convert from another color type in place, without clamping. /// /// This reuses the memory space, and the returned scope guard will restore /// the converted colors to their original type when it's dropped. #[must_use] fn from_color_unclamped_mut(color: &mut T) -> FromColorUnclampedMutGuard; } impl FromColorUnclampedMut for T where T: FromColorUnclamped + ArrayCast + Clone, U: FromColorUnclamped + ArrayCast + Clone, { #[inline] fn from_color_unclamped_mut(color: &mut U) -> FromColorUnclampedMutGuard { let color_clone = color.clone(); let result: &mut Self = cast::from_array_mut(cast::into_array_mut(color)); *result = color_clone.into_color_unclamped(); FromColorUnclampedMutGuard { current: Some(result), original: PhantomData, } } } impl FromColorUnclampedMut<[U]> for [T] where T: FromColorUnclampedMut + ArrayCast + ?Sized, U: FromColorUnclampedMut + ArrayCast + ?Sized, { #[inline] fn from_color_unclamped_mut(colors: &mut [U]) -> FromColorUnclampedMutGuard { for color in &mut *colors { // Forgetting the guard leaves the colors in the converted state. core::mem::forget(T::from_color_unclamped_mut(color)); } FromColorUnclampedMutGuard { current: Some(cast::from_array_slice_mut(cast::into_array_slice_mut( colors, ))), original: PhantomData, } } } /// Temporarily convert colors in place. The `Into` counterpart to /// [`FromColorUnclampedMut`]. /// /// See [`FromColorUnclampedMut`] for more details and examples. /// /// ``` /// use palette::{convert::IntoColorUnclampedMut, ShiftHueAssign, Srgb, Hsv}; /// /// let mut rgb = [ /// Srgb::new(1.0, 0.0, 0.0), /// Srgb::new(0.0, 1.0, 0.0), /// Srgb::new(0.0, 0.0, 1.0), /// ]; /// /// { /// let hsv: &mut [Hsv] = &mut rgb.into_color_unclamped_mut(); // The guard is coerced into a slice. /// /// // All of the colors in `rgb` have been converted to `Hsv`: /// assert_eq!( /// hsv, /// [ /// Hsv::new(0.0, 1.0, 1.0), /// Hsv::new(120.0, 1.0, 1.0), /// Hsv::new(240.0, 1.0, 1.0), /// ] /// ); /// /// hsv.shift_hue_assign(60.0); /// /// } // The guard is dropped here and the colors are restored to `Srgb`. /// /// // Notice how the colors in `rgb` have changed: /// assert_eq!( /// rgb, /// [ /// Srgb::new(1.0, 1.0, 0.0), /// Srgb::new(0.0, 1.0, 1.0), /// Srgb::new(1.0, 0.0, 1.0), /// ] /// ); /// ``` pub trait IntoColorUnclampedMut: FromColorUnclampedMut where T: ?Sized + FromColorUnclampedMut, { /// Temporarily convert to another color type in place, without clamping. /// /// This reuses the memory space, and the returned scope guard will restore /// the converted colors to their original type when it's dropped. #[allow(clippy::wrong_self_convention)] #[must_use] fn into_color_unclamped_mut(&mut self) -> FromColorUnclampedMutGuard; } impl IntoColorUnclampedMut for U where T: FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut + ?Sized, { #[inline] fn into_color_unclamped_mut(&mut self) -> FromColorUnclampedMutGuard { T::from_color_unclamped_mut(self) } } /// A scope guard that restores the guarded colors to their original type, /// without clamping, when dropped. #[repr(transparent)] pub struct FromColorUnclampedMutGuard<'a, T, U> where T: FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut + ?Sized, { // `Option` lets us move out without triggering `Drop`. pub(super) current: Option<&'a mut T>, pub(super) original: PhantomData<&'a mut U>, } impl<'a, T, U> FromColorUnclampedMutGuard<'a, T, U> where T: FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut + ?Sized, { /// Convert the colors to another type and replace this guard. /// /// The colors will not be converted back to the current color type before /// being restored, as opposed to when `into_color_mut` is called. Instead, /// they are restored directly to their original type. #[must_use] #[inline] pub fn then_into_color_mut(mut self) -> FromColorMutGuard<'a, C, U> where T: FromColorMut, C: FromColorMut + FromColorMut + ?Sized, U: FromColorMut, { FromColorMutGuard { current: self .current .take() .map(C::from_color_mut) .and_then(|mut guard| guard.current.take()), original: PhantomData, } } /// Convert the colors to another type, without clamping, and replace this /// guard. /// /// The colors will not be converted back to the current color type before /// being restored, as opposed to when `into_color_unclamped_mut` is called. /// Instead, they are restored directly to their original type. #[must_use] #[inline] pub fn then_into_color_unclamped_mut(mut self) -> FromColorUnclampedMutGuard<'a, C, U> where T: FromColorUnclampedMut, C: FromColorUnclampedMut + FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut, { FromColorUnclampedMutGuard { current: self .current .take() .map(C::from_color_unclamped_mut) .and_then(|mut guard| guard.current.take()), original: PhantomData, } } /// Replace this guard with a guard that clamps the colors after restoring. #[must_use] #[inline] pub fn into_clamped_guard(mut self) -> FromColorMutGuard<'a, T, U> where T: FromColorMut, U: FromColorMut, { FromColorMutGuard { current: self.current.take(), original: PhantomData, } } /// Immediately restore the colors to their original type. /// /// This happens automatically when the guard is dropped, but there may be /// situations where it's better or more convenient to call `restore` /// directly. #[inline] pub fn restore(mut self) -> &'a mut U { let restored = self .current .take() .map(U::from_color_unclamped_mut) .and_then(|mut guard| guard.current.take()); if let Some(restored) = restored { restored } else { unreachable!() } } } impl<'a, T, U> Deref for FromColorUnclampedMutGuard<'a, T, U> where T: FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut + ?Sized, { type Target = T; #[inline] fn deref(&self) -> &Self::Target { if let Some(current) = self.current.as_ref() { current } else { unreachable!() } } } impl<'a, T, U> DerefMut for FromColorUnclampedMutGuard<'a, T, U> where T: FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut + ?Sized, { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { if let Some(current) = self.current.as_mut() { current } else { unreachable!() } } } impl<'a, T, U> Drop for FromColorUnclampedMutGuard<'a, T, U> where T: FromColorUnclampedMut + ?Sized, U: FromColorUnclampedMut + ?Sized, { #[inline] fn drop(&mut self) { // Forgetting the guard leaves the colors in the converted state. core::mem::forget(self.current.take().map(U::from_color_unclamped_mut)); } } palette-0.7.5/src/convert/try_from_into_color.rs000064400000000000000000000064141046102023000202050ustar 00000000000000use core::fmt; use crate::IsWithinBounds; use super::FromColorUnclamped; /// The error type for a color conversion that converted a color into a color /// with invalid values. #[derive(Debug)] pub struct OutOfBounds { color: T, } impl OutOfBounds { /// Create a new error wrapping a color #[inline] fn new(color: T) -> Self { OutOfBounds { color } } /// Consume this error and return the wrapped color #[inline] pub fn color(self) -> T { self.color } } #[cfg(feature = "std")] impl std::error::Error for OutOfBounds { fn description(&self) -> &str { "color conversion is out of bounds" } } impl fmt::Display for OutOfBounds { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "color conversion is out of bounds") } } /// A trait for fallible conversion of one color from another. /// /// `U: TryFromColor` is implemented for every type `U: FromColorUnclamped + Clamp`. /// /// See [`FromColor`](crate::convert::FromColor) for a lossy version of this trait. /// See [`FromColorUnclamped`](crate::convert::FromColorUnclamped) for a lossless version. /// /// See the [`convert`](crate::convert) module for how to implement `FromColorUnclamped` for /// custom colors. pub trait TryFromColor: Sized { /// Convert from T, returning ok if the color is inside of its defined /// range, otherwise an `OutOfBounds` error is returned which contains /// the unclamped color. /// ///``` /// use palette::convert::TryFromColor; /// use palette::{Hsl, Srgb}; /// /// let rgb = match Srgb::try_from_color(Hsl::new(150.0, 1.0, 1.1)) { /// Ok(color) => color, /// Err(err) => { /// println!("Color is out of bounds"); /// err.color() /// } /// }; /// ``` fn try_from_color(t: T) -> Result>; } impl TryFromColor for U where U: FromColorUnclamped + IsWithinBounds, { #[inline] fn try_from_color(t: T) -> Result> { let this = Self::from_color_unclamped(t); if this.is_within_bounds() { Ok(this) } else { Err(OutOfBounds::new(this)) } } } /// A trait for fallible conversion of a color into another. /// /// `U: TryIntoColor` is implemented for every type `T: TryFromColor`. /// /// See [`TryFromColor`](crate::convert::TryFromColor) for more details. pub trait TryIntoColor: Sized { /// Convert into T, returning ok if the color is inside of its defined /// range, otherwise an `OutOfBounds` error is returned which contains /// the unclamped color. /// ///``` /// use palette::convert::TryIntoColor; /// use palette::{Hsl, Srgb}; /// /// let rgb: Srgb = match Hsl::new(150.0, 1.0, 1.1).try_into_color() { /// Ok(color) => color, /// Err(err) => { /// println!("Color is out of bounds"); /// err.color() /// } /// }; /// ``` fn try_into_color(self) -> Result>; } impl TryIntoColor for T where U: TryFromColor, { #[inline] fn try_into_color(self) -> Result> { U::try_from_color(self) } } palette-0.7.5/src/convert.rs000064400000000000000000000705761046102023000141270ustar 00000000000000//! Traits for converting between color spaces. //! //! Each color space type, such as [`Rgb`](crate::rgb::Rgb) and //! [`Hsl`](crate::Hsl), implement a number of conversion traits: //! //! * [`FromColor`] - Similar to [`From`], converts from another color space. //! * [`IntoColor`] - Similar to [`Into`], converts into another color space. //! * [`FromColorUnclamped`] - The same as [`FromColor`], but the resulting //! values may be outside the typical bounds. //! * [`IntoColorUnclamped`] - The same as [`IntoColor`], but the resulting //! values may be outside the typical bounds. //! //! ``` //! use palette::{FromColor, IntoColor, Srgb, Hsl}; //! //! let rgb = Srgb::new(0.3f32, 0.8, 0.1); //! //! let hsl1: Hsl = rgb.into_color(); //! let hsl2 = Hsl::from_color(rgb); //! ``` //! //! Most of the color space types can be converted directly to each other, with //! these traits. If you look at the implemented traits for any color type, you //! will see a substantial list of `FromColorUnclamped` implementations. There //! are, however, exceptions and restrictions in some cases: //! //! * **It's not always possible to change the component type while //! converting.** This can only be enabled in specific cases, to allow type //! inference to work. The input and output component types need to be the //! same in the general case. //! * **It's not always possible to change meta types while converting.** Meta //! types are the additional input types on colors, such as white point or RGB //! standard. Similar to component types, these are generally restricted to //! help type inference. //! * **Some color spaces want specific component types.** For example, //! [`Xyz`](crate::Xyz) and many other color spaces require real-ish numbers //! (`f32`, `f64`, etc.). //! * **Some color spaces want specific meta types.** For example, //! [`Oklab`](crate::Oklab) requires the white point to be //! [`D65`](crate::white_point::D65). //! //! These limitations are usually the reason for why the compiler gives an error //! when calling `into_color`, `from_color`, or the corresponding unclamped //! methods. They are possible to work around by splitting the conversion into //! multiple steps. //! //! # In-place Conversion //! //! It's possible for some color spaces to be converted in-place, meaning the //! destination color will use the memory space of the source color. The //! requirement for this is that the source and destination color types have the //! same memory layout. That is, the same component types and the same number of //! components. This is verified by the [`ArrayCast`](crate::cast::ArrayCast) //! trait. //! //! In-place conversion is done with the [`FromColorMut`] and [`IntoColorMut`] //! traits, as well as their unclamped counterparts, [`FromColorUnclampedMut`] //! and [`IntoColorUnclampedMut`]. They work for both single colors and slices //! of colors. //! //! ``` //! use palette::{convert::FromColorMut, Srgb, Hsl, Hwb}; //! //! let mut rgb_colors: Vec> = vec![/* ... */]; //! //! { //! // Creates a scope guard that prevents `rgb_colors` from being modified as RGB. //! let hsl_colors = <[Hsl]>::from_color_mut(&mut rgb_colors); //! //! // The converted colors can be converted again, without keeping the previous guard around. //! let hwb_colors = hsl_colors.then_into_color_mut::<[Hwb]>(); //! //! // The colors are automatically converted back to RGB at the end of the scope. //! // The use of `then_into_color_mut` above makes this conversion a single HWB -> RGB step, //! // instead of HWB -> HSL -> RGB, since it consumed the HSL guard. //! } //! ``` //! //! # Deriving //! //! `FromColorUnclamped` can be derived in a mostly automatic way. The other //! traits are blanket implemented based on it. The default minimum requirement //! is to implement `FromColorUnclamped`, but it can also be customized to //! make use of generics and have other manual implementations. //! //! It is also recommended to derive or implement //! [`WithAlpha`](crate::WithAlpha), to be able to convert between all `Alpha` //! wrapped color types. //! //! ## Configuration Attributes //! //! The derives can be configured using one or more `#[palette(...)]` //! attributes. They can be attached to either the item itself, or to the //! fields. //! //! ``` //! # use palette::rgb::{RgbStandard, RgbSpace}; //! # use palette::convert::FromColorUnclamped; //! # use palette::{Xyz, stimulus::Stimulus}; //! # //! #[derive(FromColorUnclamped)] //! #[palette( //! component = "T", //! rgb_standard = "S", //! )] //! #[repr(C)] //! struct ExampleType { //! // ... //! #[palette(alpha)] //! alpha: T, //! standard: std::marker::PhantomData, //! } //! //! # impl FromColorUnclamped::WhitePoint, T>> for ExampleType //! # where //! # S: RgbStandard, //! # T: Stimulus, //! # { //! # fn from_color_unclamped(color: Xyz<::WhitePoint, T>) -> Self { //! # ExampleType {alpha: T::max_intensity(), standard: std::marker::PhantomData} //! # } //! # } //! # //! # impl FromColorUnclamped> for Xyz<::WhitePoint, T> //! # where //! # S: RgbStandard, //! # Self: Default, //! # { //! # fn from_color_unclamped(color: ExampleType) -> Self { //! # Xyz::default() //! # } //! # } //! ``` //! //! ### Item Attributes //! //! * `skip_derives(Luma, Rgb)`: No conversion derives will be implemented for //! these colors. They are instead to be implemented manually, and serve as the //! basis for the automatic implementations. //! //! * `white_point = "some::white_point::Type"`: Sets the white point type that //! should be used when deriving. The default is `D65`, but it may be any other //! type, including type parameters. //! //! * `component = "some::component::Type"`: Sets the color component type that //! should be used when deriving. The default is `f32`, but it may be any other //! type, including type parameters. //! //! * `rgb_standard = "some::rgb_standard::Type"`: Sets the RGB standard type //! that should be used when deriving. The default is to either use `Srgb` or a //! best effort to convert between standards, but sometimes it has to be set to //! a specific type. This also accepts type parameters. //! //! * `luma_standard = "some::rgb_standard::Type"`: Sets the Luma standard type //! that should be used when deriving, similar to `rgb_standard`. //! //! ### Field Attributes //! //! * `alpha`: Specifies field as the color's transparency value. //! //! ## Examples //! //! Minimum requirements implementation: //! //! ```rust //! use palette::convert::FromColorUnclamped; //! use palette::{Srgb, Xyz, IntoColor}; //! //! /// A custom version of Xyz that stores integer values from 0 to 100. //! #[derive(PartialEq, Debug, FromColorUnclamped)] //! struct Xyz100 { //! x: u8, //! y: u8, //! z: u8, //! } //! //! // We have to implement at least one "manual" conversion. The default //! // is to and from `Xyz`, but it can be customized with `skip_derives(...)`. //! impl FromColorUnclamped for Xyz100 { //! fn from_color_unclamped(color: Xyz) -> Xyz100 { //! Xyz100 { //! x: (color.x * 100.0) as u8, //! y: (color.y * 100.0) as u8, //! z: (color.z * 100.0) as u8, //! } //! } //! } //! //! impl FromColorUnclamped for Xyz { //! fn from_color_unclamped(color: Xyz100) -> Xyz { //! Xyz::new( //! color.x as f32 / 100.0, //! color.y as f32 / 100.0, //! color.z as f32 / 100.0, //! ) //! } //! } //! //! // Start with an Xyz100 color. //! let xyz = Xyz100 { //! x: 59, //! y: 75, //! z: 42, //! }; //! //! // Convert the color to sRGB. //! let rgb: Srgb = xyz.into_color(); //! //! assert_eq!(rgb.into_format(), Srgb::new(196u8, 238, 154)); //! ``` //! //! With generic components: //! //! ```rust //! #[macro_use] //! extern crate approx; //! //! use palette::cast::{ComponentsAs, ArrayCast}; //! use palette::rgb::{Rgb, RgbSpace, RgbStandard}; //! use palette::encoding::Linear; //! use palette::white_point::D65; //! use palette::convert::{FromColorUnclamped, IntoColorUnclamped}; //! use palette::{Hsv, Srgb, IntoColor}; //! //! /// sRGB, but with a reversed memory layout. //! #[derive(Copy, Clone, ArrayCast, FromColorUnclamped)] //! #[palette( //! skip_derives(Rgb), //! component = "T", //! rgb_standard = "palette::encoding::Srgb" //! )] //! #[repr(C)] // Makes sure the memory layout is as we want it. //! struct Bgr { //! blue: T, //! green: T, //! red: T, //! } //! //! // It converts from and into any linear Rgb type that has the //! // D65 white point, which is the default if we don't specify //! // anything else with the `white_point` attribute argument. //! impl FromColorUnclamped> for Rgb //! where //! S: RgbStandard, //! S::Space: RgbSpace, //! Srgb: IntoColorUnclamped>, //! { //! fn from_color_unclamped(color: Bgr) -> Rgb { //! Srgb::new(color.red, color.green, color.blue) //! .into_color_unclamped() //! } //! } //! //! impl FromColorUnclamped> for Bgr //! where //! S: RgbStandard, //! S::Space: RgbSpace, //! Srgb: FromColorUnclamped>, //! { //! fn from_color_unclamped(color: Rgb) -> Bgr { //! let color = Srgb::from_color_unclamped(color); //! Bgr { //! blue: color.blue, //! green: color.green, //! red: color.red, //! } //! } //! } //! //! fn main() { //! let buffer = vec![ //! 0.0f64, //! 0.0, //! 0.0, //! 0.0, //! 0.5, //! 0.25, //! ]; //! let buffer: &[Bgr<_>] = buffer.components_as(); //! let hsv: Hsv<_, f64> = buffer[1].into_color(); //! //! assert_relative_eq!(hsv, Hsv::new(90.0, 1.0, 0.5)); //! } //! ``` //! //! With alpha component: //! //! ```rust //! #[macro_use] //! extern crate approx; //! //! use palette::{LinSrgba, Srgb, IntoColor, WithAlpha}; //! use palette::rgb::Rgb; //! use palette::convert::{FromColorUnclamped, IntoColorUnclamped}; //! //! /// CSS style sRGB. //! #[derive(PartialEq, Debug, FromColorUnclamped, WithAlpha)] //! #[palette( //! skip_derives(Rgb), //! rgb_standard = "palette::encoding::Srgb" //! )] //! struct CssRgb { //! red: u8, //! green: u8, //! blue: u8, //! #[palette(alpha)] //! alpha: f32, //! } //! //! // We will write a conversion function for opaque RGB and //! // `impl_default_conversions` will take care of preserving //! // the transparency for us. //! impl FromColorUnclamped> for CssRgb //! where //! Srgb: FromColorUnclamped> //! { //! fn from_color_unclamped(color: Rgb) -> CssRgb{ //! let srgb = Srgb::from_color_unclamped(color) //! .into_format(); //! //! CssRgb { //! red: srgb.red, //! green: srgb.green, //! blue: srgb.blue, //! alpha: 1.0 //! } //! } //! } //! //! impl FromColorUnclamped for Rgb //! where //! Srgb: IntoColorUnclamped> //! { //! fn from_color_unclamped(color: CssRgb) -> Rgb{ //! Srgb::new(color.red, color.green, color.blue) //! .into_format() //! .into_color_unclamped() //! } //! } //! //! fn main() { //! let css_color = CssRgb { //! red: 187, //! green: 0, //! blue: 255, //! alpha: 0.3, //! }; //! let color: LinSrgba = css_color.into_color(); //! //! assert_relative_eq!(color, LinSrgba::new(0.496933, 0.0, 1.0, 0.3)); //! } //! ``` pub use self::{ from_into_color::*, from_into_color_mut::*, from_into_color_unclamped::*, from_into_color_unclamped_mut::*, try_from_into_color::*, }; mod from_into_color; mod from_into_color_mut; mod from_into_color_unclamped; mod from_into_color_unclamped_mut; mod try_from_into_color; #[cfg(test)] mod tests { use core::marker::PhantomData; use super::{FromColor, FromColorUnclamped, IntoColor}; use crate::{ bool_mask::{BoolMask, HasBoolMask}, encoding::linear::Linear, luma::{Luma, LumaStandard}, num::{One, Zero}, rgb::{Rgb, RgbSpace}, Alpha, Clamp, Hsl, Hsluv, Hsv, Hwb, IsWithinBounds, Lab, Lch, Luv, Xyz, Yxy, }; #[derive(FromColorUnclamped, WithAlpha)] #[palette( skip_derives(Xyz, Luma), component = "f64", rgb_standard = "Linear", palette_internal, palette_internal_not_base_type )] struct WithXyz(PhantomData); impl Clone for WithXyz { fn clone(&self) -> Self { *self } } impl Copy for WithXyz {} impl HasBoolMask for WithXyz { type Mask = bool; } impl IsWithinBounds for WithXyz { fn is_within_bounds(&self) -> bool { true } } impl Clamp for WithXyz { fn clamp(self) -> Self { self } } impl FromColorUnclamped> for WithXyz where S1: RgbSpace, S2: RgbSpace, { fn from_color_unclamped(_color: WithXyz) -> Self { WithXyz(PhantomData) } } impl FromColorUnclamped> for WithXyz { fn from_color_unclamped(_color: Xyz) -> Self { WithXyz(PhantomData) } } impl FromColorUnclamped> for Xyz { fn from_color_unclamped(_color: WithXyz) -> Xyz { Xyz::new(0.0, 1.0, 0.0) } } impl> FromColorUnclamped> for WithXyz { fn from_color_unclamped(_color: Luma) -> Self { WithXyz(PhantomData) } } impl> FromColorUnclamped> for Luma { fn from_color_unclamped(_color: WithXyz) -> Self { Luma::new(1.0) } } #[derive(Copy, Clone, FromColorUnclamped, WithAlpha)] #[palette( skip_derives(Lch, Luma), white_point = "crate::white_point::E", component = "T", rgb_standard = "Linear<(crate::encoding::Srgb, crate::white_point::E)>", palette_internal, palette_internal_not_base_type )] struct WithoutXyz(PhantomData); impl HasBoolMask for WithoutXyz where T: HasBoolMask, { type Mask = T::Mask; } impl IsWithinBounds for WithoutXyz where T: HasBoolMask, { fn is_within_bounds(&self) -> T::Mask { T::Mask::from_bool(true) } } impl Clamp for WithoutXyz { fn clamp(self) -> Self { self } } impl FromColorUnclamped> for WithoutXyz { fn from_color_unclamped(color: WithoutXyz) -> Self { color } } impl FromColorUnclamped> for WithoutXyz { fn from_color_unclamped(_color: Lch) -> Self { WithoutXyz(PhantomData) } } impl FromColorUnclamped> for Lch { fn from_color_unclamped(_color: WithoutXyz) -> Lch { Lch::new(T::one(), T::zero(), T::zero()) } } impl FromColorUnclamped, T>> for WithoutXyz { fn from_color_unclamped(_color: Luma, T>) -> Self { WithoutXyz(PhantomData) } } impl FromColorUnclamped> for Luma, T> { fn from_color_unclamped(_color: WithoutXyz) -> Luma, T> { Luma::new(T::one()) } } #[test] fn from_with_xyz() { let color: WithXyz = WithXyz(Default::default()); let _ = WithXyz::::from_color(color); let xyz: Xyz<_, f64> = Default::default(); let _ = WithXyz::::from_color(xyz); let yxy: Yxy<_, f64> = Default::default(); let _ = WithXyz::::from_color(yxy); let lab: Lab<_, f64> = Default::default(); let _ = WithXyz::::from_color(lab); let lch: Lch<_, f64> = Default::default(); let _ = WithXyz::::from_color(lch); let luv: Hsl<_, f64> = Default::default(); let _ = WithXyz::::from_color(luv); let rgb: Rgb<_, f64> = Default::default(); let _ = WithXyz::::from_color(rgb); let hsl: Hsl<_, f64> = Default::default(); let _ = WithXyz::::from_color(hsl); let hsluv: Hsluv<_, f64> = Default::default(); let _ = WithXyz::::from_color(hsluv); let hsv: Hsv<_, f64> = Default::default(); let _ = WithXyz::::from_color(hsv); let hwb: Hwb<_, f64> = Default::default(); let _ = WithXyz::::from_color(hwb); let luma: Luma = Default::default(); let _ = WithXyz::::from_color(luma); } #[test] fn from_with_xyz_alpha() { let color: Alpha, u8> = Alpha::from(WithXyz(Default::default())); let _ = WithXyz::::from_color(color); let xyz: Alpha, u8> = Alpha::from(Xyz::default()); let _ = WithXyz::::from_color(xyz); let yxy: Alpha, u8> = Alpha::from(Yxy::default()); let _ = WithXyz::::from_color(yxy); let lab: Alpha, u8> = Alpha::from(Lab::default()); let _ = WithXyz::::from_color(lab); let lch: Alpha, u8> = Alpha::from(Lch::default()); let _ = WithXyz::::from_color(lch); let luv: Alpha, u8> = Alpha::from(Luv::default()); let _ = WithXyz::::from_color(luv); let rgb: Alpha, u8> = Alpha::from(Rgb::default()); let _ = WithXyz::::from_color(rgb); let hsl: Alpha, u8> = Alpha::from(Hsl::default()); let _ = WithXyz::::from_color(hsl); let hsluv: Alpha, u8> = Alpha::from(Hsluv::default()); let _ = WithXyz::::from_color(hsluv); let hsv: Alpha, u8> = Alpha::from(Hsv::default()); let _ = WithXyz::::from_color(hsv); let hwb: Alpha, u8> = Alpha::from(Hwb::default()); let _ = WithXyz::::from_color(hwb); let luma: Alpha, u8> = Alpha::from(Luma::::default()); let _ = WithXyz::::from_color(luma); } #[test] fn from_with_xyz_into_alpha() { let color: WithXyz = WithXyz(Default::default()); let _ = Alpha::, u8>::from_color(color); let xyz: Xyz<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(xyz); let yxy: Yxy<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(yxy); let lab: Lab<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(lab); let lch: Lch<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(lch); let luv: Hsl<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(luv); let rgb: Rgb<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(rgb); let hsl: Hsl<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hsl); let hsluv: Hsluv<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hsluv); let hsv: Hsv<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hsv); let hwb: Hwb<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hwb); let luma: Luma = Default::default(); let _ = Alpha::, u8>::from_color(luma); } #[test] fn from_with_xyz_alpha_into_alpha() { let color: Alpha, u8> = Alpha::from(WithXyz(Default::default())); let _ = Alpha::, u8>::from_color(color); let xyz: Xyz<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(xyz); let yxy: Yxy<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(yxy); let lab: Lab<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(lab); let lch: Lch<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(lch); let luv: Luv<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(luv); let rgb: Rgb<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(rgb); let hsl: Hsl<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hsl); let hsluv: Hsluv<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hsluv); let hsv: Hsv<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hsv); let hwb: Hwb<_, f64> = Default::default(); let _ = Alpha::, u8>::from_color(hwb); let luma: Luma = Default::default(); let _ = Alpha::, u8>::from_color(luma); } #[test] fn into_from_with_xyz() { let color = WithXyz::(PhantomData); let _self: WithXyz = color.into_color(); let _xyz: Xyz<_, f64> = color.into_color(); let _yxy: Yxy<_, f64> = color.into_color(); let _lab: Lab<_, f64> = color.into_color(); let _lch: Lch<_, f64> = color.into_color(); let _luv: Luv<_, f64> = color.into_color(); let _rgb: Rgb<_, f64> = color.into_color(); let _hsl: Hsl<_, f64> = color.into_color(); let _hsluv: Hsluv<_, f64> = color.into_color(); let _hsv: Hsv<_, f64> = color.into_color(); let _hwb: Hwb<_, f64> = color.into_color(); let _luma: Luma = color.into_color(); } #[test] fn into_from_with_xyz_alpha() { let color: Alpha, u8> = Alpha::from(WithXyz::(PhantomData)); let _self: WithXyz = color.into_color(); let _xyz: Xyz<_, f64> = color.into_color(); let _yxy: Yxy<_, f64> = color.into_color(); let _lab: Lab<_, f64> = color.into_color(); let _lch: Lch<_, f64> = color.into_color(); let _luv: Luv<_, f64> = color.into_color(); let _rgb: Rgb<_, f64> = color.into_color(); let _hsl: Hsl<_, f64> = color.into_color(); let _hsluv: Hsluv<_, f64> = color.into_color(); let _hsv: Hsv<_, f64> = color.into_color(); let _hwb: Hwb<_, f64> = color.into_color(); let _luma: Luma = color.into_color(); } #[test] fn into_alpha_from_with_xyz() { let color = WithXyz::(PhantomData); let _self: Alpha, u8> = color.into_color(); let _xyz: Alpha, u8> = color.into_color(); let _yxy: Alpha, u8> = color.into_color(); let _lab: Alpha, u8> = color.into_color(); let _lch: Alpha, u8> = color.into_color(); let _luv: Alpha, u8> = color.into_color(); let _rgb: Alpha, u8> = color.into_color(); let _hsl: Alpha, u8> = color.into_color(); let _hsluv: Alpha, u8> = color.into_color(); let _hsv: Alpha, u8> = color.into_color(); let _hwb: Alpha, u8> = color.into_color(); let _luma: Alpha, u8> = color.into_color(); } #[test] fn into_alpha_from_with_xyz_alpha() { let color: Alpha, u8> = Alpha::from(WithXyz::(PhantomData)); let _self: Alpha, u8> = color.into_color(); let _xyz: Alpha, u8> = color.into_color(); let _yxy: Alpha, u8> = color.into_color(); let _lab: Alpha, u8> = color.into_color(); let _lch: Alpha, u8> = color.into_color(); let _luv: Alpha, u8> = color.into_color(); let _rgb: Alpha, u8> = color.into_color(); let _hsl: Alpha, u8> = color.into_color(); let _hsluv: Alpha, u8> = color.into_color(); let _hsv: Alpha, u8> = color.into_color(); let _hwb: Alpha, u8> = color.into_color(); let _luma: Alpha, u8> = color.into_color(); } #[test] fn from_without_xyz() { let color: WithoutXyz = WithoutXyz(Default::default()); let _ = WithoutXyz::::from_color(color); let xyz: Xyz = Default::default(); let _ = WithoutXyz::::from_color(xyz); let yxy: Yxy = Default::default(); let _ = WithoutXyz::::from_color(yxy); let lab: Lab = Default::default(); let _ = WithoutXyz::::from_color(lab); let lch: Lch = Default::default(); let _ = WithoutXyz::::from_color(lch); let luv: Luv = Default::default(); let _ = WithoutXyz::::from_color(luv); let rgb: Rgb<_, f64> = Default::default(); let _ = WithoutXyz::::from_color(rgb); let hsl: Hsl<_, f64> = Default::default(); let _ = WithoutXyz::::from_color(hsl); let hsluv: Hsluv<_, f64> = Default::default(); let _ = WithoutXyz::::from_color(hsluv); let hsv: Hsv<_, f64> = Default::default(); let _ = WithoutXyz::::from_color(hsv); let hwb: Hwb<_, f64> = Default::default(); let _ = WithoutXyz::::from_color(hwb); let luma: Luma, f64> = Default::default(); let _ = WithoutXyz::::from_color(luma); } #[test] fn into_without_xyz() { let color = WithoutXyz::(PhantomData); let _self: WithoutXyz = color.into_color(); let _xyz: Xyz = color.into_color(); let _yxy: Yxy = color.into_color(); let _lab: Lab = color.into_color(); let _lch: Lch = color.into_color(); let _luv: Luv = color.into_color(); let _rgb: Rgb<_, f64> = color.into_color(); let _hsl: Hsl<_, f64> = color.into_color(); let _hsluv: Hsluv<_, f64> = color.into_color(); let _hsv: Hsv<_, f64> = color.into_color(); let _hwb: Hwb<_, f64> = color.into_color(); let _luma: Luma, f64> = color.into_color(); } } palette-0.7.5/src/encoding/gamma.rs000064400000000000000000000043341046102023000153040ustar 00000000000000//! Gamma encoding. use core::{marker::PhantomData, ops::Div}; use crate::{ luma::LumaStandard, num::{One, Powf, Real}, rgb::{RgbSpace, RgbStandard}, }; use super::{FromLinear, IntoLinear}; /// Gamma encoding. /// /// Gamma encoding or gamma correction is used to transform the intensity /// values to either match a non-linear display, like CRT, or to prevent /// banding among the darker colors. `GammaRgb` represents a gamma corrected /// RGB color, where the intensities are encoded using the following power-law /// expression: _V γ_ (where _V_ is the intensity value an _γ_ is the /// encoding gamma). /// /// The gamma value is stored as a simple type that represents an `f32` /// constant. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Gamma(PhantomData<(S, N)>); impl RgbStandard for Gamma where Sp: RgbSpace, N: Number, { type Space = Sp; type TransferFn = GammaFn; } impl LumaStandard for Gamma where N: Number, { type WhitePoint = Wp; type TransferFn = GammaFn; } /// The transfer function for gamma encoded colors. /// /// Conversion is performed using a single `powf(x, gamma)` and `powf(x, 1.0 / /// gamma)` call, for from and into linear respectively. This makes /// `GammaFn` usable as a slightly less expensive approximation of the /// [`Srgb`][super::Srgb] transfer function. /// /// The gamma value is stored as a simple type that represents an `f32` /// constant. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct GammaFn(PhantomData); impl IntoLinear for GammaFn where T: Real + One + Powf + Div, N: Number, { #[inline] fn into_linear(x: T) -> T { x.powf(T::one() / T::from_f64(N::VALUE)) } } impl FromLinear for GammaFn where T: Real + Powf, N: Number, { #[inline] fn from_linear(x: T) -> T { x.powf(T::from_f64(N::VALUE)) } } /// A type level float constant. pub trait Number: 'static { /// The represented number. const VALUE: f64; } /// Represents `2.2f64`. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct F2p2; impl Number for F2p2 { const VALUE: f64 = 2.2; } palette-0.7.5/src/encoding/linear.rs000064400000000000000000000020021046102023000154620ustar 00000000000000//! Linear encoding use core::marker::PhantomData; use crate::{ luma::LumaStandard, rgb::{RgbSpace, RgbStandard}, }; use super::{FromLinear, IntoLinear}; /// A generic standard with linear components. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Linear(PhantomData); impl RgbStandard for Linear where Sp: RgbSpace, { type Space = Sp; type TransferFn = LinearFn; } impl LumaStandard for Linear { type WhitePoint = Wp; type TransferFn = LinearFn; } /// Linear color component encoding. /// /// Converting anything from linear to linear space is a no-op and constant /// time. This is a useful property in generic code, where the transfer /// functions may be unknown. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct LinearFn; impl IntoLinear for LinearFn { #[inline(always)] fn into_linear(x: T) -> T { x } } impl FromLinear for LinearFn { #[inline(always)] fn from_linear(x: T) -> T { x } } palette-0.7.5/src/encoding/srgb/lookup_tables.rs000064400000000000000000000143101046102023000200150ustar 00000000000000pub const SRGB_U8_TO_F64: [f64; 256] = [ 0.0, 0.0003035269910469651, 0.0006070539820939302, 0.0009105810313485563, 0.0012141079641878605, 0.0015176349552348256, 0.0018211620626971126, 0.0021246890537440777, 0.002428215928375721, 0.002731743035838008, 0.0030352699104696512, 0.003346534911543131, 0.0036765062250196934, 0.004024716559797525, 0.004391441121697426, 0.00477695232257247, 0.005181515123695135, 0.005605390761047602, 0.00604883162304759, 0.006512089166790247, 0.006995408330112696, 0.007499029394239187, 0.008023192174732685, 0.00856812484562397, 0.009134056977927685, 0.009721215814352036, 0.010329820215702057, 0.010960093699395657, 0.011612244881689548, 0.01228648703545332, 0.012983030639588833, 0.013702080585062504, 0.014443840831518173, 0.015208511613309383, 0.015996290370821953, 0.016807375475764275, 0.017641952261328697, 0.01850021816790104, 0.019382357597351074, 0.020288558676838875, 0.021219005808234215, 0.02217387966811657, 0.023153364658355713, 0.024157630279660225, 0.02518685720860958, 0.02624121867120266, 0.02732088789343834, 0.0284260343760252, 0.02955683320760727, 0.030713440850377083, 0.03189602866768837, 0.03310476616024971, 0.03433980792760849, 0.03560131415724754, 0.036889445036649704, 0.038204375654459, 0.039546236395835876, 0.040915198624134064, 0.0423114113509655, 0.04373502731323242, 0.045186202973127365, 0.04666508361697197, 0.04817182198166847, 0.04970656335353851, 0.05126945301890373, 0.05286063998937607, 0.05448026955127716, 0.05612848326563835, 0.05780542269349098, 0.059511229395866394, 0.06124604120850563, 0.06301002204418182, 0.06480326503515244, 0.0666259378194809, 0.06847816705703735, 0.07036009430885315, 0.07227184623479843, 0.07421356439590454, 0.07618537545204163, 0.07818741351366043, 0.0802198126912117, 0.08228269964456558, 0.08437620103359222, 0.08650045096874237, 0.08865556865930557, 0.09084169566631317, 0.09305896610021591, 0.09530746191740036, 0.09758734703063965, 0.09989872574806213, 0.10224172472953796, 0.10461647808551788, 0.10702309757471085, 0.1094617024064064, 0.1119324192404747, 0.1144353598356247, 0.11697067320346832, 0.11953843384981155, 0.12213877588510513, 0.12477181851863861, 0.12743768095970154, 0.13013647496700287, 0.13286831974983215, 0.13563334941864014, 0.1384316235780716, 0.14126330614089966, 0.1441284865140915, 0.14702728390693665, 0.14995980262756348, 0.15292616188526154, 0.15592646598815918, 0.15896083414554596, 0.16202937066555023, 0.16513219475746155, 0.16826939582824707, 0.17144109308719635, 0.17464739084243774, 0.1778884083032608, 0.1811642348766327, 0.18447500467300415, 0.18782079219818115, 0.19120167195796967, 0.19461780786514282, 0.19806930422782898, 0.2015562355518341, 0.20507870614528656, 0.20863685011863708, 0.21223078668117523, 0.2158605307340622, 0.2195262312889099, 0.22322797775268555, 0.22696588933467865, 0.23074007034301758, 0.2345505952835083, 0.23839758336544037, 0.24228113889694214, 0.24620133638381958, 0.25015828013420105, 0.2541520893573761, 0.2581828534603119, 0.2622506618499756, 0.26635560393333435, 0.27049779891967773, 0.2746773064136505, 0.27889424562454224, 0.28314873576164246, 0.28744083642959595, 0.2917706370353699, 0.2961382567882538, 0.30054378509521484, 0.3049872815608978, 0.3094688951969147, 0.31398868560791016, 0.31854674220085144, 0.3231431841850281, 0.32777807116508484, 0.3324514925479889, 0.3371635675430298, 0.3419143855571747, 0.3467040956020355, 0.35153263807296753, 0.35640016198158264, 0.3613067865371704, 0.366252601146698, 0.37123769521713257, 0.3762621283531189, 0.38132601976394653, 0.38642942905426025, 0.3915724754333496, 0.3967552185058594, 0.4019777774810791, 0.40724021196365356, 0.4125426113605499, 0.41788506507873535, 0.423267662525177, 0.42869046330451965, 0.43415361642837524, 0.43965715169906616, 0.44520115852355957, 0.450785756111145, 0.4564109742641449, 0.46207696199417114, 0.46778374910354614, 0.47353145480155945, 0.47932013869285583, 0.48514989018440247, 0.4910207986831665, 0.4969329237937927, 0.5028864145278931, 0.5088812708854675, 0.5149176716804504, 0.5209956169128418, 0.5271152257919312, 0.5332764983177185, 0.5394796133041382, 0.5457245707511902, 0.5520114898681641, 0.5583404898643494, 0.5647116303443909, 0.5711249113082886, 0.5775805115699768, 0.5840784907341003, 0.590618908405304, 0.5972018837928772, 0.6038274168968201, 0.6104956269264221, 0.6172066330909729, 0.6239604353904724, 0.630757212638855, 0.6375969648361206, 0.6444797515869141, 0.6514056921005249, 0.6583748459815979, 0.6653873324394226, 0.6724432110786438, 0.6795425415039063, 0.68668532371521, 0.693871796131134, 0.7011018991470337, 0.7083758115768433, 0.7156935334205627, 0.7230551242828369, 0.7304607629776001, 0.7379104495048523, 0.7454043626785278, 0.7529423236846924, 0.7605246305465698, 0.7681512832641602, 0.7758223414421082, 0.7835379242897034, 0.7912980318069458, 0.7991028428077698, 0.8069523572921753, 0.8148466944694519, 0.8227858543395996, 0.830769956111908, 0.8387991189956665, 0.8468732833862305, 0.854992687702179, 0.8631572723388672, 0.8713672161102295, 0.8796224594116211, 0.8879231810569763, 0.8962693810462952, 0.904661238193512, 0.9130986928939819, 0.9215819239616394, 0.9301108717918396, 0.9386857748031616, 0.9473065733909607, 0.9559733867645264, 0.9646862745285034, 0.9734452962875366, 0.9822505712509155, 0.9911020994186401, 1.0, ]; palette-0.7.5/src/encoding/srgb.rs000064400000000000000000000124041046102023000151540ustar 00000000000000//! The sRGB standard. use crate::{ bool_mask::LazySelect, encoding::{FromLinear, IntoLinear}, luma::LumaStandard, num::{Arithmetics, MulAdd, MulSub, PartialCmp, Powf, Real}, rgb::{Primaries, RgbSpace, RgbStandard}, white_point::{Any, D65}, Mat3, Yxy, }; use lookup_tables::*; mod lookup_tables; /// The sRGB standard, color space, and transfer function. /// /// # As transfer function /// /// `Srgb` will not use any kind of approximation when converting from `T` to /// `T`. This involves calls to `powf`, which may make it too slow for certain /// applications. /// /// There are some specialized cases where it has been optimized: /// /// * When converting from `u8` to `f32` or `f64`, while converting to linear /// space. This uses lookup tables with precomputed values. `f32` will use the /// table provided by [fast_srgb8::srgb8_to_f32]. /// * When converting from `f32` or `f64` to `u8`, while converting from linear /// space. This uses [fast_srgb8::f32_to_srgb8]. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Srgb; impl Primaries for Srgb { fn red() -> Yxy { Yxy::new( T::from_f64(0.6400), T::from_f64(0.3300), T::from_f64(0.212656), ) } fn green() -> Yxy { Yxy::new( T::from_f64(0.3000), T::from_f64(0.6000), T::from_f64(0.715158), ) } fn blue() -> Yxy { Yxy::new( T::from_f64(0.1500), T::from_f64(0.0600), T::from_f64(0.072186), ) } } impl RgbSpace for Srgb { type Primaries = Srgb; type WhitePoint = D65; #[rustfmt::skip] #[inline(always)] fn rgb_to_xyz_matrix() -> Option> { // Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html Some([ 0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.0721750, 0.0193339, 0.1191920, 0.9503041, ]) } #[rustfmt::skip] #[inline(always)] fn xyz_to_rgb_matrix() -> Option> { // Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html Some([ 3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252, ]) } } impl RgbStandard for Srgb { type Space = Srgb; type TransferFn = Srgb; } impl LumaStandard for Srgb { type WhitePoint = D65; type TransferFn = Srgb; } impl IntoLinear for Srgb where T: Real + Powf + MulAdd + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { #[inline] fn into_linear(x: T) -> T { // Dividing the constants directly shows performance benefits in benchmarks for this function lazy_select! { if x.lt_eq(&T::from_f64(0.04045)) => T::from_f64(1.0 / 12.92) * &x, else => x.clone().mul_add(T::from_f64(1.0 / 1.055), T::from_f64(0.055 / 1.055)).powf(T::from_f64(2.4)), } } } impl FromLinear for Srgb where T: Real + Powf + MulSub + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { #[inline] fn from_linear(x: T) -> T { lazy_select! { if x.lt_eq(&T::from_f64(0.0031308)) => T::from_f64(12.92) * &x, else => x.clone().powf(T::from_f64(1.0 / 2.4)).mul_sub(T::from_f64(1.055), T::from_f64(0.055)), } } } impl IntoLinear for Srgb { #[inline] fn into_linear(encoded: u8) -> f32 { fast_srgb8::srgb8_to_f32(encoded) } } impl FromLinear for Srgb { #[inline] fn from_linear(linear: f32) -> u8 { fast_srgb8::f32_to_srgb8(linear) } } impl IntoLinear for Srgb { #[inline] fn into_linear(encoded: u8) -> f64 { SRGB_U8_TO_F64[encoded as usize] } } impl FromLinear for Srgb { #[inline] fn from_linear(linear: f64) -> u8 { Srgb::from_linear(linear as f32) } } #[cfg(test)] mod test { use crate::encoding::{FromLinear, IntoLinear, Srgb}; #[cfg(feature = "approx")] mod conversion { use crate::{ encoding::Srgb, matrix::{matrix_inverse, rgb_to_xyz_matrix}, rgb::RgbSpace, }; #[test] fn rgb_to_xyz() { let dynamic = rgb_to_xyz_matrix::(); let constant = Srgb::rgb_to_xyz_matrix().unwrap(); assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001); } #[test] fn xyz_to_rgb() { let dynamic = matrix_inverse(rgb_to_xyz_matrix::()); let constant = Srgb::xyz_to_rgb_matrix().unwrap(); assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001); } } #[test] fn u8_to_f32_to_u8() { for expected in 0u8..=255u8 { let linear: f32 = Srgb::into_linear(expected); let result: u8 = Srgb::from_linear(linear); assert_eq!(result, expected); } } #[test] fn u8_to_f64_to_u8() { for expected in 0u8..=255u8 { let linear: f64 = Srgb::into_linear(expected); let result: u8 = Srgb::from_linear(linear); assert_eq!(result, expected); } } } palette-0.7.5/src/encoding.rs000064400000000000000000000015151046102023000142200ustar 00000000000000//! Number and color encoding traits, types and standards. //! //! Some color spaces, particularly RGB, may be encoded in more than one way and //! may have more than one standard. These encodings and standards are //! represented as type parameters in Palette, as a form of type branding, to //! prevent accidental mixups. pub use self::gamma::{F2p2, Gamma}; pub use self::linear::Linear; pub use self::srgb::Srgb; pub mod gamma; pub mod linear; pub mod srgb; /// A transfer function from linear space. pub trait FromLinear { /// Convert the color component `linear` from linear space. #[must_use] fn from_linear(linear: L) -> E; } /// A transfer function to linear space. pub trait IntoLinear { /// Convert the color component `encoded` into linear space. #[must_use] fn into_linear(encoded: E) -> L; } palette-0.7.5/src/hsl.rs000064400000000000000000000545611046102023000132310ustar 00000000000000//! Types for the HSL color space. use core::{any::TypeId, marker::PhantomData, ops::Not}; use crate::{ angle::{FromAngle, RealAngle}, bool_mask::{BitOps, BoolMask, HasBoolMask, LazySelect, Select}, convert::FromColorUnclamped, encoding::Srgb, hues::RgbHueIter, num::{Arithmetics, IsValidDivisor, MinMax, One, PartialCmp, Real, Zero}, rgb::{Rgb, RgbSpace, RgbStandard}, stimulus::{FromStimulus, Stimulus}, Alpha, FromColor, Hsv, RgbHue, Xyz, }; /// Linear HSL with an alpha component. See the [`Hsla` implementation in /// `Alpha`](crate::Alpha#Hsla). pub type Hsla = Alpha, T>; /// HSL color space. /// /// The HSL color space can be seen as a cylindrical version of /// [RGB](crate::rgb::Rgb), where the `hue` is the angle around the color /// cylinder, the `saturation` is the distance from the center, and the /// `lightness` is the height from the bottom. Its composition makes it /// especially good for operations like changing green to red, making a color /// more gray, or making it darker. /// /// HSL component values are typically real numbers (such as floats), but may /// also be converted to and from `u8` for storage and interoperability /// purposes. The hue is then within the range `[0, 255]`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::Hsl; /// /// let hsl_u8 = Hsl::new_srgb(128u8, 85, 51); /// let hsl_f32 = hsl_u8.into_format::(); /// /// assert_relative_eq!(hsl_f32, Hsl::new(180.0, 1.0 / 3.0, 0.2)); /// ``` /// /// See [HSV](crate::Hsv) for a very similar color space, with brightness /// instead of lightness. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, rgb_standard = "S", component = "T", skip_derives(Rgb, Hsv, Hsl) )] #[repr(C)] pub struct Hsl { /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. #[palette(unsafe_same_layout_as = "T")] pub hue: RgbHue, /// The colorfulness of the color. 0.0 gives gray scale colors and 1.0 will /// give absolutely clear colors. pub saturation: T, /// Decides how light the color will look. 0.0 will be black, 0.5 will give /// a clear color, and 1.0 will give white. pub lightness: T, /// The white point and RGB primaries this color is adapted to. The default /// is the sRGB standard. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub standard: PhantomData, } impl Hsl { /// Create an sRGB HSL color. This method can be used instead of `Hsl::new` /// to help type inference. pub fn new_srgb>>(hue: H, saturation: T, lightness: T) -> Self { Self::new_const(hue.into(), saturation, lightness) } /// Create an sRGB HSL color. This is the same as `Hsl::new_srgb` without /// the generic hue type. It's temporary until `const fn` supports traits. pub const fn new_srgb_const(hue: RgbHue, saturation: T, lightness: T) -> Self { Self::new_const(hue, saturation, lightness) } } impl Hsl { /// Create an HSL color. pub fn new>>(hue: H, saturation: T, lightness: T) -> Self { Self::new_const(hue.into(), saturation, lightness) } /// Create an HSL color. This is the same as `Hsl::new` without the generic /// hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: RgbHue, saturation: T, lightness: T) -> Self { Hsl { hue, saturation, lightness, standard: PhantomData, } } /// Convert into another component type. pub fn into_format(self) -> Hsl where U: FromStimulus + FromAngle, { Hsl { hue: self.hue.into_format(), saturation: U::from_stimulus(self.saturation), lightness: U::from_stimulus(self.lightness), standard: PhantomData, } } /// Convert from another component type. pub fn from_format(color: Hsl) -> Self where T: FromStimulus + FromAngle, { color.into_format() } /// Convert to a `(hue, saturation, lightness)` tuple. pub fn into_components(self) -> (RgbHue, T, T) { (self.hue, self.saturation, self.lightness) } /// Convert from a `(hue, saturation, lightness)` tuple. pub fn from_components>>((hue, saturation, lightness): (H, T, T)) -> Self { Self::new(hue, saturation, lightness) } #[inline] fn reinterpret_as(self) -> Hsl { Hsl { hue: self.hue, saturation: self.saturation, lightness: self.lightness, standard: PhantomData, } } } impl Hsl where T: Stimulus, { /// Return the `saturation` value minimum. pub fn min_saturation() -> T { T::zero() } /// Return the `saturation` value maximum. pub fn max_saturation() -> T { T::max_intensity() } /// Return the `lightness` value minimum. pub fn min_lightness() -> T { T::zero() } /// Return the `lightness` value maximum. pub fn max_lightness() -> T { T::max_intensity() } } ///[`Hsla`](crate::Hsla) implementations. impl Alpha, A> { /// Create an sRGB HSL color with transparency. This method can be used /// instead of `Hsla::new` to help type inference. pub fn new_srgb>>(hue: H, saturation: T, lightness: T, alpha: A) -> Self { Self::new_const(hue.into(), saturation, lightness, alpha) } /// Create an sRGB HSL color with transparency. This is the same as /// `Hsla::new_srgb` without the generic hue type. It's temporary until /// `const fn` supports traits. pub const fn new_srgb_const(hue: RgbHue, saturation: T, lightness: T, alpha: A) -> Self { Self::new_const(hue, saturation, lightness, alpha) } } ///[`Hsla`](crate::Hsla) implementations. impl Alpha, A> { /// Create an HSL color with transparency. pub fn new>>(hue: H, saturation: T, lightness: T, alpha: A) -> Self { Self::new_const(hue.into(), saturation, lightness, alpha) } /// Create an HSL color with transparency. This is the same as `Hsla::new` /// without the generic hue type. It's temporary until `const fn` supports /// traits. pub const fn new_const(hue: RgbHue, saturation: T, lightness: T, alpha: A) -> Self { Alpha { color: Hsl::new_const(hue, saturation, lightness), alpha, } } /// Convert into another component type. pub fn into_format(self) -> Alpha, B> where U: FromStimulus + FromAngle, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert from another component type. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus + FromAngle, A: FromStimulus, { color.into_format() } /// Convert to a `(hue, saturation, lightness, alpha)` tuple. pub fn into_components(self) -> (RgbHue, T, T, A) { ( self.color.hue, self.color.saturation, self.color.lightness, self.alpha, ) } /// Convert from a `(hue, saturation, lightness, alpha)` tuple. pub fn from_components>>( (hue, saturation, lightness, alpha): (H, T, T, A), ) -> Self { Self::new(hue, saturation, lightness, alpha) } } impl_reference_component_methods_hue!(Hsl, [saturation, lightness], standard); impl_struct_of_arrays_methods_hue!(Hsl, [saturation, lightness], standard); impl FromColorUnclamped> for Hsl where S1: RgbStandard + 'static, S2: RgbStandard + 'static, S1::Space: RgbSpace::WhitePoint>, Rgb: FromColorUnclamped>, Rgb: FromColorUnclamped>, Self: FromColorUnclamped>, { #[inline] fn from_color_unclamped(hsl: Hsl) -> Self { if TypeId::of::() == TypeId::of::() { hsl.reinterpret_as() } else { let rgb = Rgb::::from_color_unclamped(hsl); let converted_rgb = Rgb::::from_color_unclamped(rgb); Self::from_color_unclamped(converted_rgb) } } } impl FromColorUnclamped> for Hsl where T: RealAngle + Zero + One + MinMax + Arithmetics + PartialCmp + Clone, T::Mask: BoolMask + BitOps + LazySelect + Clone + 'static, { fn from_color_unclamped(rgb: Rgb) -> Self { // Avoid negative numbers let red = rgb.red.max(T::zero()); let green = rgb.green.max(T::zero()); let blue = rgb.blue.max(T::zero()); // The SIMD optimized version showed significant slowdown for regular floats. if TypeId::of::() == TypeId::of::() { let (max, min, sep, coeff) = { let (max, min, sep, coeff) = if red.gt(&green).is_true() { (red.clone(), green.clone(), green.clone() - &blue, T::zero()) } else { ( green.clone(), red.clone(), blue.clone() - &red, T::from_f64(2.0), ) }; if blue.gt(&max).is_true() { (blue, min, red - green, T::from_f64(4.0)) } else { let min_val = if blue.lt(&min).is_true() { blue } else { min }; (max, min_val, sep, coeff) } }; let mut h = T::zero(); let mut s = T::zero(); let sum = max.clone() + &min; let l = sum.clone() / T::from_f64(2.0); if max.neq(&min).is_true() { let d = max - min; s = if sum.gt(&T::one()).is_true() { d.clone() / (T::from_f64(2.0) - sum) } else { d.clone() / sum }; h = ((sep / d) + coeff) * T::from_f64(60.0); }; Hsl { hue: h.into(), saturation: s, lightness: l, standard: PhantomData, } } else { // Based on OPTIMIZED RGB TO HSV COLOR CONVERSION USING SSE TECHNOLOGY // by KOBALICEK, Petr & BLIZNAK, Michal // // This implementation assumes less about the underlying mask and number // representation. The hue is also multiplied by 6 to avoid rounding // errors when using degrees. let six = T::from_f64(6.0); let max = red.clone().max(green.clone()).max(blue.clone()); let min = red.clone().min(green.clone()).min(blue.clone()); let sum = max.clone() + &min; let lightness = T::from_f64(0.5) * ∑ let chroma = max.clone() - &min; let saturation = lazy_select! { if min.eq(&max) => T::zero(), else => chroma.clone() / sum.gt(&T::one()).select(T::from_f64(2.0) - &sum, sum.clone()), }; // Each of these represents an RGB component. The maximum will be false // while the two other will be true. They are later used for determining // which branch in the hue equation we end up in. let x = max.neq(&red); let y = max.eq(&red) | max.neq(&green); let z = max.eq(&red) | max.eq(&green); // The hue base is the `1`, `2/6`, `4/6` or 0 part of the hue equation, // except it's multiplied by 6 here. let hue_base = x.clone().select( z.clone().select(T::from_f64(-4.0), T::from_f64(4.0)), T::zero(), ) + &six; // Each of these is a part of `G - B`, `B - R`, `R - G` or 0 from the // hue equation. They become positive, negative or 0, depending on which // branch we should be in. This makes the sum of all three combine as // expected. let red_m = lazy_select! { if x => y.clone().select(red.clone(), -red), else => T::zero(), }; let green_m = lazy_select! { if y.clone() => z.clone().select(green.clone(), -green), else => T::zero(), }; let blue_m = lazy_select! { if z => y.select(-blue.clone(), blue), else => T::zero(), }; // This is the hue equation parts combined. The hue base is the constant // and the RGB components are masked so up to two of them are non-zero. // Once again, this is multiplied by 6, so the chroma isn't multiplied // before dividing. // // We also avoid dividing by 0 for non-SIMD values. let hue = lazy_select! { if chroma.eq(&T::zero()) => T::zero(), else => hue_base + (red_m + green_m + blue_m) / &chroma, }; // hue will always be within [0, 12) (it's multiplied by 6, compared to // the paper), so we can subtract by 6 instead of using % to get it // within [0, 6). let hue_sub = hue.gt_eq(&six).select(six, T::zero()); let hue = hue - hue_sub; Hsl { hue: RgbHue::from_degrees(hue * T::from_f64(60.0)), saturation, lightness, standard: PhantomData, } } } } impl FromColorUnclamped> for Hsl where T: Real + Zero + One + IsValidDivisor + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect + Not, { fn from_color_unclamped(hsv: Hsv) -> Self { let Hsv { hue, saturation, value, .. } = hsv; let x = (T::from_f64(2.0) - &saturation) * &value; let saturation = lazy_select! { if !value.is_valid_divisor() => T::zero(), if x.lt(&T::one()) => { lazy_select!{ if x.is_valid_divisor() => saturation.clone() * &value / &x, else => T::zero(), } }, else => { let denom = T::from_f64(2.0) - &x; lazy_select! { if denom.is_valid_divisor() => saturation.clone() * &value / denom, else => T::zero(), } }, }; Hsl { hue, saturation, lightness: x / T::from_f64(2.0), standard: PhantomData, } } } impl_tuple_conversion_hue!(Hsl as (H, T, T), RgbHue); impl_is_within_bounds! { Hsl { saturation => [Self::min_saturation(), Self::max_saturation()], lightness => [Self::min_lightness(), Self::max_lightness()] } where T: Stimulus } impl_clamp! { Hsl { saturation => [Self::min_saturation(), Self::max_saturation()], lightness => [Self::min_lightness(), Self::max_lightness()] } other {hue, standard} where T: Stimulus } impl_mix_hue!(Hsl {saturation, lightness} phantom: standard); impl_lighten!(Hsl increase {lightness => [Self::min_lightness(), Self::max_lightness()]} other {hue, saturation} phantom: standard where T: Stimulus); impl_saturate!(Hsl increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, lightness} phantom: standard where T: Stimulus); impl_hue_ops!(Hsl, RgbHue); impl HasBoolMask for Hsl where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Hsl where T: Stimulus, RgbHue: Default, { fn default() -> Hsl { Hsl::new( RgbHue::default(), Self::min_saturation(), Self::min_lightness(), ) } } impl_color_add!(Hsl, [hue, saturation, lightness], standard); impl_color_sub!(Hsl, [hue, saturation, lightness], standard); impl_array_casts!(Hsl, [T; 3]); impl_simd_array_conversion_hue!(Hsl, [saturation, lightness], standard); impl_struct_of_array_traits_hue!(Hsl, RgbHueIter, [saturation, lightness], standard); impl_eq_hue!(Hsl, RgbHue, [hue, saturation, lightness]); impl_copy_clone!(Hsl, [hue, saturation, lightness], standard); #[allow(deprecated)] impl crate::RelativeContrast for Hsl where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, S: RgbStandard, Xyz<::WhitePoint, T>: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_hsl_bicone!( UniformHsl, Hsl { hue: UniformRgbHue => RgbHue, height: lightness, radius: saturation } phantom: standard: PhantomData ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Hsl where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Hsl where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Hsl; test_convert_into_from_xyz!(Hsl); #[cfg(feature = "approx")] mod conversion { use crate::{FromColor, Hsl, Hsv, Srgb}; #[test] fn red() { let a = Hsl::from_color(Srgb::new(1.0, 0.0, 0.0)); let b = Hsl::new_srgb(0.0, 1.0, 0.5); let c = Hsl::from_color(Hsv::new_srgb(0.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn orange() { let a = Hsl::from_color(Srgb::new(1.0, 0.5, 0.0)); let b = Hsl::new_srgb(30.0, 1.0, 0.5); let c = Hsl::from_color(Hsv::new_srgb(30.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn green() { let a = Hsl::from_color(Srgb::new(0.0, 1.0, 0.0)); let b = Hsl::new_srgb(120.0, 1.0, 0.5); let c = Hsl::from_color(Hsv::new_srgb(120.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn blue() { let a = Hsl::from_color(Srgb::new(0.0, 0.0, 1.0)); let b = Hsl::new_srgb(240.0, 1.0, 0.5); let c = Hsl::from_color(Hsv::new_srgb(240.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn purple() { let a = Hsl::from_color(Srgb::new(0.5, 0.0, 1.0)); let b = Hsl::new_srgb(270.0, 1.0, 0.5); let c = Hsl::from_color(Hsv::new_srgb(270.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } } #[test] fn ranges() { assert_ranges! { Hsl; clamped { saturation: 0.0 => 1.0, lightness: 0.0 => 1.0 } clamped_min {} unclamped { hue: -360.0 => 360.0 } } } raw_pixel_conversion_tests!(Hsl: hue, saturation, lightness); raw_pixel_conversion_fail_tests!(Hsl: hue, saturation, lightness); #[test] fn check_min_max_components() { use crate::encoding::Srgb; assert_eq!(Hsl::::min_saturation(), 0.0); assert_eq!(Hsl::::min_lightness(), 0.0); assert_eq!(Hsl::::max_saturation(), 1.0); assert_eq!(Hsl::::max_lightness(), 1.0); } struct_of_arrays_tests!( Hsl[hue, saturation, lightness] phantom: standard, super::Hsla::new(0.1f32, 0.2, 0.3, 0.4), super::Hsla::new(0.2, 0.3, 0.4, 0.5), super::Hsla::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Hsl::new_srgb(0.3, 0.8, 0.1)).unwrap(); assert_eq!( serialized, r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"# ); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Hsl = ::serde_json::from_str(r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"#).unwrap(); assert_eq!(deserialized, Hsl::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Hsl as crate::rgb::Rgb { red: (0.0, 1.0), green: (0.0, 1.0), blue: (0.0, 1.0) }, min: Hsl::new(0.0f32, 0.0, 0.0), max: Hsl::new(360.0, 1.0, 1.0) } /// Sanity check to make sure the test doesn't start accepting known /// non-uniform distributions. #[cfg(feature = "random")] #[test] #[should_panic(expected = "is not uniform enough")] fn uniform_distribution_fail() { use rand::Rng; const BINS: usize = crate::random_sampling::test_utils::BINS; const SAMPLES: usize = crate::random_sampling::test_utils::SAMPLES; let mut red = [0; BINS]; let mut green = [0; BINS]; let mut blue = [0; BINS]; let mut rng = rand_mt::Mt::new(1234); // We want the same seed on every run to avoid random fails for _ in 0..SAMPLES { let color = Hsl::::new( rng.gen::() * 360.0, rng.gen(), rng.gen(), ); let color: crate::rgb::Rgb = crate::IntoColor::into_color(color); red[((color.red * BINS as f32) as usize).min(9)] += 1; green[((color.green * BINS as f32) as usize).min(9)] += 1; blue[((color.blue * BINS as f32) as usize).min(9)] += 1; } assert_uniform_distribution!(red); assert_uniform_distribution!(green); assert_uniform_distribution!(blue); } } palette-0.7.5/src/hsluv.rs000064400000000000000000000263211046102023000135750ustar 00000000000000//! Types for the HSLuv color space. use core::marker::PhantomData; use crate::{ angle::RealAngle, bool_mask::{HasBoolMask, LazySelect}, convert::FromColorUnclamped, hues::LuvHueIter, luv_bounds::LuvBounds, num::{Arithmetics, PartialCmp, Powi, Real, Zero}, white_point::D65, Alpha, FromColor, Lchuv, LuvHue, Xyz, }; /// HSLuv with an alpha component. See the [`Hsluva` implementation in /// `Alpha`](crate::Alpha#Hsluva). pub type Hsluva = Alpha, T>; /// HSLuv color space. /// /// The HSLuv color space can be seen as a cylindrical version of /// [CIELUV](crate::luv::Luv), similar to /// [LCHuv](crate::lchuv::Lchuv), with the additional benefit of /// stretching the chroma values to a uniform saturation range [0.0, /// 100.0]. This makes HSLuv much more convenient for generating /// colors than Lchuv, as the set of valid saturation values is /// independent of lightness and hue. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "Wp", component = "T", skip_derives(Lchuv, Hsluv) )] #[repr(C)] pub struct Hsluv { /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. #[palette(unsafe_same_layout_as = "T")] pub hue: LuvHue, /// The colorfulness of the color, as a percentage of the maximum /// available chroma. 0.0 gives gray scale colors and 100.0 will /// give absolutely clear colors. pub saturation: T, /// Decides how light the color will look. 0.0 will be black, 50.0 will give /// a clear color, and 100.0 will give white. pub l: T, /// The white point and RGB primaries this color is adapted to. The default /// is the sRGB standard. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } impl Hsluv { /// Create an HSLuv color. pub fn new>>(hue: H, saturation: T, l: T) -> Self { Self::new_const(hue.into(), saturation, l) } /// Create an HSLuv color. This is the same as `Hsluv::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: LuvHue, saturation: T, l: T) -> Self { Hsluv { hue, saturation, l, white_point: PhantomData, } } /// Convert to a `(hue, saturation, l)` tuple. pub fn into_components(self) -> (LuvHue, T, T) { (self.hue, self.saturation, self.l) } /// Convert from a `(hue, saturation, l)` tuple. pub fn from_components>>((hue, saturation, l): (H, T, T)) -> Self { Self::new(hue, saturation, l) } } impl Hsluv where T: Zero + Real, { /// Return the `saturation` value minimum. pub fn min_saturation() -> T { T::zero() } /// Return the `saturation` value maximum. pub fn max_saturation() -> T { T::from_f64(100.0) } /// Return the `l` value minimum. pub fn min_l() -> T { T::zero() } /// Return the `l` value maximum. pub fn max_l() -> T { T::from_f64(100.0) } } ///[`Hsluva`](crate::Hsluva) implementations. impl Alpha, A> { /// Create an HSLuv color with transparency. pub fn new>>(hue: H, saturation: T, l: T, alpha: A) -> Self { Self::new_const(hue.into(), saturation, l, alpha) } /// Create an HSLuv color with transparency. This is the same as /// `Hsluva::new` without the generic hue type. It's temporary until `const /// fn` supports traits. pub const fn new_const(hue: LuvHue, saturation: T, l: T, alpha: A) -> Self { Alpha { color: Hsluv::new_const(hue, saturation, l), alpha, } } /// Convert to a `(hue, saturation, l, alpha)` tuple. pub fn into_components(self) -> (LuvHue, T, T, A) { ( self.color.hue, self.color.saturation, self.color.l, self.alpha, ) } /// Convert from a `(hue, saturation, l, alpha)` tuple. pub fn from_components>>((hue, saturation, l, alpha): (H, T, T, A)) -> Self { Self::new(hue, saturation, l, alpha) } } impl_reference_component_methods_hue!(Hsluv, [saturation, l], white_point); impl_struct_of_arrays_methods_hue!(Hsluv, [saturation, l], white_point); impl FromColorUnclamped> for Hsluv { fn from_color_unclamped(hsluv: Hsluv) -> Self { hsluv } } impl FromColorUnclamped> for Hsluv where T: Real + RealAngle + Into + Powi + Arithmetics + Clone, { fn from_color_unclamped(color: Lchuv) -> Self { // convert the chroma to a saturation based on the max // saturation at a particular hue. let max_chroma = LuvBounds::from_lightness(color.l.clone()).max_chroma_at_hue(color.hue.clone()); Hsluv::new( color.hue, color.chroma / max_chroma * T::from_f64(100.0), color.l, ) } } impl_tuple_conversion_hue!(Hsluv as (H, T, T), LuvHue); impl_is_within_bounds! { Hsluv { saturation => [Self::min_saturation(), Self::max_saturation()], l => [Self::min_l(), Self::max_l()] } where T: Real + Zero } impl_clamp! { Hsluv { saturation => [Self::min_saturation(), Self::max_saturation()], l => [Self::min_l(), Self::max_l()] } other {hue, white_point} where T: Real + Zero } impl_mix_hue!(Hsluv {saturation, l} phantom: white_point); impl_lighten!(Hsluv increase {l => [Self::min_l(), Self::max_l()]} other {hue, saturation} phantom: white_point); impl_saturate!(Hsluv increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, l} phantom: white_point); impl_hue_ops!(Hsluv, LuvHue); impl HasBoolMask for Hsluv where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Hsluv where T: Real + Zero, LuvHue: Default, { fn default() -> Hsluv { Hsluv::new(LuvHue::default(), Self::min_saturation(), Self::min_l()) } } impl_color_add!(Hsluv, [hue, saturation, l], white_point); impl_color_sub!(Hsluv, [hue, saturation, l], white_point); impl_array_casts!(Hsluv, [T; 3]); impl_simd_array_conversion_hue!(Hsluv, [saturation, l], white_point); impl_struct_of_array_traits_hue!(Hsluv, LuvHueIter, [saturation, l], white_point); impl_eq_hue!(Hsluv, LuvHue, [hue, saturation, l]); impl_copy_clone!(Hsluv, [hue, saturation, l], white_point); #[allow(deprecated)] impl crate::RelativeContrast for Hsluv where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_hsl_bicone!( UniformHsluv, Hsluv { hue: UniformLuvHue => LuvHue, height: l => [|l: T| l * T::from_f64(100.0), |l: T| l / T::from_f64(100.0)], radius: saturation => [|s: T| s * T::from_f64(100.0), |s: T| s / T::from_f64(100.0)] } phantom: white_point: PhantomData ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Hsluv where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Hsluv where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Hsluv; use crate::white_point::D65; test_convert_into_from_xyz!(Hsluv); #[cfg(feature = "approx")] #[cfg_attr(miri, ignore)] #[test] fn lchuv_round_trip() { use crate::{FromColor, Lchuv, LuvHue}; for hue in (0..=20).map(|x| x as f64 * 18.0) { for sat in (0..=20).map(|x| x as f64 * 5.0) { for l in (1..=20).map(|x| x as f64 * 5.0) { let hsluv = Hsluv::::new(hue, sat, l); let lchuv = Lchuv::from_color(hsluv); let mut to_hsluv = Hsluv::from_color(lchuv); if to_hsluv.l < 1e-8 { to_hsluv.hue = LuvHue::from(0.0); } assert_relative_eq!(hsluv, to_hsluv, epsilon = 1e-5); } } } } #[test] fn ranges() { assert_ranges! { Hsluv; clamped { saturation: 0.0 => 100.0, l: 0.0 => 100.0 } clamped_min {} unclamped { hue: -360.0 => 360.0 } } } /// Check that the arithmetic operations (add/sub) are all /// implemented. #[test] fn test_arithmetic() { let hsl = Hsluv::::new(120.0, 40.0, 30.0); let hsl2 = Hsluv::new(200.0, 30.0, 40.0); let mut _hsl3 = hsl + hsl2; _hsl3 += hsl2; let mut _hsl4 = hsl2 + 0.3; _hsl4 += 0.1; _hsl3 = hsl2 - hsl; _hsl3 = _hsl4 - 0.1; _hsl4 -= _hsl3; _hsl3 -= 0.1; } #[cfg(feature = "approx")] #[test] fn saturate() { use crate::Saturate; for sat in (0..=10).map(|s| s as f64 * 10.0) { for a in (0..=10).map(|l| l as f64 * 10.0) { let hsl = Hsluv::::new(150.0, sat, a); let hsl_sat_fixed = hsl.saturate_fixed(0.1); let expected_sat_fixed = Hsluv::new(150.0, (sat + 10.0).min(100.0), a); assert_relative_eq!(hsl_sat_fixed, expected_sat_fixed); let hsl_sat = hsl.saturate(0.1); let expected_sat = Hsluv::new(150.0, (sat + (100.0 - sat) * 0.1).min(100.0), a); assert_relative_eq!(hsl_sat, expected_sat); } } } raw_pixel_conversion_tests!(Hsluv: hue, saturation, lightness); raw_pixel_conversion_fail_tests!(Hsluv: hue, saturation, lightness); #[test] fn check_min_max_components() { assert_eq!(Hsluv::::min_saturation(), 0.0); assert_eq!(Hsluv::::min_l(), 0.0); assert_eq!(Hsluv::::max_saturation(), 100.0); assert_eq!(Hsluv::::max_l(), 100.0); } struct_of_arrays_tests!( Hsluv[hue, saturation, l] phantom: white_point, super::Hsluva::new(0.1f32, 0.2, 0.3, 0.4), super::Hsluva::new(0.2, 0.3, 0.4, 0.5), super::Hsluva::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Hsluv::::new(120.0, 80.0, 60.0)).unwrap(); assert_eq!(serialized, r#"{"hue":120.0,"saturation":80.0,"l":60.0}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Hsluv = ::serde_json::from_str(r#"{"hue":120.0,"saturation":80.0,"l":60.0}"#).unwrap(); assert_eq!(deserialized, Hsluv::new(120.0, 80.0, 60.0)); } } palette-0.7.5/src/hsv.rs000064400000000000000000000513511046102023000132350ustar 00000000000000//! Types for the HSV color space. use core::{any::TypeId, marker::PhantomData}; use crate::{ angle::{FromAngle, RealAngle}, bool_mask::{BitOps, BoolMask, HasBoolMask, LazySelect, Select}, convert::FromColorUnclamped, encoding::Srgb, hues::RgbHueIter, num::{Arithmetics, IsValidDivisor, MinMax, One, PartialCmp, Real, Zero}, rgb::{Rgb, RgbSpace, RgbStandard}, stimulus::{FromStimulus, Stimulus}, Alpha, FromColor, Hsl, Hwb, RgbHue, Xyz, }; /// Linear HSV with an alpha component. See the [`Hsva` implementation in /// `Alpha`](crate::Alpha#Hsva). pub type Hsva = Alpha, T>; /// HSV color space. /// /// HSV is a cylindrical version of [RGB](crate::rgb::Rgb) and it's very similar /// to [HSL](crate::Hsl). The difference is that the `value` component in HSV /// determines the _brightness_ of the color, and not the _lightness_. The /// difference is that, for example, red (100% R, 0% G, 0% B) and white (100% R, /// 100% G, 100% B) has the same brightness (or value), but not the same /// lightness. /// /// HSV component values are typically real numbers (such as floats), but may /// also be converted to and from `u8` for storage and interoperability /// purposes. The hue is then within the range `[0, 255]`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::Hsv; /// /// let hsv_u8 = Hsv::new_srgb(128u8, 85, 51); /// let hsv_f32 = hsv_u8.into_format::(); /// /// assert_relative_eq!(hsv_f32, Hsv::new(180.0, 1.0 / 3.0, 0.2)); /// ``` #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, rgb_standard = "S", component = "T", skip_derives(Rgb, Hsl, Hwb, Hsv) )] #[repr(C)] #[doc(alias = "hsb")] pub struct Hsv { /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. #[palette(unsafe_same_layout_as = "T")] pub hue: RgbHue, /// The colorfulness of the color. 0.0 gives gray scale colors and 1.0 will /// give absolutely clear colors. pub saturation: T, /// Decides how bright the color will look. 0.0 will be black, and 1.0 will /// give a bright an clear color that goes towards white when `saturation` /// goes towards 0.0. pub value: T, /// The white point and RGB primaries this color is adapted to. The default /// is the sRGB standard. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub standard: PhantomData, } impl Hsv { /// Create an sRGB HSV color. This method can be used instead of `Hsv::new` /// to help type inference. pub fn new_srgb>>(hue: H, saturation: T, value: T) -> Self { Self::new_const(hue.into(), saturation, value) } /// Create an sRGB HSV color. This is the same as `Hsv::new_srgb` without /// the generic hue type. It's temporary until `const fn` supports traits. pub const fn new_srgb_const(hue: RgbHue, saturation: T, value: T) -> Self { Self::new_const(hue, saturation, value) } } impl Hsv { /// Create an HSV color. pub fn new>>(hue: H, saturation: T, value: T) -> Self { Self::new_const(hue.into(), saturation, value) } /// Create an HSV color. This is the same as `Hsv::new` without the generic /// hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: RgbHue, saturation: T, value: T) -> Self { Hsv { hue, saturation, value, standard: PhantomData, } } /// Convert into another component type. pub fn into_format(self) -> Hsv where U: FromStimulus + FromAngle, { Hsv { hue: self.hue.into_format(), saturation: U::from_stimulus(self.saturation), value: U::from_stimulus(self.value), standard: PhantomData, } } /// Convert from another component type. pub fn from_format(color: Hsv) -> Self where T: FromStimulus + FromAngle, { color.into_format() } /// Convert to a `(hue, saturation, value)` tuple. pub fn into_components(self) -> (RgbHue, T, T) { (self.hue, self.saturation, self.value) } /// Convert from a `(hue, saturation, value)` tuple. pub fn from_components>>((hue, saturation, value): (H, T, T)) -> Self { Self::new(hue, saturation, value) } #[inline] fn reinterpret_as(self) -> Hsv { Hsv { hue: self.hue, saturation: self.saturation, value: self.value, standard: PhantomData, } } } impl Hsv where T: Stimulus, { /// Return the `saturation` value minimum. pub fn min_saturation() -> T { T::zero() } /// Return the `saturation` value maximum. pub fn max_saturation() -> T { T::max_intensity() } /// Return the `value` value minimum. pub fn min_value() -> T { T::zero() } /// Return the `value` value maximum. pub fn max_value() -> T { T::max_intensity() } } ///[`Hsva`](crate::Hsva) implementations. impl Alpha, A> { /// Create an sRGB HSV color with transparency. This method can be used /// instead of `Hsva::new` to help type inference. pub fn new_srgb>>(hue: H, saturation: T, value: T, alpha: A) -> Self { Self::new_const(hue.into(), saturation, value, alpha) } /// Create an sRGB HSV color with transparency. This is the same as /// `Hsva::new_srgb` without the generic hue type. It's temporary until /// `const fn` supports traits. pub const fn new_srgb_const(hue: RgbHue, saturation: T, value: T, alpha: A) -> Self { Self::new_const(hue, saturation, value, alpha) } } ///[`Hsva`](crate::Hsva) implementations. impl Alpha, A> { /// Create an HSV color with transparency. pub fn new>>(hue: H, saturation: T, value: T, alpha: A) -> Self { Self::new_const(hue.into(), saturation, value, alpha) } /// Create an HSV color with transparency. This is the same as `Hsva::new` /// without the generic hue type. It's temporary until `const fn` supports /// traits. pub const fn new_const(hue: RgbHue, saturation: T, value: T, alpha: A) -> Self { Alpha { color: Hsv::new_const(hue, saturation, value), alpha, } } /// Convert into another component type. pub fn into_format(self) -> Alpha, B> where U: FromStimulus + FromAngle, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert from another component type. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus + FromAngle, A: FromStimulus, { color.into_format() } /// Convert to a `(hue, saturation, value, alpha)` tuple. pub fn into_components(self) -> (RgbHue, T, T, A) { ( self.color.hue, self.color.saturation, self.color.value, self.alpha, ) } /// Convert from a `(hue, saturation, value, alpha)` tuple. pub fn from_components>>( (hue, saturation, value, alpha): (H, T, T, A), ) -> Self { Self::new(hue, saturation, value, alpha) } } impl_reference_component_methods_hue!(Hsv, [saturation, value], standard); impl_struct_of_arrays_methods_hue!(Hsv, [saturation, value], standard); impl FromColorUnclamped> for Hsv where S1: RgbStandard + 'static, S2: RgbStandard + 'static, S1::Space: RgbSpace::WhitePoint>, Rgb: FromColorUnclamped>, Rgb: FromColorUnclamped>, Self: FromColorUnclamped>, { #[inline] fn from_color_unclamped(hsv: Hsv) -> Self { if TypeId::of::() == TypeId::of::() { hsv.reinterpret_as() } else { let rgb = Rgb::::from_color_unclamped(hsv); let converted_rgb = Rgb::::from_color_unclamped(rgb); Self::from_color_unclamped(converted_rgb) } } } impl FromColorUnclamped> for Hsv where T: RealAngle + One + Zero + MinMax + Arithmetics + PartialCmp + Clone, T::Mask: BoolMask + BitOps + LazySelect + Clone + 'static, { fn from_color_unclamped(rgb: Rgb) -> Self { // Avoid negative numbers let red = rgb.red.max(T::zero()); let green = rgb.green.max(T::zero()); let blue = rgb.blue.max(T::zero()); // The SIMD optimized version showed significant slowdown for regular floats. if TypeId::of::() == TypeId::of::() { let (max, min, sep, coeff) = { let (max, min, sep, coeff) = if red.gt(&green).is_true() { (red.clone(), green.clone(), green.clone() - &blue, T::zero()) } else { ( green.clone(), red.clone(), blue.clone() - &red, T::from_f64(2.0), ) }; if blue.gt(&max).is_true() { (blue, min, red - green, T::from_f64(4.0)) } else { let min_val = if blue.lt(&min).is_true() { blue } else { min }; (max, min_val, sep, coeff) } }; let (h, s) = if max.neq(&min).is_true() { let d = max.clone() - min; let h = ((sep / &d) + coeff) * T::from_f64(60.0); let s = d / &max; (h, s) } else { (T::zero(), T::zero()) }; let v = max; Hsv { hue: h.into(), saturation: s, value: v, standard: PhantomData, } } else { // Based on OPTIMIZED RGB TO HSV COLOR CONVERSION USING SSE TECHNOLOGY // by KOBALICEK, Petr & BLIZNAK, Michal // // This implementation assumes less about the underlying mask and number // representation. The hue is also multiplied by 6 to avoid rounding // errors when using degrees. let six = T::from_f64(6.0); let value = red.clone().max(green.clone()).max(blue.clone()); let min = red.clone().min(green.clone()).min(blue.clone()); let chroma = value.clone() - min; let saturation = chroma .eq(&T::zero()) .lazy_select(|| T::zero(), || chroma.clone() / &value); // Each of these represents an RGB component. The maximum will be false // while the two other will be true. They are later used for determining // which branch in the hue equation we end up in. let x = value.neq(&red); let y = value.eq(&red) | value.neq(&green); let z = value.eq(&red) | value.eq(&green); // The hue base is the `1`, `2/6`, `4/6` or 0 part of the hue equation, // except it's multiplied by 6 here. let hue_base = x.clone().select( z.clone().select(T::from_f64(-4.0), T::from_f64(4.0)), T::zero(), ) + &six; // Each of these is a part of `G - B`, `B - R`, `R - G` or 0 from the // hue equation. They become positive, negative or 0, depending on which // branch we should be in. This makes the sum of all three combine as // expected. let red_m = lazy_select! { if x => y.clone().select(red.clone(), -red), else => T::zero(), }; let green_m = lazy_select! { if y.clone() => z.clone().select(green.clone(), -green), else => T::zero(), }; let blue_m = lazy_select! { if z => y.select(-blue.clone(), blue), else => T::zero(), }; // This is the hue equation parts combined. The hue base is the constant // and the RGB components are masked so up to two of them are non-zero. // Once again, this is multiplied by 6, so the chroma isn't multiplied // before dividing. // // We also avoid dividing by 0 for non-SIMD values. let hue = lazy_select! { if chroma.eq(&T::zero()) => T::zero(), else => hue_base + (red_m + green_m + blue_m) / &chroma, }; // hue will always be within [0, 12) (it's multiplied by 6, compared to // the paper), so we can subtract by 6 instead of using % to get it // within [0, 6). let hue_sub = hue.gt_eq(&six).select(six, T::zero()); let hue = hue - hue_sub; Hsv { hue: RgbHue::from_degrees(hue * T::from_f64(60.0)), saturation, value, standard: PhantomData, } } } } impl FromColorUnclamped> for Hsv where T: Real + Zero + One + IsValidDivisor + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { #[inline] fn from_color_unclamped(hsl: Hsl) -> Self { let x = lazy_select! { if hsl.lightness.lt(&T::from_f64(0.5)) => hsl.lightness.clone(), else => T::one() - &hsl.lightness, } * hsl.saturation; let value = hsl.lightness + &x; // avoid divide by zero let saturation = lazy_select! { if value.is_valid_divisor() => x * T::from_f64(2.0) / &value, else => T::zero(), }; Hsv { hue: hsl.hue, saturation, value, standard: PhantomData, } } } impl FromColorUnclamped> for Hsv where T: One + Zero + IsValidDivisor + Arithmetics, T::Mask: LazySelect, { #[inline] fn from_color_unclamped(hwb: Hwb) -> Self { let Hwb { hue, whiteness, blackness, .. } = hwb; let value = T::one() - blackness; // avoid divide by zero let saturation = lazy_select! { if value.is_valid_divisor() => T::one() - (whiteness / &value), else => T::zero(), }; Hsv { hue, saturation, value, standard: PhantomData, } } } impl_tuple_conversion_hue!(Hsv as (H, T, T), RgbHue); impl_is_within_bounds! { Hsv { saturation => [Self::min_saturation(), Self::max_saturation()], value => [Self::min_value(), Self::max_value()] } where T: Stimulus } impl_clamp! { Hsv { saturation => [Self::min_saturation(), Self::max_saturation()], value => [Self::min_value(), Self::max_value()] } other {hue, standard} where T: Stimulus } impl_mix_hue!(Hsv {saturation, value} phantom: standard); impl_lighten!(Hsv increase {value => [Self::min_value(), Self::max_value()]} other {hue, saturation} phantom: standard where T: Stimulus); impl_saturate!(Hsv increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, value} phantom: standard where T: Stimulus); impl_hue_ops!(Hsv, RgbHue); impl HasBoolMask for Hsv where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Hsv where T: Stimulus, RgbHue: Default, { fn default() -> Hsv { Hsv::new(RgbHue::default(), Self::min_saturation(), Self::min_value()) } } impl_color_add!(Hsv, [hue, saturation, value], standard); impl_color_sub!(Hsv, [hue, saturation, value], standard); impl_array_casts!(Hsv, [T; 3]); impl_simd_array_conversion_hue!(Hsv, [saturation, value], standard); impl_struct_of_array_traits_hue!(Hsv, RgbHueIter, [saturation, value], standard); impl_eq_hue!(Hsv, RgbHue, [hue, saturation, value]); impl_copy_clone!(Hsv, [hue, saturation, value], standard); #[allow(deprecated)] impl crate::RelativeContrast for Hsv where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, S: RgbStandard, Xyz<::WhitePoint, T>: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_hsv_cone!( UniformHsv, Hsv { hue: UniformRgbHue => RgbHue, height: value, radius: saturation } phantom: standard: PhantomData ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Hsv where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Hsv where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Hsv; test_convert_into_from_xyz!(Hsv); #[cfg(feature = "approx")] mod conversion { use crate::{FromColor, Hsl, Hsv, Srgb}; #[test] fn red() { let a = Hsv::from_color(Srgb::new(1.0, 0.0, 0.0)); let b = Hsv::new_srgb(0.0, 1.0, 1.0); let c = Hsv::from_color(Hsl::new_srgb(0.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn orange() { let a = Hsv::from_color(Srgb::new(1.0, 0.5, 0.0)); let b = Hsv::new_srgb(30.0, 1.0, 1.0); let c = Hsv::from_color(Hsl::new_srgb(30.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn green() { let a = Hsv::from_color(Srgb::new(0.0, 1.0, 0.0)); let b = Hsv::new_srgb(120.0, 1.0, 1.0); let c = Hsv::from_color(Hsl::new_srgb(120.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn blue() { let a = Hsv::from_color(Srgb::new(0.0, 0.0, 1.0)); let b = Hsv::new_srgb(240.0, 1.0, 1.0); let c = Hsv::from_color(Hsl::new_srgb(240.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } #[test] fn purple() { let a = Hsv::from_color(Srgb::new(0.5, 0.0, 1.0)); let b = Hsv::new_srgb(270.0, 1.0, 1.0); let c = Hsv::from_color(Hsl::new_srgb(270.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); } } #[test] fn ranges() { assert_ranges! { Hsv; clamped { saturation: 0.0 => 1.0, value: 0.0 => 1.0 } clamped_min {} unclamped { hue: -360.0 => 360.0 } } } raw_pixel_conversion_tests!(Hsv: hue, saturation, value); raw_pixel_conversion_fail_tests!(Hsv: hue, saturation, value); #[test] fn check_min_max_components() { use crate::encoding::Srgb; assert_eq!(Hsv::::min_saturation(), 0.0,); assert_eq!(Hsv::::min_value(), 0.0,); assert_eq!(Hsv::::max_saturation(), 1.0,); assert_eq!(Hsv::::max_value(), 1.0,); } struct_of_arrays_tests!( Hsv[hue, saturation, value] phantom: standard, super::Hsva::new(0.1f32, 0.2, 0.3, 0.4), super::Hsva::new(0.2, 0.3, 0.4, 0.5), super::Hsva::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Hsv::new_srgb(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"hue":0.3,"saturation":0.8,"value":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Hsv = ::serde_json::from_str(r#"{"hue":0.3,"saturation":0.8,"value":0.1}"#).unwrap(); assert_eq!(deserialized, Hsv::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Hsv as crate::rgb::Rgb { red: (0.0, 1.0), green: (0.0, 1.0), blue: (0.0, 1.0) }, min: Hsv::new(0.0f32, 0.0, 0.0), max: Hsv::new(360.0, 1.0, 1.0) } } palette-0.7.5/src/hues.rs000064400000000000000000001005511046102023000133760ustar 00000000000000//! Hues and hue related types. #[cfg(any(feature = "approx", feature = "random"))] use core::ops::Mul; use core::ops::{Add, AddAssign, Neg, Sub, SubAssign}; #[cfg(feature = "approx")] use approx::{AbsDiffEq, RelativeEq, UlpsEq}; #[cfg(feature = "random")] use rand::{ distributions::{ uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}, Distribution, Standard, }, Rng, }; #[cfg(feature = "approx")] use crate::{angle::HalfRotation, num::Zero}; #[cfg(feature = "random")] use crate::angle::FullRotation; use crate::{ angle::{AngleEq, FromAngle, RealAngle, SignedAngle, UnsignedAngle}, num::Trigonometry, }; macro_rules! make_hues { ($($(#[$doc:meta])+ struct $name:ident; $iter_name:ident)+) => ($( $(#[$doc])+ /// /// The hue is a circular type, where `0` and `360` is the same, and /// it's normalized to `(-180, 180]` when it's converted to a linear /// number (like `f32`). This makes many calculations easier, but may /// also have some surprising effects if it's expected to act as a /// linear number. #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[repr(C)] pub struct $name(T); impl $name { /// Create a new hue, specified in the default unit for the angle /// type `T`. /// /// `f32`, `f64` and other real number types represent degrees, /// while `u8` simply represents the range `[0, 360]` as `[0, 256]`. #[inline] pub const fn new(angle: T) -> Self { Self(angle) } /// Get the internal representation without normalizing or converting it. /// /// `f32`, `f64` and other real number types represent degrees, /// while `u8` simply represents the range `[0, 360]` as `[0, 256]`. pub fn into_inner(self) -> T { self.0 } /// Convert into another angle type. pub fn into_format(self) -> $name where U: FromAngle, { $name(U::from_angle(self.0)) } /// Convert from another angle type. pub fn from_format(hue: $name) -> Self where T: FromAngle, { hue.into_format() } } impl $name { /// Create a new hue from degrees. This is an alias for `new`. #[inline] pub fn from_degrees(degrees: T) -> Self { Self::new(degrees) } /// Create a new hue from radians, instead of degrees. #[inline] pub fn from_radians(radians: T) -> Self { Self(T::radians_to_degrees(radians)) } /// Get the internal representation as degrees, without normalizing it. #[inline] pub fn into_raw_degrees(self) -> T { self.0 } /// Get the internal representation as radians, without normalizing it. #[inline] pub fn into_raw_radians(self) -> T { T::degrees_to_radians(self.0) } } impl $name { /// Get the hue as degrees, in the range `(-180, 180]`. #[inline] pub fn into_degrees(self) -> T { self.0.normalize_signed_angle() } /// Convert the hue to radians, in the range `(-π, π]`. #[inline] pub fn into_radians(self) -> T { T::degrees_to_radians(self.0.normalize_signed_angle()) } } impl $name { /// Convert the hue to positive degrees, in the range `[0, 360)`. #[inline] pub fn into_positive_degrees(self) -> T { self.0.normalize_unsigned_angle() } /// Convert the hue to positive radians, in the range `[0, 2π)`. #[inline] pub fn into_positive_radians(self) -> T { T::degrees_to_radians(self.0.normalize_unsigned_angle()) } } impl $name { /// Returns a hue from `a` and `b`, normalized to `[0°, 360°)`. /// /// If `a` and `b` are both `0`, returns `0`, #[inline(always)] pub fn from_cartesian(a: T, b: T) -> Self where T: Add + Neg { // atan2 returns values in the interval [-π, π] // instead of // let hue_rad = T::atan2(b,a); // use negative a and be and rotate, to ensure the hue is normalized, let hue_rad = T::from_f64(core::f64::consts::PI) + T::atan2(-b, -a); Self::from_radians(hue_rad) } /// Returns `a` and `b` values for this hue, normalized to `[-1, /// 1]`. /// /// They will have to be multiplied by a radius values, such as /// saturation, value, chroma, etc., to represent a specific color. #[inline(always)] pub fn into_cartesian(self) -> (T, T) { let (b, a) = self.into_raw_radians().sin_cos(); (a, b) // Note the swapped order compared to above } } impl $name<&T> { /// Get an owned, copied version of this hue. #[inline(always)] pub fn copied(&self) -> $name where T: Copy, { $name(*self.0) } /// Get an owned, cloned version of this hue. #[inline(always)] pub fn cloned(&self) -> $name where T: Clone, { $name(self.0.clone()) } } impl $name<&mut T> { /// Update this hue with a new value. #[inline(always)] pub fn set(&mut self, value: $name) { *self.0 = value.0; } /// Borrow this hue's value as shared references. #[inline(always)] pub fn as_ref(&self) -> $name<&T> { $name(&*self.0) } /// Get an owned, copied version of this hue. #[inline(always)] pub fn copied(&self) -> $name where T: Copy, { $name(*self.0) } /// Get an owned, cloned version of this hue. #[inline(always)] pub fn cloned(&self) -> $name where T: Clone, { $name(self.0.clone()) } } impl $name { /// Return an iterator over the hues in the wrapped collection. #[inline(always)] pub fn iter<'a>(&'a self) -> <&'a Self as IntoIterator>::IntoIter where &'a Self: IntoIterator { self.into_iter() } /// Return an iterator that allows modifying the hues in the wrapped collection. #[inline(always)] pub fn iter_mut<'a>(&'a mut self) -> <&'a mut Self as IntoIterator>::IntoIter where &'a mut Self: IntoIterator { self.into_iter() } /// Get a hue, or slice of hues, with references to the values at `index`. See [`slice::get`] for details. #[inline(always)] pub fn get<'a, I, T>(&'a self, index: I) -> Option<$name<&>::Output>> where T: 'a, C: AsRef<[T]>, I: core::slice::SliceIndex<[T]> + Clone, { self.0.as_ref().get(index).map($name) } /// Get a hue, or slice of hues, that allows modifying the values at `index`. See [`slice::get_mut`] for details. #[inline(always)] pub fn get_mut<'a, I, T>(&'a mut self, index: I) -> Option<$name<&mut >::Output>> where T: 'a, C: AsMut<[T]>, I: core::slice::SliceIndex<[T]> + Clone, { self.0.as_mut().get_mut(index).map($name) } } #[cfg(feature = "alloc")] impl $name> { /// Create a struct with a vector with a minimum capacity. See [`Vec::with_capacity`] for details. pub fn with_capacity(capacity: usize) -> Self { Self(alloc::vec::Vec::with_capacity(capacity)) } /// Push an additional hue onto the hue vector. See [`Vec::push`] for details. pub fn push(&mut self, value: $name) { self.0.push(value.0); } /// Pop a hue from the hue vector. See [`Vec::pop`] for details. pub fn pop(&mut self) -> Option<$name> { self.0.pop().map($name) } /// Clear the hue vector. See [`Vec::clear`] for details. pub fn clear(&mut self) { self.0.clear(); } /// Return an iterator that moves hues out of the specified range. pub fn drain(&mut self, range: R) -> $iter_name> where R: core::ops::RangeBounds + Clone, { $iter_name(self.0.drain(range)) } } impl From for $name { #[inline] fn from(degrees: T) -> $name { $name(degrees) } } impl From<$name> for f64 { #[inline] fn from(hue: $name) -> f64 { hue.0.normalize_signed_angle() } } impl From<$name> for f64 { #[inline] fn from(hue: $name) -> f64 { hue.0.normalize_signed_angle() as f64 } } impl From<$name> for f32 { #[inline] fn from(hue: $name) -> f32 { hue.0.normalize_signed_angle() } } impl From<$name> for f32 { #[inline] fn from(hue: $name) -> f32 { hue.0.normalize_signed_angle() as f32 } } impl From<$name> for u8 { #[inline] fn from(hue: $name) -> u8 { hue.0 } } impl PartialEq for $name where T: AngleEq + PartialEq { #[inline] fn eq(&self, other: &$name) -> bool { self.0.angle_eq(&other.0) } } impl PartialEq for $name where T: AngleEq + PartialEq { #[inline] fn eq(&self, other: &T) -> bool { self.0.angle_eq(other) } } impl Eq for $name where T: AngleEq + Eq {} #[cfg(feature = "approx")] impl AbsDiffEq for $name where T: RealAngle + SignedAngle + Zero + AngleEq + Sub + AbsDiffEq + Clone, T::Epsilon: HalfRotation + Mul, { type Epsilon = T::Epsilon; fn default_epsilon() -> Self::Epsilon { // For hues, angles in (normalized) degrees are compared. // Scaling from radians to degrees raises the order of magnitude of the // error by 180/PI. // Scale the default epsilon accordingly for absolute comparisons. // Scaling is not required for relative comparisons (including ulps), as // there the error is scaled to unit size anyway T::default_epsilon() * T::Epsilon::half_rotation() } fn abs_diff_eq(&self, other: &Self, epsilon: T::Epsilon) -> bool { T::abs_diff_eq(&self.clone().into_degrees(), &other.clone().into_degrees(), epsilon) } fn abs_diff_ne(&self, other: &Self, epsilon: T::Epsilon) -> bool { T::abs_diff_ne(&self.clone().into_degrees(), &other.clone().into_degrees(), epsilon) } } #[cfg(feature = "approx")] impl RelativeEq for $name where T: RealAngle + SignedAngle + Zero + AngleEq + Sub + Clone + RelativeEq, T::Epsilon: HalfRotation + Mul, { fn default_max_relative() -> Self::Epsilon { T::default_max_relative() } fn relative_eq( &self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon, ) -> bool { T::relative_eq(&self.clone().into_degrees(), &other.clone().into_degrees(), epsilon, max_relative) } fn relative_ne( &self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon, ) -> bool { T::relative_ne(&self.clone().into_degrees(), &other.clone().into_degrees(), epsilon, max_relative) } } #[cfg(feature = "approx")] impl UlpsEq for $name where T: RealAngle + SignedAngle + Zero + AngleEq + Sub + Clone + UlpsEq, T::Epsilon: HalfRotation + Mul, { fn default_max_ulps() -> u32 { T::default_max_ulps() } fn ulps_eq(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool { T::ulps_eq(&self.clone().into_degrees(), &other.clone().into_degrees(), epsilon, max_ulps) } fn ulps_ne(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { T::ulps_ne(&self.clone().into_degrees(), &other.clone().into_degrees(), epsilon, max_ulps) } } impl> Add<$name> for $name { type Output = $name; #[inline] fn add(self, other: $name) -> $name { $name(self.0 + other.0) } } impl> Add for $name { type Output = $name; #[inline] fn add(self, other: T) -> $name { $name(self.0 + other) } } impl Add<$name> for f32 { type Output = $name; #[inline] fn add(self, other: $name) -> $name { $name(self + other.0) } } impl Add<$name> for f64 { type Output = $name; #[inline] fn add(self, other: $name) -> $name { $name(self + other.0) } } impl AddAssign<$name> for $name { #[inline] fn add_assign(&mut self, other: $name) { self.0 += other.0; } } impl AddAssign for $name { #[inline] fn add_assign(&mut self, other: T) { self.0 += other; } } impl AddAssign<$name> for f32 { #[inline] fn add_assign(&mut self, other: $name) { *self += other.0; } } impl AddAssign<$name> for f64 { #[inline] fn add_assign(&mut self, other: $name){ *self += other.0; } } impl> $crate::num::SaturatingAdd<$name> for $name { type Output = $name; #[inline] fn saturating_add(self, other: $name) -> $name { $name(self.0.saturating_add(other.0)) } } impl> $crate::num::SaturatingAdd for $name { type Output = $name; #[inline] fn saturating_add(self, other: T) -> $name { $name(self.0.saturating_add(other)) } } impl> Sub<$name> for $name { type Output = $name; #[inline] fn sub(self, other: $name) -> $name { $name(self.0 - other.0) } } impl> Sub for $name { type Output = $name; #[inline] fn sub(self, other: T) -> $name { $name(self.0 - other) } } impl Sub<$name> for f32 { type Output = $name; #[inline] fn sub(self, other: $name) -> $name { $name(self - other.0) } } impl Sub<$name> for f64 { type Output = $name; #[inline] fn sub(self, other: $name) -> $name { $name(self - other.0) } } impl SubAssign<$name> for $name { #[inline] fn sub_assign(&mut self, other: $name) { self.0 -= other.0; } } impl SubAssign for $name { #[inline] fn sub_assign(&mut self, other: T) { self.0 -= other; } } impl SubAssign<$name> for f32 { #[inline] fn sub_assign(&mut self, other: $name) { *self -= other.0; } } impl SubAssign<$name> for f64 { #[inline] fn sub_assign(&mut self, other: $name){ *self -= other.0; } } impl> $crate::num::SaturatingSub<$name> for $name { type Output = $name; #[inline] fn saturating_sub(self, other: $name) -> $name { $name(self.0.saturating_sub(other.0)) } } impl> $crate::num::SaturatingSub for $name { type Output = $name; #[inline] fn saturating_sub(self, other: T) -> $name { $name(self.0.saturating_sub(other)) } } impl Extend for $name where C: Extend { #[inline(always)] fn extend>(&mut self, iter: I) { self.0.extend(iter); } } impl IntoIterator for $name<[T; N]> { type Item = $name; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name(IntoIterator::into_iter(self.0)) } } impl<'a, T> IntoIterator for $name<&'a [T]> { type Item = $name<&'a T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name(self.0.into_iter()) } } impl<'a, T> IntoIterator for $name<&'a mut [T]> { type Item = $name<&'a mut T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name(self.0.into_iter()) } } #[cfg(feature = "alloc")] impl IntoIterator for $name> { type Item = $name; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name(self.0.into_iter()) } } impl<'a, T, const N: usize> IntoIterator for &'a $name<[T; N]> { type Item = $name<&'a T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name((&self.0).into_iter()) } } impl<'a, 'b, T> IntoIterator for &'a $name<&'b [T]> { type Item = $name<&'a T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name(self.0.into_iter()) } } impl<'a, 'b, T> IntoIterator for &'a $name<&'b mut [T]> { type Item = $name<&'a T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name((&*self.0).into_iter()) } } #[cfg(feature = "alloc")] impl<'a, T> IntoIterator for &'a $name> { type Item = $name<&'a T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name((&self.0).into_iter()) } } #[cfg(feature = "alloc")] impl<'a, T> IntoIterator for &'a $name> { type Item = $name<&'a T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name((&self.0).into_iter()) } } impl<'a, T, const N: usize> IntoIterator for &'a mut $name<[T; N]> { type Item = $name<&'a mut T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name((&mut self.0).into_iter()) } } impl<'a, 'b, T> IntoIterator for &'a mut $name<&'b mut [T]> { type Item = $name<&'a mut T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name(self.0.into_iter()) } } #[cfg(feature = "alloc")] impl<'a, T> IntoIterator for &'a mut $name> { type Item = $name<&'a mut T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name((&mut self.0).into_iter()) } } #[cfg(feature = "alloc")] impl<'a, T> IntoIterator for &'a mut $name> { type Item = $name<&'a mut T>; type IntoIter = $iter_name>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { $iter_name((&mut *self.0).into_iter()) } } #[doc = concat!("Iterator over [`", stringify!($name), "`] values.")] pub struct $iter_name(I); impl Iterator for $iter_name where I: Iterator, { type Item = $name; #[inline(always)] fn next(&mut self) -> Option { self.0.next().map($name) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } #[inline(always)] fn count(self) -> usize { self.0.count() } } impl DoubleEndedIterator for $iter_name where I: DoubleEndedIterator, { #[inline(always)] fn next_back(&mut self) -> Option { self.0.next_back().map($name) } } impl ExactSizeIterator for $iter_name where I: ExactSizeIterator, { #[inline(always)] fn len(&self) -> usize { self.0.len() } } #[cfg(feature = "random")] impl Distribution<$name> for Standard where T: RealAngle + FullRotation + Mul, Standard: Distribution, { #[inline(always)] fn sample(&self, rng: &mut R) -> $name { $name::from_degrees(rng.gen() * T::full_rotation()) } } #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for $name {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for $name {} )+) } make_hues! { /// A hue type for the CIE L\*a\*b\* family of color spaces. /// /// It's measured in degrees and it's based on the four physiological /// elementary colors _red_, _yellow_, _green_ and _blue_. This makes it /// different from the hue of RGB based color spaces. struct LabHue; LabHueIter /// A hue type for the CIE L\*u\*v\* family of color spaces. struct LuvHue; LuvHueIter /// A hue type for the RGB family of color spaces. /// /// It's measured in degrees and uses the three additive primaries _red_, /// _green_ and _blue_. struct RgbHue; RgbHueIter /// A hue type for the Oklab color space. /// /// It's measured in degrees. struct OklabHue; OklabHueIter } macro_rules! impl_uniform { ( $uni_ty: ident , $base_ty: ident) => { #[doc = concat!("Sample [`", stringify!($base_ty), "`] uniformly.")] #[cfg(feature = "random")] pub struct $uni_ty where T: SampleUniform, { hue: Uniform, } #[cfg(feature = "random")] impl SampleUniform for $base_ty where T: RealAngle + UnsignedAngle + FullRotation + Add + Mul + PartialOrd + Clone + SampleUniform, { type Sampler = $uni_ty; } #[cfg(feature = "random")] impl UniformSampler for $uni_ty where T: RealAngle + UnsignedAngle + FullRotation + Add + Mul + PartialOrd + Clone + SampleUniform, { type X = $base_ty; fn new(low_b: B1, high_b: B2) -> Self where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = low_b.borrow().clone(); let normalized_low = $base_ty::into_positive_degrees(low.clone()); let high = high_b.borrow().clone(); let normalized_high = $base_ty::into_positive_degrees(high.clone()); let normalized_high = if normalized_low >= normalized_high && low.0 < high.0 { normalized_high + T::full_rotation() } else { normalized_high }; $uni_ty { hue: Uniform::new(normalized_low, normalized_high), } } fn new_inclusive(low_b: B1, high_b: B2) -> Self where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = low_b.borrow().clone(); let normalized_low = $base_ty::into_positive_degrees(low.clone()); let high = high_b.borrow().clone(); let normalized_high = $base_ty::into_positive_degrees(high.clone()); let normalized_high = if normalized_low >= normalized_high && low.0 < high.0 { normalized_high + T::full_rotation() } else { normalized_high }; $uni_ty { hue: Uniform::new_inclusive(normalized_low, normalized_high), } } fn sample(&self, rng: &mut R) -> $base_ty { $base_ty::from(self.hue.sample(rng) * T::full_rotation()) } } }; } impl_uniform!(UniformLabHue, LabHue); impl_uniform!(UniformRgbHue, RgbHue); impl_uniform!(UniformLuvHue, LuvHue); impl_uniform!(UniformOklabHue, OklabHue); #[cfg(test)] mod test { #[cfg(feature = "approx")] mod math { use crate::{ angle::{SignedAngle, UnsignedAngle}, OklabHue, RgbHue, }; #[test] fn oklabhue_ab_roundtrip() { for degree in [0.0_f64, 90.0, 30.0, 330.0, 120.0, 240.0] { let hue = OklabHue::from_degrees(degree); let (a, b) = hue.into_cartesian(); let roundtrip_hue = OklabHue::from_cartesian(a * 10000.0, b * 10000.0); assert_abs_diff_eq!(roundtrip_hue, hue); } } #[test] fn normalize_angle_0_360() { let inp = [ -1000.0_f32, -900.0, -360.5, -360.0, -359.5, -240.0, -180.5, -180.0, -179.5, -90.0, -0.5, 0.0, 0.5, 90.0, 179.5, 180.0, 180.5, 240.0, 359.5, 360.0, 360.5, 900.0, 1000.0, ]; let expected = [ 80.0_f32, 180.0, 359.5, 0.0, 0.5, 120.0, 179.5, 180.0, 180.5, 270.0, 359.5, 0.0, 0.5, 90.0, 179.5, 180.0, 180.5, 240.0, 359.5, 0.0, 0.5, 180.0, 280.0, ]; let result: Vec = inp .iter() .map(|x| (*x).normalize_unsigned_angle()) .collect(); for (res, exp) in result.iter().zip(expected.iter()) { assert_relative_eq!(res, exp); } } #[test] fn normalize_angle_180_180() { let inp = [ -1000.0_f32, -900.0, -360.5, -360.0, -359.5, -240.0, -180.5, -180.0, -179.5, -90.0, -0.5, 0.0, 0.5, 90.0, 179.5, 180.0, 180.5, 240.0, 359.5, 360.0, 360.5, 900.0, 1000.0, ]; let expected = [ 80.0, 180.0, -0.5, 0.0, 0.5, 120.0, 179.5, 180.0, -179.5, -90.0, -0.5, 0.0, 0.5, 90.0, 179.5, 180.0, -179.5, -120.0, -0.5, 0.0, 0.5, 180.0, -80.0, ]; let result: Vec = inp.iter().map(|x| (*x).normalize_signed_angle()).collect(); for (res, exp) in result.iter().zip(expected.iter()) { assert_relative_eq!(res, exp); } } #[test] fn float_conversion() { for i in -180..180 { let hue = RgbHue::from(4.0 * i as f32); let degs = hue.into_degrees(); assert!(degs > -180.0 && degs <= 180.0); let pos_degs = hue.into_positive_degrees(); assert!((0.0..360.0).contains(&pos_degs)); assert_relative_eq!(RgbHue::from(degs), RgbHue::from(pos_degs)); } } } #[cfg(feature = "serializing")] mod serde { use crate::RgbHue; #[test] fn serialize() { let serialized = ::serde_json::to_string(&RgbHue::from_degrees(10.2)).unwrap(); assert_eq!(serialized, "10.2"); } #[test] fn deserialize() { let deserialized: RgbHue = ::serde_json::from_str("10.2").unwrap(); assert_eq!(deserialized, RgbHue::from_degrees(10.2)); } } } palette-0.7.5/src/hwb.rs000064400000000000000000000353551046102023000132230ustar 00000000000000//! Types for the HWB color space. use core::{any::TypeId, marker::PhantomData}; #[cfg(feature = "random")] use crate::hsv::UniformHsv; use crate::{ angle::FromAngle, bool_mask::{HasBoolMask, LazySelect, Select}, convert::FromColorUnclamped, encoding::Srgb, hues::RgbHueIter, num::{Arithmetics, One, PartialCmp, Real}, rgb::{RgbSpace, RgbStandard}, stimulus::{FromStimulus, Stimulus}, Alpha, FromColor, Hsv, RgbHue, Xyz, }; /// Linear HWB with an alpha component. See the [`Hwba` implementation in /// `Alpha`](crate::Alpha#Hwba). pub type Hwba = Alpha, T>; /// HWB color space. /// /// HWB is a cylindrical version of [RGB](crate::rgb::Rgb) and it's very /// closely related to [HSV](crate::Hsv). It describes colors with a /// starting hue, then a degree of whiteness and blackness to mix into that /// base hue. /// /// HWB component values are typically real numbers (such as floats), but may /// also be converted to and from `u8` for storage and interoperability /// purposes. The hue is then within the range `[0, 255]`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::Hwb; /// /// let hwb_u8 = Hwb::new_srgb(128u8, 85, 51); /// let hwb_f32 = hwb_u8.into_format::(); /// /// assert_relative_eq!(hwb_f32, Hwb::new(180.0, 1.0 / 3.0, 0.2)); /// ``` /// /// It is very intuitive for humans to use and many color-pickers are based on /// the HWB color system #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, rgb_standard = "S", component = "T", skip_derives(Hsv, Hwb) )] #[repr(C)] pub struct Hwb { /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. Same as the hue for HSL and HSV. #[palette(unsafe_same_layout_as = "T")] pub hue: RgbHue, /// The whiteness of the color. It specifies the amount white to mix into /// the hue. It varies from 0 to 1, with 1 being always full white and 0 /// always being the color shade (a mixture of a pure hue with black) /// chosen with the other two controls. pub whiteness: T, /// The blackness of the color. It specifies the amount black to mix into /// the hue. It varies from 0 to 1, with 1 being always full black and /// 0 always being the color tint (a mixture of a pure hue with white) /// chosen with the other two //controls. pub blackness: T, /// The white point and RGB primaries this color is adapted to. The default /// is the sRGB standard. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub standard: PhantomData, } impl Hwb { /// Create an sRGB HWB color. This method can be used instead of `Hwb::new` /// to help type inference. pub fn new_srgb>>(hue: H, whiteness: T, blackness: T) -> Self { Self::new_const(hue.into(), whiteness, blackness) } /// Create an sRGB HWB color. This is the same as `Hwb::new_srgb` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_srgb_const(hue: RgbHue, whiteness: T, blackness: T) -> Self { Self::new_const(hue, whiteness, blackness) } } impl Hwb { /// Create an HWB color. pub fn new>>(hue: H, whiteness: T, blackness: T) -> Self { Self::new_const(hue.into(), whiteness, blackness) } /// Create an HWB color. This is the same as `Hwb::new` without the generic /// hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: RgbHue, whiteness: T, blackness: T) -> Self { Hwb { hue, whiteness, blackness, standard: PhantomData, } } /// Convert into another component type. pub fn into_format(self) -> Hwb where U: FromStimulus + FromAngle, { Hwb { hue: self.hue.into_format(), whiteness: U::from_stimulus(self.whiteness), blackness: U::from_stimulus(self.blackness), standard: PhantomData, } } /// Convert from another component type. pub fn from_format(color: Hwb) -> Self where T: FromStimulus + FromAngle, { color.into_format() } /// Convert to a `(hue, whiteness, blackness)` tuple. pub fn into_components(self) -> (RgbHue, T, T) { (self.hue, self.whiteness, self.blackness) } /// Convert from a `(hue, whiteness, blackness)` tuple. pub fn from_components>>((hue, whiteness, blackness): (H, T, T)) -> Self { Self::new(hue, whiteness, blackness) } #[inline] fn reinterpret_as(self) -> Hwb { Hwb { hue: self.hue, whiteness: self.whiteness, blackness: self.blackness, standard: PhantomData, } } } impl Hwb where T: Stimulus, { /// Return the `whiteness` value minimum. pub fn min_whiteness() -> T { T::zero() } /// Return the `whiteness` value maximum. pub fn max_whiteness() -> T { T::max_intensity() } /// Return the `blackness` value minimum. pub fn min_blackness() -> T { T::zero() } /// Return the `blackness` value maximum. pub fn max_blackness() -> T { T::max_intensity() } } ///[`Hwba`](crate::Hwba) implementations. impl Alpha, A> { /// Create an sRGB HWB color with transparency. This method can be used /// instead of `Hwba::new` to help type inference. pub fn new_srgb>>(hue: H, whiteness: T, blackness: T, alpha: A) -> Self { Self::new_const(hue.into(), whiteness, blackness, alpha) } /// Create an sRGB HWB color with transparency. This is the same as /// `Hwba::new_srgb` without the generic hue type. It's temporary until `const /// fn` supports traits. pub const fn new_srgb_const(hue: RgbHue, whiteness: T, blackness: T, alpha: A) -> Self { Self::new_const(hue, whiteness, blackness, alpha) } } ///[`Hwba`](crate::Hwba) implementations. impl Alpha, A> { /// Create an HWB color with transparency. pub fn new>>(hue: H, whiteness: T, blackness: T, alpha: A) -> Self { Self::new_const(hue.into(), whiteness, blackness, alpha) } /// Create an HWB color with transparency. This is the same as `Hwba::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: RgbHue, whiteness: T, blackness: T, alpha: A) -> Self { Alpha { color: Hwb::new_const(hue, whiteness, blackness), alpha, } } /// Convert into another component type. pub fn into_format(self) -> Alpha, B> where U: FromStimulus + FromAngle, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert from another component type. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus + FromAngle, A: FromStimulus, { color.into_format() } /// Convert to a `(hue, whiteness, blackness, alpha)` tuple. pub fn into_components(self) -> (RgbHue, T, T, A) { ( self.color.hue, self.color.whiteness, self.color.blackness, self.alpha, ) } /// Convert from a `(hue, whiteness, blackness, alpha)` tuple. pub fn from_components>>( (hue, whiteness, blackness, alpha): (H, T, T, A), ) -> Self { Self::new(hue, whiteness, blackness, alpha) } } impl_reference_component_methods_hue!(Hwb, [whiteness, blackness], standard); impl_struct_of_arrays_methods_hue!(Hwb, [whiteness, blackness], standard); impl FromColorUnclamped> for Hwb where S1: RgbStandard + 'static, S2: RgbStandard + 'static, S1::Space: RgbSpace::WhitePoint>, Hsv: FromColorUnclamped>, Hsv: FromColorUnclamped>, Self: FromColorUnclamped>, { #[inline] fn from_color_unclamped(hwb: Hwb) -> Self { if TypeId::of::() == TypeId::of::() { hwb.reinterpret_as() } else { let hsv = Hsv::::from_color_unclamped(hwb); let converted_hsv = Hsv::::from_color_unclamped(hsv); Self::from_color_unclamped(converted_hsv) } } } impl FromColorUnclamped> for Hwb where T: One + Arithmetics, { #[inline] fn from_color_unclamped(color: Hsv) -> Self { Hwb { hue: color.hue, whiteness: (T::one() - color.saturation) * &color.value, blackness: (T::one() - color.value), standard: PhantomData, } } } impl_tuple_conversion_hue!(Hwb as (H, T, T), RgbHue); impl_is_within_bounds_hwb!(Hwb where T: Stimulus); impl_clamp_hwb!(Hwb phantom: standard where T: Stimulus); impl_mix_hue!(Hwb {whiteness, blackness} phantom: standard); impl_lighten_hwb!(Hwb phantom: standard where T: Stimulus); impl_hue_ops!(Hwb, RgbHue); impl HasBoolMask for Hwb where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Hwb where T: Stimulus, RgbHue: Default, { fn default() -> Hwb { Hwb::new( RgbHue::default(), Self::min_whiteness(), Self::max_blackness(), ) } } impl_color_add!(Hwb, [hue, whiteness, blackness], standard); impl_color_sub!(Hwb, [hue, whiteness, blackness], standard); impl_array_casts!(Hwb, [T; 3]); impl_simd_array_conversion_hue!(Hwb, [whiteness, blackness], standard); impl_struct_of_array_traits_hue!(Hwb, RgbHueIter, [whiteness, blackness], standard); impl_copy_clone!(Hwb, [hue, whiteness, blackness], standard); impl_eq_hue!(Hwb, RgbHue, [hue, whiteness, blackness]); #[allow(deprecated)] impl crate::RelativeContrast for Hwb where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, S: RgbStandard, Xyz<::WhitePoint, T>: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_hwb_cone!( UniformHwb, Hwb, UniformHsv, Hsv { height: value, radius: saturation } phantom: standard: PhantomData ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Hwb where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Hwb where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Hwb; test_convert_into_from_xyz!(Hwb); #[cfg(feature = "approx")] mod conversion { use crate::{FromColor, Hwb, Srgb}; #[test] fn red() { let a = Hwb::from_color(Srgb::new(1.0, 0.0, 0.0)); let b = Hwb::new_srgb(0.0, 0.0, 0.0); assert_relative_eq!(a, b); } #[test] fn orange() { let a = Hwb::from_color(Srgb::new(1.0, 0.5, 0.0)); let b = Hwb::new_srgb(30.0, 0.0, 0.0); assert_relative_eq!(a, b); } #[test] fn green() { let a = Hwb::from_color(Srgb::new(0.0, 1.0, 0.0)); let b = Hwb::new_srgb(120.0, 0.0, 0.0); assert_relative_eq!(a, b); } #[test] fn blue() { let a = Hwb::from_color(Srgb::new(0.0, 0.0, 1.0)); let b = Hwb::new_srgb(240.0, 0.0, 0.0); assert_relative_eq!(a, b); } #[test] fn purple() { let a = Hwb::from_color(Srgb::new(0.5, 0.0, 1.0)); let b = Hwb::new_srgb(270.0, 0.0, 0.0); assert_relative_eq!(a, b); } } #[cfg(feature = "approx")] mod clamp { use crate::{Clamp, Hwb}; #[test] fn clamp_invalid() { let expected = Hwb::new_srgb(240.0, 0.0, 0.0); let clamped = Hwb::new_srgb(240.0, -3.0, -4.0).clamp(); assert_relative_eq!(expected, clamped); } #[test] fn clamp_none() { let expected = Hwb::new_srgb(240.0, 0.3, 0.7); let clamped = Hwb::new_srgb(240.0, 0.3, 0.7).clamp(); assert_relative_eq!(expected, clamped); } #[test] fn clamp_over_one() { let expected = Hwb::new_srgb(240.0, 0.2, 0.8); let clamped = Hwb::new_srgb(240.0, 5.0, 20.0).clamp(); assert_relative_eq!(expected, clamped); } #[test] fn clamp_under_one() { let expected = Hwb::new_srgb(240.0, 0.3, 0.1); let clamped = Hwb::new_srgb(240.0, 0.3, 0.1).clamp(); assert_relative_eq!(expected, clamped); } } raw_pixel_conversion_tests!(Hwb: hue, whiteness, blackness); raw_pixel_conversion_fail_tests!(Hwb: hue, whiteness, blackness); #[test] fn check_min_max_components() { use crate::encoding::Srgb; assert_eq!(Hwb::::min_whiteness(), 0.0,); assert_eq!(Hwb::::min_blackness(), 0.0,); assert_eq!(Hwb::::max_whiteness(), 1.0,); assert_eq!(Hwb::::max_blackness(), 1.0,); } struct_of_arrays_tests!( Hwb[hue, whiteness, blackness] phantom: standard, super::Hwba::new(0.1f32, 0.2, 0.3, 0.4), super::Hwba::new(0.2, 0.3, 0.4, 0.5), super::Hwba::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Hwb::new_srgb(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"hue":0.3,"whiteness":0.8,"blackness":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Hwb = ::serde_json::from_str(r#"{"hue":0.3,"whiteness":0.8,"blackness":0.1}"#).unwrap(); assert_eq!(deserialized, Hwb::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Hwb as crate::rgb::Rgb { red: (0.0, 1.0), green: (0.0, 1.0), blue: (0.0, 1.0) }, min: Hwb::new(0.0f32, 0.0, 0.0), max: Hwb::new(360.0, 1.0, 1.0) } } palette-0.7.5/src/lab.rs000064400000000000000000000320001046102023000131610ustar 00000000000000//! Types for the CIE L\*a\*b\* (CIELAB) color space. use core::{ marker::PhantomData, ops::{Add, BitAnd, BitOr, Mul, Neg}, }; use crate::{ angle::RealAngle, bool_mask::{HasBoolMask, LazySelect}, color_difference::{ get_ciede2000_difference, Ciede2000, DeltaE, EuclideanDistance, ImprovedDeltaE, LabColorDiff, }, convert::FromColorUnclamped, num::{ Abs, Arithmetics, Cbrt, Exp, Hypot, MinMax, One, PartialCmp, Powf, Powi, Real, Sqrt, Trigonometry, Zero, }, white_point::{WhitePoint, D65}, Alpha, FromColor, GetHue, LabHue, Lch, Xyz, }; /// CIE L\*a\*b\* (CIELAB) with an alpha component. See the [`Laba` /// implementation in `Alpha`](crate::Alpha#Laba). pub type Laba = Alpha, T>; /// The CIE L\*a\*b\* (CIELAB) color space. /// /// CIE L\*a\*b\* is a device independent color space which includes all /// perceivable colors. It's sometimes used to convert between other color /// spaces, because of its ability to represent all of their colors, and /// sometimes in color manipulation, because of its perceptual uniformity. This /// means that the perceptual difference between two colors is equal to their /// numerical difference. It was, however, [never designed for the perceptual /// qualities required for gamut mapping](http://www.brucelindbloom.com/UPLab.html). /// For perceptually uniform color manipulation the newer color spaces based on /// [`Oklab`](crate::Oklab) are preferable: /// [`Oklch`](crate::Oklch), [`Okhsv`](crate::Okhsv), [`Okhsl`](crate::Okhsl), /// [`Okhwb`](crate::Okhwb) (Note that the latter three are tied to the sRGB gamut /// and reference white). /// /// The parameters of L\*a\*b\* are quite different, compared to many other /// color spaces, so manipulating them manually may be unintuitive. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "Wp", component = "T", skip_derives(Xyz, Lab, Lch) )] #[repr(C)] pub struct Lab { /// L\* is the lightness of the color. 0.0 gives absolute black and 100 /// give the brightest white. pub l: T, /// a\* goes from red at -128 to green at 127. pub a: T, /// b\* goes from yellow at -128 to blue at 127. pub b: T, /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } impl Lab { /// Create a CIE L\*a\*b\* color. pub const fn new(l: T, a: T, b: T) -> Lab { Lab { l, a, b, white_point: PhantomData, } } /// Convert to a `(L\*, a\*, b\*)` tuple. pub fn into_components(self) -> (T, T, T) { (self.l, self.a, self.b) } /// Convert from a `(L\*, a\*, b\*)` tuple. pub fn from_components((l, a, b): (T, T, T)) -> Self { Self::new(l, a, b) } } impl Lab where T: Zero + Real, { /// Return the `l` value minimum. pub fn min_l() -> T { T::zero() } /// Return the `l` value maximum. pub fn max_l() -> T { T::from_f64(100.0) } /// Return the `a` value minimum. pub fn min_a() -> T { T::from_f64(-128.0) } /// Return the `a` value maximum. pub fn max_a() -> T { T::from_f64(127.0) } /// Return the `b` value minimum. pub fn min_b() -> T { T::from_f64(-128.0) } /// Return the `b` value maximum. pub fn max_b() -> T { T::from_f64(127.0) } } ///[`Laba`](crate::Laba) implementations. impl Alpha, A> { /// Create a CIE L\*a\*b\* with transparency. pub const fn new(l: T, a: T, b: T, alpha: A) -> Self { Alpha { color: Lab::new(l, a, b), alpha, } } /// Convert to a `(L\*, a\*, b\*, alpha)` tuple. pub fn into_components(self) -> (T, T, T, A) { (self.color.l, self.color.a, self.color.b, self.alpha) } /// Convert from a `(L\*, a\*, b\*, alpha)` tuple. pub fn from_components((l, a, b, alpha): (T, T, T, A)) -> Self { Self::new(l, a, b, alpha) } } impl_reference_component_methods!(Lab, [l, a, b], white_point); impl_struct_of_arrays_methods!(Lab, [l, a, b], white_point); impl FromColorUnclamped> for Lab { fn from_color_unclamped(color: Lab) -> Self { color } } impl FromColorUnclamped> for Lab where Wp: WhitePoint, T: Real + Powi + Cbrt + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { fn from_color_unclamped(color: Xyz) -> Self { let Xyz { x, y, z, .. } = color / Wp::get_xyz().with_white_point(); let epsilon = T::from_f64(6.0 / 29.0).powi(3); let kappa: T = T::from_f64(841.0 / 108.0); let delta: T = T::from_f64(4.0 / 29.0); let convert = |c: T| { lazy_select! { if c.gt(&epsilon) => c.clone().cbrt(), else => (kappa.clone() * &c) + &delta, } }; let x = convert(x); let y = convert(y); let z = convert(z); Lab { l: ((y.clone() * T::from_f64(116.0)) - T::from_f64(16.0)), a: ((x - &y) * T::from_f64(500.0)), b: ((y - z) * T::from_f64(200.0)), white_point: PhantomData, } } } impl FromColorUnclamped> for Lab where T: RealAngle + Zero + MinMax + Trigonometry + Mul + Clone, { fn from_color_unclamped(color: Lch) -> Self { let (hue_sin, hue_cos) = color.hue.into_raw_radians().sin_cos(); let chroma = color.chroma.max(T::zero()); Lab { l: color.l, a: hue_cos * chroma.clone(), b: hue_sin * chroma, white_point: PhantomData, } } } impl_tuple_conversion!(Lab as (T, T, T)); impl_is_within_bounds! { Lab { l => [Self::min_l(), Self::max_l()], a => [Self::min_a(), Self::max_a()], b => [Self::min_b(), Self::max_b()] } where T: Real + Zero } impl_clamp! { Lab { l => [Self::min_l(), Self::max_l()], a => [Self::min_a(), Self::max_a()], b => [Self::min_b(), Self::max_b()] } other {white_point} where T: Real + Zero } impl_mix!(Lab); impl_lighten!(Lab increase {l => [Self::min_l(), Self::max_l()]} other {a, b} phantom: white_point); impl_premultiply!(Lab {l, a, b} phantom: white_point); impl_euclidean_distance!(Lab {l, a, b}); impl_hyab!(Lab {lightness: l, chroma1: a, chroma2: b}); impl GetHue for Lab where T: RealAngle + Trigonometry + Add + Neg + Clone, { type Hue = LabHue; fn get_hue(&self) -> LabHue { LabHue::from_cartesian(self.a.clone(), self.b.clone()) } } impl DeltaE for Lab where Self: EuclideanDistance, T: Sqrt, { type Scalar = T; #[inline] fn delta_e(self, other: Self) -> Self::Scalar { self.distance(other) } } impl ImprovedDeltaE for Lab where Self: DeltaE, T: Real + Mul + Powf + Sqrt, { #[inline] fn improved_delta_e(self, other: Self) -> Self::Scalar { // Coefficients from "Power functions improving the performance of // color-difference formulas" by Huang et al. // https://opg.optica.org/oe/fulltext.cfm?uri=oe-23-1-597&id=307643 T::from_f64(1.26) * self.delta_e(other).powf(T::from_f64(0.55)) } } #[allow(deprecated)] impl crate::ColorDifference for Lab where T: Real + RealAngle + One + Zero + Powi + Exp + Trigonometry + Abs + Sqrt + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect + BitAnd + BitOr, Self: Into>, { type Scalar = T; #[inline] fn get_color_difference(self, other: Lab) -> Self::Scalar { get_ciede2000_difference(self.into(), other.into()) } } impl Ciede2000 for Lab where T: Real + RealAngle + One + Zero + Powi + Exp + Trigonometry + Abs + Sqrt + Arithmetics + PartialCmp + Hypot + Clone, T::Mask: LazySelect + BitAnd + BitOr, { type Scalar = T; #[inline] fn difference(self, other: Self) -> Self::Scalar { get_ciede2000_difference(self.into(), other.into()) } } impl HasBoolMask for Lab where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Lab where T: Zero, { fn default() -> Lab { Lab::new(T::zero(), T::zero(), T::zero()) } } impl_color_add!(Lab, [l, a, b], white_point); impl_color_sub!(Lab, [l, a, b], white_point); impl_color_mul!(Lab, [l, a, b], white_point); impl_color_div!(Lab, [l, a, b], white_point); impl_array_casts!(Lab, [T; 3]); impl_simd_array_conversion!(Lab, [l, a, b], white_point); impl_struct_of_array_traits!(Lab, [l, a, b], white_point); impl_eq!(Lab, [l, a, b]); impl_copy_clone!(Lab, [l, a, b], white_point); #[allow(deprecated)] impl crate::RelativeContrast for Lab where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_cartesian!( UniformLab, Lab { l => [|x| x * T::from_f64(100.0)], a => [|x| x * T::from_f64(255.0) - T::from_f64(128.0)], b => [|x| x * T::from_f64(255.0) - T::from_f64(128.0)] } phantom: white_point: PhantomData where T: Real + core::ops::Sub + core::ops::Mul ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Lab where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Lab where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Lab; use crate::white_point::D65; test_convert_into_from_xyz!(Lab); #[cfg(feature = "approx")] mod conversion { use crate::{FromColor, Lab, LinSrgb}; #[test] fn red() { let a = Lab::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Lab::new(53.23288, 80.09246, 67.2031); assert_relative_eq!(a, b, epsilon = 0.01); } #[test] fn green() { let a = Lab::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Lab::new(87.73704, -86.184654, 83.18117); assert_relative_eq!(a, b, epsilon = 0.01); } #[test] fn blue() { let a = Lab::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Lab::new(32.302586, 79.19668, -107.863686); assert_relative_eq!(a, b, epsilon = 0.01); } } #[test] fn ranges() { assert_ranges! { Lab; clamped { l: 0.0 => 100.0, a: -128.0 => 127.0, b: -128.0 => 127.0 } clamped_min {} unclamped {} } } raw_pixel_conversion_tests!(Lab: l, a, b); raw_pixel_conversion_fail_tests!(Lab: l, a, b); #[test] fn check_min_max_components() { assert_eq!(Lab::::min_l(), 0.0); assert_eq!(Lab::::min_a(), -128.0); assert_eq!(Lab::::min_b(), -128.0); assert_eq!(Lab::::max_l(), 100.0); assert_eq!(Lab::::max_a(), 127.0); assert_eq!(Lab::::max_b(), 127.0); } struct_of_arrays_tests!( Lab[l, a, b] phantom: white_point, super::Laba::new(0.1f32, 0.2, 0.3, 0.4), super::Laba::new(0.2, 0.3, 0.4, 0.5), super::Laba::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Lab::::new(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"l":0.3,"a":0.8,"b":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Lab = ::serde_json::from_str(r#"{"l":0.3,"a":0.8,"b":0.1}"#).unwrap(); assert_eq!(deserialized, Lab::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Lab { l: (0.0, 100.0), a: (-128.0, 127.0), b: (-128.0, 127.0) }, min: Lab::new(0.0f32, -128.0, -128.0), max: Lab::new(100.0, 127.0, 127.0) } } palette-0.7.5/src/lch.rs000064400000000000000000000346431046102023000132100ustar 00000000000000//! Types for the CIE L\*C\*h° color space. use core::{ marker::PhantomData, ops::{BitAnd, BitOr}, }; use crate::{ angle::RealAngle, bool_mask::{HasBoolMask, LazySelect}, color_difference::{get_ciede2000_difference, Ciede2000, DeltaE, ImprovedDeltaE, LabColorDiff}, convert::{FromColorUnclamped, IntoColorUnclamped}, hues::LabHueIter, num::{Abs, Arithmetics, Exp, Hypot, One, PartialCmp, Powi, Real, Sqrt, Trigonometry, Zero}, white_point::D65, Alpha, FromColor, GetHue, Lab, LabHue, Xyz, }; /// CIE L\*C\*h° with an alpha component. See the [`Lcha` implementation in /// `Alpha`](crate::Alpha#Lcha). pub type Lcha = Alpha, T>; /// CIE L\*C\*h°, a polar version of [CIE L\*a\*b\*](crate::Lab). /// /// L\*C\*h° shares its range and perceptual uniformity with L\*a\*b\*, but /// it's a cylindrical color space, like [HSL](crate::Hsl) and /// [HSV](crate::Hsv). This gives it the same ability to directly change /// the hue and colorfulness of a color, while preserving other visual aspects. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "Wp", component = "T", skip_derives(Lab, Lch) )] #[repr(C)] pub struct Lch { /// L\* is the lightness of the color. 0.0 gives absolute black and 100.0 /// gives the brightest white. pub l: T, /// C\* is the colorfulness of the color. It's similar to saturation. 0.0 /// gives gray scale colors, and numbers around 128-181 gives fully /// saturated colors. The upper limit of 128 should /// include the whole L\*a\*b\* space and some more. pub chroma: T, /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. #[palette(unsafe_same_layout_as = "T")] pub hue: LabHue, /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } impl Lch { /// Create a CIE L\*C\*h° color. pub fn new>>(l: T, chroma: T, hue: H) -> Self { Self::new_const(l, chroma, hue.into()) } /// Create a CIE L\*C\*h° color. This is the same as `Lch::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(l: T, chroma: T, hue: LabHue) -> Self { Lch { l, chroma, hue, white_point: PhantomData, } } /// Convert to a `(L\*, C\*, h°)` tuple. pub fn into_components(self) -> (T, T, LabHue) { (self.l, self.chroma, self.hue) } /// Convert from a `(L\*, C\*, h°)` tuple. pub fn from_components>>((l, chroma, hue): (T, T, H)) -> Self { Self::new(l, chroma, hue) } } impl Lch where T: Zero + Real, { /// Return the `l` value minimum. pub fn min_l() -> T { T::zero() } /// Return the `l` value maximum. pub fn max_l() -> T { T::from_f64(100.0) } /// Return the `chroma` value minimum. pub fn min_chroma() -> T { T::zero() } /// Return the `chroma` value maximum. This value does not cover the entire /// color space, but covers enough to be practical for downsampling to /// smaller color spaces like sRGB. pub fn max_chroma() -> T { T::from_f64(128.0) } /// Return the `chroma` extended maximum value. This value covers the entire /// color space and is included for completeness, but the additional range /// should be unnecessary for most use cases. pub fn max_extended_chroma() -> T { T::from_f64(crate::num::Sqrt::sqrt(128.0f64 * 128.0 + 128.0 * 128.0)) } } ///[`Lcha`](crate::Lcha) implementations. impl Alpha, A> { /// Create a CIE L\*C\*h° color with transparency. pub fn new>>(l: T, chroma: T, hue: H, alpha: A) -> Self { Self::new_const(l, chroma, hue.into(), alpha) } /// Create a CIE L\*C\*h° color with transparency. This is the same as /// `Lcha::new` without the generic hue type. It's temporary until `const /// fn` supports traits. pub const fn new_const(l: T, chroma: T, hue: LabHue, alpha: A) -> Self { Alpha { color: Lch::new_const(l, chroma, hue), alpha, } } /// Convert to a `(L\*, C\*, h°, alpha)` tuple. pub fn into_components(self) -> (T, T, LabHue, A) { (self.color.l, self.color.chroma, self.color.hue, self.alpha) } /// Convert from a `(L\*, C\*, h°, alpha)` tuple. pub fn from_components>>((l, chroma, hue, alpha): (T, T, H, A)) -> Self { Self::new(l, chroma, hue, alpha) } } impl_reference_component_methods_hue!(Lch, [l, chroma], white_point); impl_struct_of_arrays_methods_hue!(Lch, [l, chroma], white_point); impl FromColorUnclamped> for Lch { fn from_color_unclamped(color: Lch) -> Self { color } } impl FromColorUnclamped> for Lch where T: Zero + Hypot, Lab: GetHue>, { fn from_color_unclamped(color: Lab) -> Self { Lch { hue: color.get_hue(), l: color.l, chroma: color.a.hypot(color.b), white_point: PhantomData, } } } impl_tuple_conversion_hue!(Lch as (T, T, H), LabHue); impl_is_within_bounds! { Lch { l => [Self::min_l(), Self::max_l()], chroma => [Self::min_chroma(), None] } where T: Real + Zero } impl_clamp! { Lch { l => [Self::min_l(), Self::max_l()], chroma => [Self::min_chroma()] } other {hue, white_point} where T: Real + Zero } impl_mix_hue!(Lch {l, chroma} phantom: white_point); impl_lighten!(Lch increase {l => [Self::min_l(), Self::max_l()]} other {hue, chroma} phantom: white_point); impl_saturate!(Lch increase {chroma => [Self::min_chroma(), Self::max_chroma()]} other {hue, l} phantom: white_point); impl_hue_ops!(Lch, LabHue); impl DeltaE for Lch where Lab: FromColorUnclamped + DeltaE, { type Scalar = T; #[inline] fn delta_e(self, other: Self) -> Self::Scalar { // The definitions of delta E for Lch and Lab are equivalent. Converting // to Lab is the fastest way, so far. Lab::from_color_unclamped(self).delta_e(other.into_color_unclamped()) } } impl ImprovedDeltaE for Lch where Lab: FromColorUnclamped + ImprovedDeltaE, { #[inline] fn improved_delta_e(self, other: Self) -> Self::Scalar { // The definitions of delta E for Lch and Lab are equivalent. Lab::from_color_unclamped(self).improved_delta_e(other.into_color_unclamped()) } } /// CIEDE2000 distance metric for color difference. #[allow(deprecated)] impl crate::ColorDifference for Lch where T: Real + RealAngle + One + Zero + Trigonometry + Abs + Sqrt + Powi + Exp + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect + BitAnd + BitOr, Self: Into>, { type Scalar = T; #[inline] fn get_color_difference(self, other: Lch) -> Self::Scalar { get_ciede2000_difference(self.into(), other.into()) } } impl Ciede2000 for Lch where T: Real + RealAngle + One + Zero + Powi + Exp + Trigonometry + Abs + Sqrt + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect + BitAnd + BitOr, Self: IntoColorUnclamped>, { type Scalar = T; #[inline] fn difference(self, other: Self) -> Self::Scalar { get_ciede2000_difference(self.into(), other.into()) } } impl HasBoolMask for Lch where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Lch where T: Zero + Real, LabHue: Default, { fn default() -> Lch { Lch::new(Self::min_l(), Self::min_chroma(), LabHue::default()) } } impl_color_add!(Lch, [l, chroma, hue], white_point); impl_color_sub!(Lch, [l, chroma, hue], white_point); impl_array_casts!(Lch, [T; 3]); impl_simd_array_conversion_hue!(Lch, [l, chroma], white_point); impl_struct_of_array_traits_hue!(Lch, LabHueIter, [l, chroma], white_point); impl_eq_hue!(Lch, LabHue, [l, chroma, hue]); impl_copy_clone!(Lch, [l, chroma, hue], white_point); #[allow(deprecated)] impl crate::RelativeContrast for Lch where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_cylinder!( UniformLch, Lch { hue: UniformLabHue => LabHue, height: l => [|l: T| l * Lch::::max_l()], radius: chroma => [|chroma| chroma * Lch::::max_chroma()] } phantom: white_point: PhantomData where T: Real + Zero + core::ops::Mul, ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Lch where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Lch where T: bytemuck::Pod {} #[cfg(test)] mod test { use crate::{white_point::D65, Lch}; #[cfg(all(feature = "alloc", feature = "approx"))] use crate::{ color_difference::{DeltaE, ImprovedDeltaE}, convert::IntoColorUnclamped, Lab, }; test_convert_into_from_xyz!(Lch); #[test] fn ranges() { assert_ranges! { Lch; clamped { l: 0.0 => 100.0 } clamped_min { chroma: 0.0 => 200.0 } unclamped { hue: -360.0 => 360.0 } } } raw_pixel_conversion_tests!(Lch: l, chroma, hue); raw_pixel_conversion_fail_tests!(Lch: l, chroma, hue); #[test] fn check_min_max_components() { assert_eq!(Lch::::min_l(), 0.0); assert_eq!(Lch::::max_l(), 100.0); assert_eq!(Lch::::min_chroma(), 0.0); assert_eq!(Lch::::max_chroma(), 128.0); #[cfg(feature = "approx")] assert_relative_eq!(Lch::::max_extended_chroma(), 181.01933598375618); } #[cfg(feature = "approx")] #[test] fn delta_e_large_hue_diff() { use crate::color_difference::DeltaE; let lhs1 = Lch::::new(50.0, 64.0, -730.0); let rhs1 = Lch::new(50.0, 64.0, 730.0); let lhs2 = Lch::::new(50.0, 64.0, -10.0); let rhs2 = Lch::new(50.0, 64.0, 10.0); assert_relative_eq!( lhs1.delta_e(rhs1), lhs2.delta_e(rhs2), epsilon = 0.0000000000001 ); } // Lab and Lch have the same delta E. #[cfg(all(feature = "alloc", feature = "approx"))] #[test] fn lab_delta_e_equality() { let mut lab_colors: Vec> = Vec::new(); for l_step in 0i8..5 { for a_step in -2i8..3 { for b_step in -2i8..3 { lab_colors.push(Lab::new( l_step as f64 * 25.0, a_step as f64 * 60.0, b_step as f64 * 60.0, )) } } } let lch_colors: Vec> = lab_colors.clone().into_color_unclamped(); for (&lhs_lab, &lhs_lch) in lab_colors.iter().zip(&lch_colors) { for (&rhs_lab, &rhs_lch) in lab_colors.iter().zip(&lch_colors) { let delta_e_lab = lhs_lab.delta_e(rhs_lab); let delta_e_lch = lhs_lch.delta_e(rhs_lch); assert_relative_eq!(delta_e_lab, delta_e_lch, epsilon = 0.0000000000001); } } } // Lab and Lch have the same delta E, so should also have the same improved // delta E. #[cfg(all(feature = "alloc", feature = "approx"))] #[test] fn lab_improved_delta_e_equality() { let mut lab_colors: Vec> = Vec::new(); for l_step in 0i8..5 { for a_step in -2i8..3 { for b_step in -2i8..3 { lab_colors.push(Lab::new( l_step as f64 * 25.0, a_step as f64 * 60.0, b_step as f64 * 60.0, )) } } } let lch_colors: Vec> = lab_colors.clone().into_color_unclamped(); for (&lhs_lab, &lhs_lch) in lab_colors.iter().zip(&lch_colors) { for (&rhs_lab, &rhs_lch) in lab_colors.iter().zip(&lch_colors) { let delta_e_lab = lhs_lab.improved_delta_e(rhs_lab); let delta_e_lch = lhs_lch.improved_delta_e(rhs_lch); assert_relative_eq!(delta_e_lab, delta_e_lch, epsilon = 0.0000000000001); } } } struct_of_arrays_tests!( Lch[l, chroma, hue] phantom: white_point, super::Lcha::new(0.1f32, 0.2, 0.3, 0.4), super::Lcha::new(0.2, 0.3, 0.4, 0.5), super::Lcha::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Lch::::new(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"l":0.3,"chroma":0.8,"hue":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Lch = ::serde_json::from_str(r#"{"l":0.3,"chroma":0.8,"hue":0.1}"#).unwrap(); assert_eq!(deserialized, Lch::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Lch as crate::Lab { l: (0.0, 100.0), a: (-89.0, 89.0), b: (-89.0, 89.0), }, min: Lch::new(0.0f32, 0.0, 0.0), max: Lch::new(100.0, 128.0, 360.0) } } palette-0.7.5/src/lchuv.rs000064400000000000000000000245321046102023000135570ustar 00000000000000//! Types for the CIE L\*C\*uv h°uv color space. use core::{marker::PhantomData, ops::Mul}; use crate::{ angle::RealAngle, bool_mask::{HasBoolMask, LazySelect}, convert::FromColorUnclamped, hues::LuvHueIter, luv_bounds::LuvBounds, num::{Arithmetics, Hypot, PartialCmp, Powi, Real, Zero}, white_point::D65, Alpha, FromColor, GetHue, Hsluv, Luv, LuvHue, Xyz, }; /// CIE L\*C\*uv h°uv with an alpha component. See the [`Lchuva` implementation in /// `Alpha`](crate::Alpha#Lchuva). pub type Lchuva = Alpha, T>; /// CIE L\*C\*uv h°uv, a polar version of [CIE L\*u\*v\*](crate::Luv). /// /// L\*C\*uv h°uv shares its range and perceptual uniformity with L\*u\*v\*, but /// it's a cylindrical color space, like [HSL](crate::Hsl) and /// [HSV](crate::Hsv). This gives it the same ability to directly change /// the hue and colorfulness of a color, while preserving other visual aspects. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "Wp", component = "T", skip_derives(Luv, Lchuv, Hsluv) )] #[repr(C)] pub struct Lchuv { /// L\* is the lightness of the color. 0.0 gives absolute black and 100.0 /// gives the brightest white. pub l: T, /// C\*uv is the colorfulness of the color. It's similar to /// saturation. 0.0 gives gray scale colors, and numbers around /// 130-180 gives fully saturated colors, depending on the /// hue. The upper limit of 180 should include the whole /// L\*u\*v\*. pub chroma: T, /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. #[palette(unsafe_same_layout_as = "T")] pub hue: LuvHue, /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } impl Lchuv { /// Create a CIE L\*C\*uv h°uv color. pub fn new>>(l: T, chroma: T, hue: H) -> Self { Self::new_const(l, chroma, hue.into()) } /// Create a CIE L\*C\*uv h°uv color. This is the same as `Lchuv::new` /// without the generic hue type. It's temporary until `const fn` supports /// traits. pub const fn new_const(l: T, chroma: T, hue: LuvHue) -> Self { Lchuv { l, chroma, hue, white_point: PhantomData, } } /// Convert to a `(L\*, C\*uv, h°uv)` tuple. pub fn into_components(self) -> (T, T, LuvHue) { (self.l, self.chroma, self.hue) } /// Convert from a `(L\*, C\*uv, h°uv)` tuple. pub fn from_components>>((l, chroma, hue): (T, T, H)) -> Self { Self::new(l, chroma, hue) } } impl Lchuv where T: Zero + Real, { /// Return the `l` value minimum. pub fn min_l() -> T { T::zero() } /// Return the `l` value maximum. pub fn max_l() -> T { T::from_f64(100.0) } /// Return the `chroma` value minimum. pub fn min_chroma() -> T { T::zero() } /// Return the `chroma` value maximum. pub fn max_chroma() -> T { T::from_f64(180.0) } } ///[`Lchuva`](crate::Lchuva) implementations. impl Alpha, A> { /// Create a CIE L\*C\*uv h°uv color with transparency. pub fn new>>(l: T, chroma: T, hue: H, alpha: A) -> Self { Self::new_const(l, chroma, hue.into(), alpha) } /// Create a CIE L\*C\*uv h°uv color with transparency. This is the same as /// `Lchuva::new` without the generic hue type. It's temporary until `const /// fn` supports traits. pub const fn new_const(l: T, chroma: T, hue: LuvHue, alpha: A) -> Self { Alpha { color: Lchuv::new_const(l, chroma, hue), alpha, } } /// Convert to a `(L\*, C\*uv, h°uv, alpha)` tuple. pub fn into_components(self) -> (T, T, LuvHue, A) { (self.color.l, self.color.chroma, self.color.hue, self.alpha) } /// Convert from a `(L\*, C\*uv, h°uv, alpha)` tuple. pub fn from_components>>((l, chroma, hue, alpha): (T, T, H, A)) -> Self { Self::new(l, chroma, hue, alpha) } } impl_reference_component_methods_hue!(Lchuv, [l, chroma], white_point); impl_struct_of_arrays_methods_hue!(Lchuv, [l, chroma], white_point); impl FromColorUnclamped> for Lchuv { fn from_color_unclamped(color: Lchuv) -> Self { color } } impl FromColorUnclamped> for Lchuv where T: Zero + Hypot, Luv: GetHue>, { fn from_color_unclamped(color: Luv) -> Self { Lchuv { hue: color.get_hue(), l: color.l, chroma: color.u.hypot(color.v), white_point: PhantomData, } } } impl FromColorUnclamped> for Lchuv where T: Real + RealAngle + Into + Powi + Mul + Clone, { fn from_color_unclamped(color: Hsluv) -> Self { // Apply the given saturation as a percentage of the max // chroma for that hue. let max_chroma = LuvBounds::from_lightness(color.l.clone()).max_chroma_at_hue(color.hue.clone()); Lchuv::new( color.l, color.saturation * max_chroma * T::from_f64(0.01), color.hue, ) } } impl_tuple_conversion_hue!(Lchuv as (T, T, H), LuvHue); impl_is_within_bounds! { Lchuv { l => [Self::min_l(), Self::max_l()], chroma => [Self::min_chroma(), Self::max_chroma()] } where T: Real + Zero } impl_clamp! { Lchuv { l => [Self::min_l(), Self::max_l()], chroma => [Self::min_chroma(), Self::max_chroma()] } other {hue, white_point} where T: Real + Zero } impl_mix_hue!(Lchuv {l, chroma} phantom: white_point); impl_lighten!(Lchuv increase {l => [Self::min_l(), Self::max_l()]} other {hue, chroma} phantom: white_point); impl_saturate!(Lchuv increase {chroma => [Self::min_chroma(), Self::max_chroma()]} other {hue, l} phantom: white_point); impl_hue_ops!(Lchuv, LuvHue); impl HasBoolMask for Lchuv where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Lchuv where T: Zero + Real, LuvHue: Default, { fn default() -> Lchuv { Lchuv::new(Self::min_l(), Self::min_chroma(), LuvHue::default()) } } impl_color_add!(Lchuv, [l, chroma, hue], white_point); impl_color_sub!(Lchuv, [l, chroma, hue], white_point); impl_array_casts!(Lchuv, [T; 3]); impl_simd_array_conversion_hue!(Lchuv, [l, chroma], white_point); impl_struct_of_array_traits_hue!(Lchuv, LuvHueIter, [l, chroma], white_point); impl_eq_hue!(Lchuv, LuvHue, [l, chroma, hue]); impl_copy_clone!(Lchuv, [l, chroma, hue], white_point); #[allow(deprecated)] impl crate::RelativeContrast for Lchuv where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_cylinder!( UniformLchuv, Lchuv { hue: UniformLuvHue => LuvHue, height: l => [|l: T| l * Lchuv::::max_l()], radius: chroma => [|chroma| chroma * Lchuv::::max_chroma()] } phantom: white_point: PhantomData where T: Real + Zero + core::ops::Mul, ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Lchuv where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Lchuv where T: bytemuck::Pod {} #[cfg(test)] mod test { use crate::white_point::D65; use crate::Lchuv; test_convert_into_from_xyz!(Lchuv); #[test] fn ranges() { assert_ranges! { Lchuv; clamped { l: 0.0 => 100.0, chroma: 0.0 => 180.0 } clamped_min { } unclamped { hue: -360.0 => 360.0 } } } /// Check that the arithmetic operations (add/sub) are all /// implemented. #[test] fn test_arithmetic() { let lchuv = Lchuv::::new(120.0, 40.0, 30.0); let lchuv2 = Lchuv::new(200.0, 30.0, 40.0); let mut _lchuv3 = lchuv + lchuv2; _lchuv3 += lchuv2; let mut _lchuv4 = lchuv2 + 0.3; _lchuv4 += 0.1; _lchuv3 = lchuv2 - lchuv; _lchuv3 = _lchuv4 - 0.1; _lchuv4 -= _lchuv3; _lchuv3 -= 0.1; } raw_pixel_conversion_tests!(Lchuv: l, chroma, hue); raw_pixel_conversion_fail_tests!(Lchuv: l, chroma, hue); #[test] fn check_min_max_components() { assert_eq!(Lchuv::::min_l(), 0.0); assert_eq!(Lchuv::::max_l(), 100.0); assert_eq!(Lchuv::::min_chroma(), 0.0); assert_eq!(Lchuv::::max_chroma(), 180.0); } struct_of_arrays_tests!( Lchuv[l, chroma, hue] phantom: white_point, super::Lchuva::new(0.1f32, 0.2, 0.3, 0.4), super::Lchuva::new(0.2, 0.3, 0.4, 0.5), super::Lchuva::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Lchuv::::new(80.0, 70.0, 130.0)).unwrap(); assert_eq!(serialized, r#"{"l":80.0,"chroma":70.0,"hue":130.0}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Lchuv = ::serde_json::from_str(r#"{"l":70.0,"chroma":80.0,"hue":130.0}"#).unwrap(); assert_eq!(deserialized, Lchuv::new(70.0, 80.0, 130.0)); } test_uniform_distribution! { Lchuv as crate::Luv { l: (0.0, 100.0), u: (-80.0, 80.0), v: (-80.0, 80.0), }, min: Lchuv::new(0.0f32, 0.0, 0.0), max: Lchuv::new(100.0, 180.0, 360.0) } } palette-0.7.5/src/lib.rs000064400000000000000000001316771046102023000132150ustar 00000000000000//! A library that makes linear color calculations and conversion easy and //! accessible for anyone. It uses the type system to enforce correctness and to //! avoid mistakes, such as mixing incompatible color types. //! //! # Where Do I Start? //! //! The sections below give an overview of how the types in this library work, //! including color conversion. If you want to get your hands dirty, you'll //! probably want to start with [`Srgb`] or [`Srgba`]. They are aliases for the //! more generic [`Rgb`](rgb::Rgb) type and represent sRGB(A), the most common //! RGB format in images and tools. Their documentation has more details and //! examples. //! //! The documentation for each module and type goes deeper into their concepts. //! Here are a few you may want to read: //! //! * [`Rgb`](rgb::Rgb) - For getting started with RGB values. //! * [`Alpha`] - For more details on transparency. //! * [`convert`] - Describes the conversion traits and how to use and implement //! them. //! * [`cast`] - Describes how to cast color types to and from other data //! formats, such as arrays and unsigned integers. //! * [`color_difference`] - Describes different ways of measuring the //! difference between colors. //! //! # Type Safety for Colors //! //! Digital colors are not "just RGB", and not even RGB is "just RGB". There are //! multiple representations of color, with a variety of pros and cons, and //! multiple standards for how to encode and decode them. Palette represents //! these "color spaces" as separate types for increased expressiveness and to //! prevent mistakes. //! //! Taking RGB as an example, it's often stored or displayed as "gamma //! corrected" values, meaning that a non-linear function has been applied to //! its values. This encoding is not suitable for all kinds of calculations //! (such as rescaling) and will give visibly incorrect results. Functions that //! require linear RGB can therefore request, for example, [`LinSrgb`] as their //! input type. //! //! ```rust,compile_fail //! // Srgb is an alias for Rgb, which is what most pictures store. //! // LinSrgb is an alias for Rgb, T>, better for color manipulation. //! use palette::{Srgb, LinSrgb}; //! //! fn do_something(a: LinSrgb, b: LinSrgb) -> LinSrgb { //! // ... //! # LinSrgb::default() //! } //! //! let orangeish = Srgb::new(1.0, 0.6, 0.0); //! let blueish = Srgb::new(0.0, 0.2, 1.0); //! let result = do_something(orangeish, blueish); // Does not compile //! ``` //! //! The colors will have to be decoded before being used in the function: //! //! ```rust //! // Srgb is an alias for Rgb, which is what most pictures store. //! // LinSrgb is an alias for Rgb, T>, better for color manipulation. //! use palette::{Srgb, LinSrgb}; //! //! fn do_something(a: LinSrgb, b: LinSrgb) -> LinSrgb { //! // ... //! # LinSrgb::default() //! } //! //! let orangeish = Srgb::new(1.0, 0.6, 0.0).into_linear(); //! let blueish = Srgb::new(0.0, 0.2, 1.0).into_linear(); //! let result = do_something(orangeish, blueish); //! ``` //! //! See the [rgb] module for a deeper dive into RGB and (non-)linearity. //! //! # Color Spaces and Conversion //! //! As the previous section mentions, there are many different ways of //! representing colors. These "color spaces" are represented as different types //! in Palette, each with a description of what it is and how it works. Most of //! them also have two type parameters for customization: //! //! * The component type (`T`) that decides which number type is used. The //! default is `f32`, but `u8`, `f64`, and any other type that implement the //! required traits will work. Including SIMD types in many cases. //! * The reference white point (`W`) or standard (`S`) that affects the range, //! encoding or display properties of the color. This varies between color //! spaces and can usually be left as its default or be set via a type alias. //! For example, the [`Srgb`] and [`LinSrgb`] type aliases are both variants //! of the [`Rgb`][rgb::Rgb] type, but with different standard (`S`) types. //! //! Selecting the proper color space can have a big impact on how the resulting //! image looks (as illustrated by some of the programs in `examples`), and //! Palette makes the conversion between them as easy as a call to //! [`from_color`][FromColor::from_color] or //! [`into_color`][IntoColor::into_color]. //! //! This example takes an sRGB color, converts it to CIE L\*C\*h°, a color space //! similar to the colloquial HSL/HSV color spaces, shifts its hue by 180° and //! converts it back to RGB: //! //! ``` //! use palette::{FromColor, ShiftHue, IntoColor, Lch, Srgb}; //! //! let lch_color: Lch = Srgb::new(0.8, 0.2, 0.1).into_color(); //! let new_color = Srgb::from_color(lch_color.shift_hue(180.0)); //! ``` //! //! # Transparency //! //! There are many cases where pixel transparency is important, but there are //! also many cases where it would just be unused memory space. Palette has //! therefore adopted a structure where the transparency component (alpha) is //! attachable using the [`Alpha`](crate::Alpha) type. This approach has shown //! to be very modular and easy to maintain, compared to having transparent //! copies of each type. //! //! An additional benefit is allowing operations to selectively affect the alpha //! component: //! //! ```rust //! // Each color type has a transparent alias that ends with "a" for "alpha" //! use palette::{LinSrgb, LinSrgba}; //! //! let mut c1 = LinSrgba::new(1.0, 0.5, 0.5, 0.8); //! let c2 = LinSrgb::new(0.5, 1.0, 1.0); //! //! c1.color = c1.color * c2; //Leave the alpha as it is //! c1.blue += 0.2; //The color components can easily be accessed //! c1 = c1 * 0.5; //Scale both the color and the alpha //! ``` //! //! There's also [`PreAlpha`][blend::PreAlpha] that represents pre-multiplied //! alpha (also known as alpha masked colors). It's commonly used in color //! blending and composition. //! //! # Images and Buffers //! //! Oftentimes, pixel data is stored in a plain array or slice such as a `[u8; //! 3]`. The [`cast`] module allows for easy conversion between Palette colors //! and arrays or slices. This also helps when working with other crates or //! systems. Here's an example of how the pixels in an image from the `image` //! crate can be worked with as `Srgb`: //! //! ```rust //! use image::RgbImage; //! use palette::{Srgb, Oklab, cast::FromComponents, Lighten, IntoColor, FromColor}; //! //! fn lighten(image: &mut RgbImage, amount: f32) { //! // RgbImage can be dereferenced as [u8], allowing us to cast it as a //! // component slice to sRGB with u8 components. //! for pixel in <&mut [Srgb]>::from_components(&mut **image) { //! // Converting to linear sRGB with f32 components, and then to Oklab. //! let color: Oklab = pixel.into_linear::().into_color(); //! //! let lightened_color = color.lighten(amount); //! //! // Converting back to non-linear sRGB with u8 components. //! *pixel = Srgb::from_linear(lightened_color.into_color()); //! } //! } //! ``` //! //! Some of the conversions are also implemented on the color types as `From`, //! `TryFrom`, `Into`, `TryFrom` and `AsRef`. This example shows how `from` can //! be used to convert a `[u8;3]` into a Palette color, `into_format` converts //! from `Srgb` to `Srgb`, and finally `into` converts back from a //! Palette color back to a `[u8;3]`: //! //! ```rust //! use approx::assert_relative_eq; //! use palette::Srgb; //! //! let buffer = [255, 0, 255]; //! let srgb = Srgb::from(buffer); //! assert_eq!(srgb, Srgb::::new(255u8, 0, 255)); //! //! let srgb_float: Srgb = srgb.into_format(); //! assert_relative_eq!(srgb_float, Srgb::new(1.0, 0.0, 1.0)); //! //! let array: [u8; 3] = srgb_float.into_format().into(); //! assert_eq!(array, buffer); //! ``` //! //! # A Basic Workflow //! //! The overall workflow can be divided into three steps, where the first and //! last may be taken care of by other parts of the application: //! //! ```text //! Decoding -> Processing -> Encoding //! ``` //! //! ## 1. Decoding //! //! Find out what the source format is and convert it to a linear color space. //! There may be a specification, such as when working with SVG or CSS. //! //! When working with RGB or gray scale (luma): //! //! * If you are asking your user to enter an RGB value, you are in a gray zone //! where it depends on the context. It's usually safe to assume sRGB, but //! sometimes it's already linear. //! //! * If you are decoding an image, there may be some meta data that gives you //! the necessary details. Otherwise it's most commonly sRGB. Usually you will //! end up with a slice or vector with RGB bytes, which can easily be converted //! to Palette colors: //! //! ```rust //! # let mut image_buffer: Vec = vec![]; //! use palette::{Srgb, cast::ComponentsAsMut}; //! //! // This works for any color type (not only RGB) that can have the //! // buffer element type as component. //! let color_buffer: &mut [Srgb] = image_buffer.components_as_mut(); //! ``` //! //! * If you are getting your colors from the GPU, in a game or other graphical //! application, or if they are otherwise generated by the application, then //! chances are that they are already linear. Still, make sure to check that //! they are not being encoded somewhere. //! //! When working with other colors: //! //! * For HSL, HSV, HWB: Check if they are based on any other color space than //! sRGB, such as Adobe or Apple RGB. //! //! * For any of the CIE color spaces, check for a specification of white point //! and light source. These are necessary for converting to RGB and other //! colors, that depend on perception and "viewing devices". Common defaults are //! the D65 light source and the sRGB white point. The Palette defaults should //! take you far. //! //! ## 2. Processing //! //! When your color has been decoded into some Palette type, it's ready for //! processing. This includes things like blending, hue shifting, darkening and //! conversion to other formats. Just make sure that your non-linear RGB is made //! linear first (`my_srgb.into_linear()`), to make the operations available. //! //! Different color spaced have different capabilities, pros and cons. You may //! have to experiment a bit (or look at the example programs) to find out what //! gives the desired result. //! //! ## 3. Encoding //! //! When the desired processing is done, it's time to encode the colors back //! into some image format. The same rules applies as for the decoding, but the //! process reversed. // Keep the standard library when running tests, too #![cfg_attr(all(not(feature = "std"), not(test)), no_std)] #![doc(html_root_url = "https://docs.rs/palette/0.7.5/")] #![warn(missing_docs)] #[cfg(feature = "alloc")] extern crate alloc; #[cfg(any(feature = "std", test))] extern crate core; #[cfg(feature = "approx")] #[cfg_attr(test, macro_use)] extern crate approx; #[macro_use] extern crate palette_derive; #[cfg(feature = "phf")] extern crate phf; #[cfg(feature = "serializing")] #[macro_use] extern crate serde as _; #[cfg(all(test, feature = "serializing"))] extern crate serde_json; use core::ops::{BitAndAssign, Neg}; use bool_mask::{BoolMask, HasBoolMask}; use luma::Luma; #[doc(inline)] pub use alpha::{Alpha, WithAlpha}; #[doc(inline)] pub use hsl::{Hsl, Hsla}; #[doc(inline)] pub use hsluv::{Hsluv, Hsluva}; #[doc(inline)] pub use hsv::{Hsv, Hsva}; #[doc(inline)] pub use hwb::{Hwb, Hwba}; #[doc(inline)] pub use lab::{Lab, Laba}; #[doc(inline)] pub use lch::{Lch, Lcha}; #[doc(inline)] pub use lchuv::{Lchuv, Lchuva}; #[doc(inline)] pub use luma::{GammaLuma, GammaLumaa, LinLuma, LinLumaa, SrgbLuma, SrgbLumaa}; #[doc(inline)] pub use luv::{Luv, Luva}; #[doc(inline)] pub use okhsl::{Okhsl, Okhsla}; #[doc(inline)] pub use okhsv::{Okhsv, Okhsva}; #[doc(inline)] pub use okhwb::{Okhwb, Okhwba}; #[doc(inline)] pub use oklab::{Oklab, Oklaba}; #[doc(inline)] pub use oklch::{Oklch, Oklcha}; #[doc(inline)] pub use rgb::{GammaSrgb, GammaSrgba, LinSrgb, LinSrgba, Srgb, Srgba}; #[doc(inline)] pub use xyz::{Xyz, Xyza}; #[doc(inline)] pub use yxy::{Yxy, Yxya}; #[doc(inline)] pub use hues::{LabHue, LuvHue, OklabHue, RgbHue}; #[allow(deprecated)] pub use color_difference::ColorDifference; pub use convert::{FromColor, FromColorMut, FromColorMutGuard, IntoColor, IntoColorMut}; pub use matrix::Mat3; #[allow(deprecated)] pub use relative_contrast::{contrast_ratio, RelativeContrast}; #[macro_use] mod macros; #[cfg(feature = "named")] pub mod named; #[cfg(feature = "random")] mod random_sampling; #[cfg(feature = "serializing")] pub mod serde; pub mod alpha; pub mod angle; pub mod blend; pub mod bool_mask; pub mod cast; pub mod chromatic_adaptation; pub mod color_difference; pub mod convert; pub mod encoding; pub mod hsl; pub mod hsluv; pub mod hsv; pub mod hues; pub mod hwb; pub mod lab; pub mod lch; pub mod lchuv; pub mod luma; pub mod luv; mod luv_bounds; pub mod num; mod ok_utils; pub mod okhsl; pub mod okhsv; pub mod okhwb; pub mod oklab; pub mod oklch; mod relative_contrast; pub mod rgb; pub mod stimulus; pub mod white_point; pub mod xyz; pub mod yxy; #[cfg(test)] #[cfg(feature = "approx")] mod visual; #[doc(hidden)] pub mod matrix; #[inline] fn clamp(value: T, min: T, max: T) -> T { value.clamp(min, max) } #[inline] fn clamp_assign(value: &mut T, min: T, max: T) { value.clamp_assign(min, max); } #[inline] fn clamp_min(value: T, min: T) -> T { value.clamp_min(min) } #[inline] fn clamp_min_assign(value: &mut T, min: T) { value.clamp_min_assign(min); } /// Checks if color components are within their expected range bounds. /// /// A color with out-of-bounds components may be clamped with [`Clamp`] or /// [`ClampAssign`]. /// /// ``` /// use palette::{Srgb, IsWithinBounds}; /// let a = Srgb::new(0.4f32, 0.3, 0.8); /// let b = Srgb::new(1.2f32, 0.3, 0.8); /// let c = Srgb::new(-0.6f32, 0.3, 0.8); /// /// assert!(a.is_within_bounds()); /// assert!(!b.is_within_bounds()); /// assert!(!c.is_within_bounds()); /// ``` /// /// `IsWithinBounds` is also implemented for `[T]`: /// /// ``` /// use palette::{Srgb, IsWithinBounds}; /// /// let my_vec = vec![Srgb::new(0.4f32, 0.3, 0.8), Srgb::new(0.8, 0.5, 0.1)]; /// let my_array = [Srgb::new(0.4f32, 0.3, 0.8), Srgb::new(1.3, 0.5, -3.0)]; /// let my_slice = &[Srgb::new(0.4f32, 0.3, 0.8), Srgb::new(1.2, 0.3, 0.8)]; /// /// assert!(my_vec.is_within_bounds()); /// assert!(!my_array.is_within_bounds()); /// assert!(!my_slice.is_within_bounds()); /// ``` pub trait IsWithinBounds: HasBoolMask { /// Check if the color's components are within the expected range bounds. /// /// ``` /// use palette::{Srgb, IsWithinBounds}; /// assert!(Srgb::new(0.8f32, 0.5, 0.2).is_within_bounds()); /// assert!(!Srgb::new(1.3f32, 0.5, -3.0).is_within_bounds()); /// ``` fn is_within_bounds(&self) -> Self::Mask; } impl IsWithinBounds for [T] where T: IsWithinBounds, T::Mask: BoolMask + BitAndAssign, { #[inline] fn is_within_bounds(&self) -> Self::Mask { let mut result = Self::Mask::from_bool(true); for item in self { result &= item.is_within_bounds(); if result.is_false() { break; } } result } } /// An operator for restricting a color's components to their expected ranges. /// /// [`IsWithinBounds`] can be used to check if the components are within their /// range bounds. /// /// See also [`ClampAssign`]. /// /// ``` /// use palette::{Srgb, IsWithinBounds, Clamp}; /// /// let unclamped = Srgb::new(1.3f32, 0.5, -3.0); /// assert!(!unclamped.is_within_bounds()); /// /// let clamped = unclamped.clamp(); /// assert!(clamped.is_within_bounds()); /// assert_eq!(clamped, Srgb::new(1.0, 0.5, 0.0)); /// ``` pub trait Clamp { /// Return a new color where out-of-bounds components have been changed to /// the nearest valid values. /// /// ``` /// use palette::{Srgb, Clamp}; /// assert_eq!(Srgb::new(1.3, 0.5, -3.0).clamp(), Srgb::new(1.0, 0.5, 0.0)); /// ``` #[must_use] fn clamp(self) -> Self; } /// An assigning operator for restricting a color's components to their expected /// ranges. /// /// [`IsWithinBounds`] can be used to check if the components are within their /// range bounds. /// /// See also [`Clamp`]. /// /// ``` /// use palette::{Srgb, IsWithinBounds, ClampAssign}; /// /// let mut color = Srgb::new(1.3f32, 0.5, -3.0); /// assert!(!color.is_within_bounds()); /// /// color.clamp_assign(); /// assert!(color.is_within_bounds()); /// assert_eq!(color, Srgb::new(1.0, 0.5, 0.0)); /// ``` /// /// `ClampAssign` is also implemented for `[T]`: /// /// ``` /// use palette::{Srgb, ClampAssign}; /// /// let mut my_vec = vec![Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.3, 0.5, -3.0)]; /// let mut my_array = [Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.3, 0.5, -3.0)]; /// let mut my_slice = &mut [Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.2, 0.3, 0.8)]; /// /// my_vec.clamp_assign(); /// my_array.clamp_assign(); /// my_slice.clamp_assign(); /// ``` pub trait ClampAssign { /// Changes out-of-bounds components to the nearest valid values. /// /// ``` /// use palette::{Srgb, ClampAssign}; /// /// let mut color = Srgb::new(1.3, 0.5, -3.0); /// color.clamp_assign(); /// assert_eq!(color, Srgb::new(1.0, 0.5, 0.0)); /// ``` fn clamp_assign(&mut self); } impl ClampAssign for [T] where T: ClampAssign, { #[inline] fn clamp_assign(&mut self) { self.iter_mut().for_each(T::clamp_assign); } } /// Linear color interpolation of two colors. /// /// See also [`MixAssign`]. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{LinSrgb, Mix}; /// /// let a = LinSrgb::new(0.0, 0.5, 1.0); /// let b = LinSrgb::new(1.0, 0.5, 0.0); /// /// assert_relative_eq!(a.mix(b, 0.0), a); /// assert_relative_eq!(a.mix(b, 0.5), LinSrgb::new(0.5, 0.5, 0.5)); /// assert_relative_eq!(a.mix(b, 1.0), b); /// ``` pub trait Mix { /// The type of the mixing factor. type Scalar; /// Mix the color with an other color, by `factor`. /// /// `factor` should be between `0.0` and `1.0`, where `0.0` will result in /// the same color as `self` and `1.0` will result in the same color as /// `other`. #[must_use] fn mix(self, other: Self, factor: Self::Scalar) -> Self; } /// Assigning linear color interpolation of two colors. /// /// See also [`Mix`]. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{LinSrgb, MixAssign}; /// /// let mut a = LinSrgb::new(0.0, 0.5, 1.0); /// let b = LinSrgb::new(1.0, 0.5, 0.0); /// /// a.mix_assign(b, 0.5); /// assert_relative_eq!(a, LinSrgb::new(0.5, 0.5, 0.5)); /// ``` pub trait MixAssign { /// The type of the mixing factor. type Scalar; /// Mix the color with an other color, by `factor`. /// /// `factor` should be between `0.0` and `1.0`, where `0.0` will result in /// the same color as `self` and `1.0` will result in the same color as /// `other`. fn mix_assign(&mut self, other: Self, factor: Self::Scalar); } /// Operators for lightening a color. /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, [`lighten`](Lighten::lighten), scales the lightness /// towards the maximum lightness value. This means that for a color with 50% /// lightness, if `lighten(0.5)` is applied to it, the color will scale halfway /// to the maximum value of 100% resulting in a new lightness value of 75%. /// /// The fixed or absolute function, [`lighten_fixed`](Lighten::lighten_fixed), /// increase the lightness value by an amount that is independent of the current /// lightness of the color. So for a color with 50% lightness, if /// `lighten_fixed(0.5)` is applied to it, the color will have 50% lightness /// added to its lightness value resulting in a new value of 100%. /// /// See also [`LightenAssign`], [`Darken`] and [`DarkenAssign`]. pub trait Lighten { /// The type of the lighten modifier. type Scalar; /// Scale the color towards the maximum lightness by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, Lighten}; /// /// let color = Hsl::new_srgb(0.0, 1.0, 0.5); /// assert_relative_eq!(color.lighten(0.5).lightness, 0.75); /// ``` #[must_use] fn lighten(self, factor: Self::Scalar) -> Self; /// Lighten the color by `amount`, a value ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, Lighten}; /// /// let color = Hsl::new_srgb(0.0, 1.0, 0.4); /// assert_relative_eq!(color.lighten_fixed(0.2).lightness, 0.6); /// ``` #[must_use] fn lighten_fixed(self, amount: Self::Scalar) -> Self; } /// Assigning operators for lightening a color. /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, [`lighten_assign`](LightenAssign::lighten_assign), /// scales the lightness towards the maximum lightness value. This means that /// for a color with 50% lightness, if `lighten_assign(0.5)` is applied to it, /// the color will scale halfway to the maximum value of 100% resulting in a new /// lightness value of 75%. /// /// The fixed or absolute function, /// [`lighten_fixed_assign`](LightenAssign::lighten_fixed_assign), increase the /// lightness value by an amount that is independent of the current lightness of /// the color. So for a color with 50% lightness, if `lighten_fixed_assign(0.5)` /// is applied to it, the color will have 50% lightness added to its lightness /// value resulting in a new value of 100%. /// /// `LightenAssign` is also implemented for `[T]`: /// /// ``` /// use palette::{Hsl, LightenAssign}; /// /// let mut my_vec = vec![Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_array = [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_slice = &mut [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(112.0, 0.5, 0.8)]; /// /// my_vec.lighten_assign(0.5); /// my_array.lighten_assign(0.5); /// my_slice.lighten_assign(0.5); /// ``` /// /// See also [`Lighten`], [`Darken`] and [`DarkenAssign`]. pub trait LightenAssign { /// The type of the lighten modifier. type Scalar; /// Scale the color towards the maximum lightness by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, LightenAssign}; /// /// let mut color = Hsl::new_srgb(0.0, 1.0, 0.5); /// color.lighten_assign(0.5); /// assert_relative_eq!(color.lightness, 0.75); /// ``` fn lighten_assign(&mut self, factor: Self::Scalar); /// Lighten the color by `amount`, a value ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, LightenAssign}; /// /// let mut color = Hsl::new_srgb(0.0, 1.0, 0.4); /// color.lighten_fixed_assign(0.2); /// assert_relative_eq!(color.lightness, 0.6); /// ``` fn lighten_fixed_assign(&mut self, amount: Self::Scalar); } impl LightenAssign for [T] where T: LightenAssign, T::Scalar: Clone, { type Scalar = T::Scalar; #[inline] fn lighten_assign(&mut self, factor: Self::Scalar) { for color in self { color.lighten_assign(factor.clone()); } } #[inline] fn lighten_fixed_assign(&mut self, amount: Self::Scalar) { for color in self { color.lighten_fixed_assign(amount.clone()); } } } /// Operators for darkening a color; /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, [`darken`](Darken::darken), scales the lightness /// towards the minimum lightness value. This means that for a color with 50% /// lightness, if `darken(0.5)` is applied to it, the color will scale halfway /// to the minimum value of 0% resulting in a new lightness value of 25%. /// /// The fixed or absolute function, [`darken_fixed`](Darken::darken_fixed), /// decreases the lightness value by an amount that is independent of the /// current lightness of the color. So for a color with 50% lightness, if /// `darken_fixed(0.5)` is applied to it, the color will have 50% lightness /// removed from its lightness value resulting in a new value of 0%. /// /// See also [`DarkenAssign`], [`Lighten`] and [`LightenAssign`]. pub trait Darken { /// The type of the darken modifier. type Scalar; /// Scale the color towards the minimum lightness by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, Darken}; /// /// let color = Hsv::new_srgb(0.0, 1.0, 0.5); /// assert_relative_eq!(color.darken(0.5).value, 0.25); /// ``` #[must_use] fn darken(self, factor: Self::Scalar) -> Self; /// Darken the color by `amount`, a value ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, Darken}; /// /// let color = Hsv::new_srgb(0.0, 1.0, 0.4); /// assert_relative_eq!(color.darken_fixed(0.2).value, 0.2); /// ``` #[must_use] fn darken_fixed(self, amount: Self::Scalar) -> Self; } impl Darken for T where T: Lighten, T::Scalar: Neg, { type Scalar = T::Scalar; #[inline] fn darken(self, factor: Self::Scalar) -> Self { self.lighten(-factor) } #[inline] fn darken_fixed(self, amount: Self::Scalar) -> Self { self.lighten_fixed(-amount) } } /// Assigning operators for darkening a color; /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, [`darken_assign`](DarkenAssign::darken_assign), /// scales the lightness towards the minimum lightness value. This means that /// for a color with 50% lightness, if `darken_assign(0.5)` is applied to it, /// the color will scale halfway to the minimum value of 0% resulting in a new /// lightness value of 25%. /// /// The fixed or absolute function, /// [`darken_fixed_assign`](DarkenAssign::darken_fixed_assign), decreases the /// lightness value by an amount that is independent of the current lightness of /// the color. So for a color with 50% lightness, if `darken_fixed_assign(0.5)` /// is applied to it, the color will have 50% lightness removed from its /// lightness value resulting in a new value of 0%. /// /// `DarkenAssign` is also implemented for `[T]`: /// /// ``` /// use palette::{Hsl, DarkenAssign}; /// /// let mut my_vec = vec![Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_array = [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_slice = &mut [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(112.0, 0.5, 0.8)]; /// /// my_vec.darken_assign(0.5); /// my_array.darken_assign(0.5); /// my_slice.darken_assign(0.5); /// ``` /// /// See also [`Darken`], [`Lighten`] and [`LightenAssign`]. pub trait DarkenAssign { /// The type of the darken modifier. type Scalar; /// Scale the color towards the minimum lightness by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, DarkenAssign}; /// /// let mut color = Hsv::new_srgb(0.0, 1.0, 0.5); /// color.darken_assign(0.5); /// assert_relative_eq!(color.value, 0.25); /// ``` fn darken_assign(&mut self, factor: Self::Scalar); /// Darken the color by `amount`, a value ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, DarkenAssign}; /// /// let mut color = Hsv::new_srgb(0.0, 1.0, 0.4); /// color.darken_fixed_assign(0.2); /// assert_relative_eq!(color.value, 0.2); /// ``` fn darken_fixed_assign(&mut self, amount: Self::Scalar); } impl DarkenAssign for T where T: LightenAssign + ?Sized, T::Scalar: Neg, { type Scalar = T::Scalar; #[inline] fn darken_assign(&mut self, factor: Self::Scalar) { self.lighten_assign(-factor); } #[inline] fn darken_fixed_assign(&mut self, amount: Self::Scalar) { self.lighten_fixed_assign(-amount); } } /// A trait for colors where a hue may be calculated. /// /// See also [`WithHue`], [`SetHue`], [`ShiftHue`] and [`ShiftHueAssign`]. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{GetHue, LinSrgb}; /// /// let red = LinSrgb::new(1.0f32, 0.0, 0.0); /// let green = LinSrgb::new(0.0f32, 1.0, 0.0); /// let blue = LinSrgb::new(0.0f32, 0.0, 1.0); /// let gray = LinSrgb::new(0.5f32, 0.5, 0.5); /// /// assert_relative_eq!(red.get_hue(), 0.0.into()); /// assert_relative_eq!(green.get_hue(), 120.0.into()); /// assert_relative_eq!(blue.get_hue(), 240.0.into()); /// assert_relative_eq!(gray.get_hue(), 0.0.into()); /// ``` pub trait GetHue { /// The kind of hue unit this color space uses. /// /// The hue is most commonly calculated as an angle around a color circle /// and may not always be uniform between color spaces. It's therefore not /// recommended to take one type of hue and apply it to a color space that /// expects an other. type Hue; /// Calculate a hue if possible. /// /// Colors in the gray scale has no well defined hue and should preferably /// return `0`. #[must_use] fn get_hue(&self) -> Self::Hue; } /// Change the hue of a color to a specific value. /// /// See also [`SetHue`], [`GetHue`], [`ShiftHue`] and [`ShiftHueAssign`]. /// /// ``` /// use palette::{Hsl, WithHue}; /// /// let green = Hsl::new_srgb(120.0, 1.0, 0.5); /// let blue = green.with_hue(240.0); /// assert_eq!(blue, Hsl::new_srgb(240.0, 1.0, 0.5)); /// ``` pub trait WithHue { /// Return a copy of `self` with a specific hue. #[must_use] fn with_hue(self, hue: H) -> Self; } /// Change the hue of a color to a specific value without moving. /// /// See also [`WithHue`], [`GetHue`], [`ShiftHue`] and [`ShiftHueAssign`]. /// /// ``` /// use palette::{Hsl, SetHue}; /// /// let mut color = Hsl::new_srgb(120.0, 1.0, 0.5); /// color.set_hue(240.0); /// assert_eq!(color, Hsl::new_srgb(240.0, 1.0, 0.5)); /// ``` /// /// `SetHue` is also implemented for `[T]`: /// /// ``` /// use palette::{Hsl, SetHue}; /// /// let mut my_vec = vec![Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_array = [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_slice = &mut [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(112.0, 0.5, 0.8)]; /// /// my_vec.set_hue(120.0); /// my_array.set_hue(120.0); /// my_slice.set_hue(120.0); /// ``` pub trait SetHue { /// Change the hue to a specific value. fn set_hue(&mut self, hue: H); } impl SetHue for [T] where T: SetHue, H: Clone, { fn set_hue(&mut self, hue: H) { for color in self { color.set_hue(hue.clone()); } } } /// Operator for increasing or decreasing the hue by an amount. /// /// See also [`ShiftHueAssign`], [`WithHue`], [`SetHue`] and [`GetHue`]. /// /// ``` /// use palette::{Hsl, ShiftHue}; /// /// let green = Hsl::new_srgb(120.0, 1.0, 0.5); /// let blue = green.shift_hue(120.0); /// assert_eq!(blue, Hsl::new_srgb(240.0, 1.0, 0.5)); /// ``` pub trait ShiftHue { /// The type of the hue modifier. type Scalar; /// Return a copy of `self` with the hue shifted by `amount`. #[must_use] fn shift_hue(self, amount: Self::Scalar) -> Self; } /// Assigning operator for increasing or decreasing the hue by an amount. /// /// See also [`ShiftHue`], [`WithHue`], [`SetHue`] and [`GetHue`]. /// /// ``` /// use palette::{Hsl, ShiftHueAssign}; /// /// let mut color = Hsl::new_srgb(120.0, 1.0, 0.5); /// color.shift_hue_assign(120.0); /// assert_eq!(color, Hsl::new_srgb(240.0, 1.0, 0.5)); /// ``` /// /// `ShiftHueAssign` is also implemented for `[T]`: /// /// ``` /// use palette::{Hsl, ShiftHueAssign}; /// /// let mut my_vec = vec![Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_array = [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_slice = &mut [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(112.0, 0.5, 0.8)]; /// /// my_vec.shift_hue_assign(120.0); /// my_array.shift_hue_assign(120.0); /// my_slice.shift_hue_assign(120.0); /// ``` pub trait ShiftHueAssign { /// The type of the hue modifier. type Scalar; /// Shifts the hue by `amount`. fn shift_hue_assign(&mut self, amount: Self::Scalar); } impl ShiftHueAssign for [T] where T: ShiftHueAssign, T::Scalar: Clone, { type Scalar = T::Scalar; fn shift_hue_assign(&mut self, amount: Self::Scalar) { for color in self { color.shift_hue_assign(amount.clone()); } } } /// Operator for increasing the saturation (or chroma) of a color. /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, [`saturate`](Saturate::saturate), scales the /// saturation towards the maximum saturation value. This means that for a color /// with 50% saturation, if `saturate(0.5)` is applied to it, the color will /// scale halfway to the maximum value of 100% resulting in a new saturation /// value of 75%. /// /// The fixed or absolute function, /// [`saturate_fixed`](Saturate::saturate_fixed), increases the saturation by an /// amount that is independent of the current saturation of the color. So for a /// color with 50% saturation, if `saturate_fixed(0.5)` is applied to it, the /// color will have 50% saturation added to its saturation value resulting in a /// new value of 100%. /// /// See also [`SaturateAssign`], [`Desaturate`] and [`DesaturateAssign`]. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, Saturate}; /// /// let a = Hsv::new_srgb(0.0, 0.5, 1.0); /// /// assert_relative_eq!(a.saturate(0.5).saturation, 0.75); /// assert_relative_eq!(a.saturate_fixed(0.5).saturation, 1.0); /// ``` pub trait Saturate { /// The type of the saturation modifier. type Scalar; /// Scale the color towards the maximum saturation by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, Saturate}; /// /// let color = Hsl::new_srgb(0.0, 0.5, 0.5); /// assert_relative_eq!(color.saturate(0.5).saturation, 0.75); /// ``` #[must_use] fn saturate(self, factor: Self::Scalar) -> Self; /// Increase the saturation by `amount`, a value ranging from `0.0` to /// `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, Saturate}; /// /// let color = Hsl::new_srgb(0.0, 0.4, 0.5); /// assert_relative_eq!(color.saturate_fixed(0.2).saturation, 0.6); /// ``` #[must_use] fn saturate_fixed(self, amount: Self::Scalar) -> Self; } /// Assigning operator for increasing the saturation (or chroma) of a color. /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, [`saturate_assign`](SaturateAssign::saturate_assign), /// scales the saturation towards the maximum saturation value. This means that /// for a color with 50% saturation, if `saturate_assign(0.5)` is applied to it, /// the color will scale halfway to the maximum value of 100% resulting in a new /// saturation value of 75%. /// /// The fixed or absolute function, /// [`saturate_fixed_assign`](SaturateAssign::saturate_fixed_assign), increases /// the saturation by an amount that is independent of the current saturation of /// the color. So for a color with 50% saturation, if /// `saturate_fixed_assign(0.5)` is applied to it, the color will have 50% /// saturation added to its saturation value resulting in a new value of 100%. /// /// See also [`Saturate`], [`Desaturate`] and [`DesaturateAssign`]. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, SaturateAssign}; /// /// let mut relative = Hsv::new_srgb(0.0, 0.5, 1.0); /// relative.saturate_assign(0.5); /// /// let mut fixed = Hsv::new_srgb(0.0, 0.5, 1.0); /// fixed.saturate_fixed_assign(0.5); /// /// assert_relative_eq!(relative.saturation, 0.75); /// assert_relative_eq!(fixed.saturation, 1.0); /// ``` /// /// `SaturateAssign` is also implemented for `[T]`: /// /// ``` /// use palette::{Hsl, SaturateAssign}; /// /// let mut my_vec = vec![Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_array = [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_slice = &mut [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(112.0, 0.5, 0.8)]; /// /// my_vec.saturate_assign(0.5); /// my_array.saturate_assign(0.5); /// my_slice.saturate_assign(0.5); /// ``` pub trait SaturateAssign { /// The type of the saturation modifier. type Scalar; /// Scale the color towards the maximum saturation by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, SaturateAssign}; /// /// let mut color = Hsl::new_srgb(0.0, 0.5, 0.5); /// color.saturate_assign(0.5); /// assert_relative_eq!(color.saturation, 0.75); /// ``` fn saturate_assign(&mut self, factor: Self::Scalar); /// Increase the saturation by `amount`, a value ranging from `0.0` to /// `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsl, SaturateAssign}; /// /// let mut color = Hsl::new_srgb(0.0, 0.4, 0.5); /// color.saturate_fixed_assign(0.2); /// assert_relative_eq!(color.saturation, 0.6); /// ``` fn saturate_fixed_assign(&mut self, amount: Self::Scalar); } impl SaturateAssign for [T] where T: SaturateAssign, T::Scalar: Clone, { type Scalar = T::Scalar; fn saturate_assign(&mut self, factor: Self::Scalar) { for color in self { color.saturate_assign(factor.clone()); } } fn saturate_fixed_assign(&mut self, amount: Self::Scalar) { for color in self { color.saturate_fixed_assign(amount.clone()); } } } /// Operator for decreasing the saturation (or chroma) of a color. /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, [`desaturate`](Desaturate::desaturate), scales the /// saturation towards the minimum saturation value. This means that for a color /// with 50% saturation, if `desaturate(0.5)` is applied to it, the color will /// scale halfway to the minimum value of 0% resulting in a new saturation value /// of 25%. /// /// The fixed or absolute function, /// [`desaturate_fixed`](Desaturate::desaturate_fixed), decreases the saturation /// by an amount that is independent of the current saturation of the color. So /// for a color with 50% saturation, if `desaturate_fixed(0.5)` is applied to /// it, the color will have 50% saturation removed from its saturation value /// resulting in a new value of 0%. /// /// See also [`DesaturateAssign`], [`Saturate`] and [`SaturateAssign`]. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, Desaturate}; /// /// let a = Hsv::new_srgb(0.0, 0.5, 1.0); /// /// assert_relative_eq!(a.desaturate(0.5).saturation, 0.25); /// assert_relative_eq!(a.desaturate_fixed(0.5).saturation, 0.0); /// ``` pub trait Desaturate { /// The type of the desaturation modifier. type Scalar; /// Scale the color towards the minimum saturation by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, Desaturate}; /// /// let color = Hsv::new_srgb(0.0, 0.5, 0.5); /// assert_relative_eq!(color.desaturate(0.5).saturation, 0.25); /// ``` #[must_use] fn desaturate(self, factor: Self::Scalar) -> Self; /// Increase the saturation by `amount`, a value ranging from `0.0` to /// `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, Desaturate}; /// /// let color = Hsv::new_srgb(0.0, 0.4, 0.5); /// assert_relative_eq!(color.desaturate_fixed(0.2).saturation, 0.2); /// ``` #[must_use] fn desaturate_fixed(self, amount: Self::Scalar) -> Self; } impl Desaturate for T where T: Saturate, T::Scalar: Neg, { type Scalar = T::Scalar; #[inline] fn desaturate(self, factor: Self::Scalar) -> Self { self.saturate(-factor) } #[inline] fn desaturate_fixed(self, amount: Self::Scalar) -> Self { self.saturate_fixed(-amount) } } /// Assigning operator for decreasing the saturation (or chroma) of a color. /// /// The trait's functions are split into two groups of functions: relative and /// fixed/absolute. /// /// The relative function, /// [`desaturate_assign`](DesaturateAssign::desaturate_assign), scales the /// saturation towards the minimum saturation value. This means that for a color /// with 50% saturation, if `desaturate_assign(0.5)` is applied to it, the color /// will scale halfway to the minimum value of 0% resulting in a new saturation /// value of 25%. /// /// The fixed or absolute function, /// [`desaturate_fixed_assign`](DesaturateAssign::desaturate_fixed_assign), /// decreases the saturation by an amount that is independent of the current /// saturation of the color. So for a color with 50% saturation, if /// `desaturate_fixed_assign(0.5)` is applied to it, the color will have 50% /// saturation removed from its saturation value resulting in a new value of 0%. /// /// See also [`Desaturate`], [`Saturate`] and [`SaturateAssign`]. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, DesaturateAssign}; /// /// let mut relative = Hsv::new_srgb(0.0, 0.5, 1.0); /// relative.desaturate_assign(0.5); /// /// let mut fixed = Hsv::new_srgb(0.0, 0.5, 1.0); /// fixed.desaturate_fixed_assign(0.5); /// /// assert_relative_eq!(relative.saturation, 0.25); /// assert_relative_eq!(fixed.saturation, 0.0); /// ``` /// /// `DesaturateAssign` is also implemented for `[T]`: /// /// ``` /// use palette::{Hsl, DesaturateAssign}; /// /// let mut my_vec = vec![Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_array = [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(113.0, 0.5, 0.8)]; /// let mut my_slice = &mut [Hsl::new_srgb(104.0, 0.3, 0.8), Hsl::new_srgb(112.0, 0.5, 0.8)]; /// /// my_vec.desaturate_assign(0.5); /// my_array.desaturate_assign(0.5); /// my_slice.desaturate_assign(0.5); /// ``` pub trait DesaturateAssign { /// The type of the desaturation modifier. type Scalar; /// Scale the color towards the minimum saturation by `factor`, a value /// ranging from `0.0` to `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, DesaturateAssign}; /// /// let mut color = Hsv::new_srgb(0.0, 0.5, 0.5); /// color.desaturate_assign(0.5); /// assert_relative_eq!(color.saturation, 0.25); /// ``` fn desaturate_assign(&mut self, factor: Self::Scalar); /// Increase the saturation by `amount`, a value ranging from `0.0` to /// `1.0`. /// /// ``` /// use approx::assert_relative_eq; /// use palette::{Hsv, DesaturateAssign}; /// /// let mut color = Hsv::new_srgb(0.0, 0.4, 0.5); /// color.desaturate_fixed_assign(0.2); /// assert_relative_eq!(color.saturation, 0.2); /// ``` fn desaturate_fixed_assign(&mut self, amount: Self::Scalar); } impl DesaturateAssign for T where T: SaturateAssign + ?Sized, T::Scalar: Neg, { type Scalar = T::Scalar; #[inline] fn desaturate_assign(&mut self, factor: Self::Scalar) { self.saturate_assign(-factor); } #[inline] fn desaturate_fixed_assign(&mut self, amount: Self::Scalar) { self.saturate_fixed_assign(-amount); } } /// Extension trait for fixed size arrays. /// /// ## Safety /// /// * `Item` must be the type of the array's items (eg: `T` in `[T; N]`). /// * `LENGTH` must be the length of the array (eg: `N` in `[T; N]`). pub unsafe trait ArrayExt { /// The type of the array's items. type Item; /// The number of items in the array. const LENGTH: usize; } unsafe impl ArrayExt for [T; N] { type Item = T; const LENGTH: usize = N; } /// Temporary helper trait for getting an array type of size `N + 1`. /// /// ## Safety /// /// * `Next` must have the same item type as `Self`. /// * `Next` must be one item longer than `Self`. pub unsafe trait NextArray { /// An array of size `N + 1`. type Next: ArrayExt; } macro_rules! impl_next_array { ($length: expr) => {}; ($length: expr, $next_length: expr $(, $rest: expr)*) => { unsafe impl NextArray for [T; $length] { type Next = [T; $next_length]; } impl_next_array!($next_length $(, $rest)*); }; } impl_next_array!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17); #[cfg(doctest)] macro_rules! doctest { ($str: expr, $name: ident) => { #[doc = $str] mod $name {} }; } // Makes doctest run tests on README.md. #[cfg(doctest)] doctest!(include_str!("../README.md"), readme); palette-0.7.5/src/luma/channels.rs000064400000000000000000000017671046102023000151740ustar 00000000000000//! Channel orders for packed Luma types. use crate::{cast::ComponentOrder, luma}; /// Luma+Alpha color packed in LA order. /// /// See [Packed](crate::cast::Packed) for more details. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct La; impl ComponentOrder, [T; 2]> for La { #[inline] fn pack(color: luma::Lumaa) -> [T; 2] { color.into() } #[inline] fn unpack(packed: [T; 2]) -> luma::Lumaa { packed.into() } } /// Luma+Alpha color packed in AL order. /// /// See [Packed](crate::cast::Packed) for more details. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Al; impl ComponentOrder, [T; 2]> for Al { #[inline] fn pack(color: luma::Lumaa) -> [T; 2] { let [luma, alpha]: [T; 2] = color.into(); [alpha, luma] } #[inline] fn unpack(packed: [T; 2]) -> luma::Lumaa { let [alpha, luma] = packed; luma::Lumaa::new(luma, alpha) } } palette-0.7.5/src/luma/luma.rs000064400000000000000000000622531046102023000143340ustar 00000000000000use core::{ any::TypeId, convert::TryInto, fmt, marker::PhantomData, ops::{Add, Div}, }; use crate::{ bool_mask::{HasBoolMask, LazySelect}, cast::{ComponentOrder, Packed, UintCast}, color_difference::Wcag21RelativeContrast, convert::FromColorUnclamped, encoding::{FromLinear, IntoLinear, Linear, Srgb}, luma::LumaStandard, num::{Arithmetics, MinMax, PartialCmp, Real}, stimulus::{FromStimulus, Stimulus, StimulusColor}, white_point::D65, Alpha, IntoColor, Xyz, Yxy, }; /// Luminance with an alpha component. See the [`Lumaa` implementation /// in `Alpha`](crate::Alpha#Lumaa). pub type Lumaa = Alpha, T>; /// Luminance. /// /// Luma is a purely gray scale color space, which is included more for /// completeness than anything else, and represents how bright a color is /// perceived to be. It's basically the `Y` component of [CIE /// XYZ](crate::Xyz). The lack of any form of hue representation limits /// the set of operations that can be performed on it. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, luma_standard = "S", component = "T", skip_derives(Xyz, Yxy, Luma) )] #[repr(C)] #[doc(alias = "gray")] #[doc(alias = "grey")] pub struct Luma { /// The lightness of the color. 0.0 is black and 1.0 is white. pub luma: T, /// The kind of RGB standard. sRGB is the default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub standard: PhantomData, } impl Luma { /// Create a luminance color. pub const fn new(luma: T) -> Luma { Luma { luma, standard: PhantomData, } } /// Convert into another component type. pub fn into_format(self) -> Luma where U: FromStimulus, { Luma { luma: U::from_stimulus(self.luma), standard: PhantomData, } } /// Convert from another component type. pub fn from_format(color: Luma) -> Self where T: FromStimulus, { color.into_format() } /// Convert to a `(luma,)` tuple. pub fn into_components(self) -> (T,) { (self.luma,) } /// Convert from a `(luma,)` tuple. pub fn from_components((luma,): (T,)) -> Self { Self::new(luma) } fn reinterpret_as(self) -> Luma where S: LumaStandard, S2: LumaStandard, { Luma { luma: self.luma, standard: PhantomData, } } } impl Luma where T: Stimulus, { /// Return the `luma` value minimum. pub fn min_luma() -> T { T::zero() } /// Return the `luma` value maximum. pub fn max_luma() -> T { T::max_intensity() } } impl Luma { /// Convert to a packed `u16` with with specifiable component order. /// /// ``` /// use palette::{luma, SrgbLuma}; /// /// let integer = SrgbLuma::new(96u8).into_u16::(); /// assert_eq!(0x60FF, integer); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xAALL` component order: /// /// ``` /// use palette::SrgbLuma; /// /// let integer = u16::from(SrgbLuma::new(96u8)); /// assert_eq!(0xFF60, integer); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn into_u16(self) -> u16 where O: ComponentOrder, u16>, { O::pack(Lumaa::from(self)) } /// Convert from a packed `u16` with specifiable component order. /// /// ``` /// use palette::{luma, SrgbLuma}; /// /// let luma = SrgbLuma::from_u16::(0x60FF); /// assert_eq!(SrgbLuma::new(96u8), luma); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xAALL` component order: /// /// ``` /// use palette::SrgbLuma; /// /// let luma = SrgbLuma::from(0x60u16); /// assert_eq!(SrgbLuma::new(96u8), luma); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn from_u16(color: u16) -> Self where O: ComponentOrder, u16>, { O::unpack(color).color } } impl Luma where S: LumaStandard, { /// Convert the color to linear luminance. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLuma, LinLuma}; /// /// let linear: LinLuma<_, f32> = SrgbLuma::new(96u8).into_linear(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn into_linear(self) -> Luma, U> where S::TransferFn: IntoLinear, { Luma::new(S::TransferFn::into_linear(self.luma)) } /// Convert linear luminance to non-linear luminance. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLuma, LinLuma}; /// /// let encoded = SrgbLuma::::from_linear(LinLuma::new(0.95f32)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn from_linear(color: Luma, U>) -> Luma where S::TransferFn: FromLinear, { Luma::new(S::TransferFn::from_linear(color.luma)) } } impl Luma, T> { /// Convert a linear color to a different encoding. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLuma, LinLuma}; /// /// let encoded: SrgbLuma = LinLuma::new(0.95f32).into_encoding(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn into_encoding(self) -> Luma where St: LumaStandard, St::TransferFn: FromLinear, { Luma::::from_linear(self) } /// Convert from linear luminance from a different encoding. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLuma, LinLuma}; /// /// let linear = LinLuma::<_, f32>::from_encoding(SrgbLuma::new(96u8)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn from_encoding(color: Luma) -> Self where St: LumaStandard, St::TransferFn: IntoLinear, { color.into_linear() } } // Safety: // // Luma is a transparent wrapper around its component, which fulfills the // requirements of UintCast. unsafe impl UintCast for Luma { type Uint = u8; } // Safety: // // Luma is a transparent wrapper around its component, which fulfills the // requirements of UintCast. unsafe impl UintCast for Luma { type Uint = u16; } // Safety: // // Luma is a transparent wrapper around its component, which fulfills the // requirements of UintCast. unsafe impl UintCast for Luma { type Uint = u32; } // Safety: // // Luma is a transparent wrapper around its component, which fulfills the // requirements of UintCast. unsafe impl UintCast for Luma { type Uint = u64; } // Safety: // // Luma is a transparent wrapper around its component, which fulfills the // requirements of UintCast. unsafe impl UintCast for Luma { type Uint = u128; } ///[`Lumaa`](crate::luma::Lumaa) implementations. impl Alpha, A> { /// Create a luminance color with transparency. pub const fn new(luma: T, alpha: A) -> Self { Alpha { color: Luma::new(luma), alpha, } } /// Convert into another component type. pub fn into_format(self) -> Alpha, B> where U: FromStimulus, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert from another component type. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus, A: FromStimulus, { color.into_format() } /// Convert to a `(luma, alpha)` tuple. pub fn into_components(self) -> (T, A) { (self.color.luma, self.alpha) } /// Convert from a `(luma, alpha)` tuple. pub fn from_components((luma, alpha): (T, A)) -> Self { Self::new(luma, alpha) } } impl Lumaa { /// Convert to a packed `u16` with with a specific component order. /// /// ``` /// use palette::{luma, SrgbLumaa}; /// /// let integer = SrgbLumaa::new(96u8, 255).into_u16::(); /// assert_eq!(0xFF60, integer); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xLLAA` component order: /// /// ``` /// use palette::SrgbLumaa; /// /// let integer = u16::from(SrgbLumaa::new(96u8, 255)); /// assert_eq!(0x60FF, integer); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn into_u16(self) -> u16 where O: ComponentOrder, u16>, { O::pack(self) } /// Convert from a packed `u16` with a specific component order. /// /// ``` /// use palette::{luma, SrgbLumaa}; /// /// let luma = SrgbLumaa::from_u16::(0xFF60); /// assert_eq!(SrgbLumaa::new(96u8, 255), luma); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xLLAA` component order: /// /// ``` /// use palette::SrgbLumaa; /// /// let luma = SrgbLumaa::from(0x60FF); /// assert_eq!(SrgbLumaa::new(96u8, 255), luma); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn from_u16(color: u16) -> Self where O: ComponentOrder, u16>, { O::unpack(color) } } impl Alpha, A> where S: LumaStandard, { /// Convert the color to linear luminance with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLumaa, LinLumaa}; /// /// let linear: LinLumaa<_, f32> = SrgbLumaa::new(96u8, 38).into_linear(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn into_linear(self) -> Alpha, U>, B> where S::TransferFn: IntoLinear, B: FromStimulus, { Alpha { color: self.color.into_linear(), alpha: B::from_stimulus(self.alpha), } } /// Convert linear luminance to non-linear luminance with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLumaa, LinLumaa}; /// /// let encoded = SrgbLumaa::::from_linear(LinLumaa::new(0.95f32, 0.75)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn from_linear(color: Alpha, U>, B>) -> Self where S::TransferFn: FromLinear, A: FromStimulus, { Alpha { color: Luma::from_linear(color.color), alpha: A::from_stimulus(color.alpha), } } } impl Alpha, T>, A> { /// Convert a linear color to a different encoding with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLumaa, LinLumaa}; /// /// let encoded: SrgbLumaa = LinLumaa::new(0.95f32, 0.75).into_encoding(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn into_encoding(self) -> Alpha, B> where St: LumaStandard, St::TransferFn: FromLinear, B: FromStimulus, { Alpha::, B>::from_linear(self) } /// Convert to linear luminance from a different encoding with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Luma::into_format]. /// /// ``` /// use palette::{SrgbLumaa, LinLumaa}; /// /// let linear = LinLumaa::<_, f32>::from_encoding(SrgbLumaa::new(96u8, 38)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn from_encoding(color: Alpha, B>) -> Self where St: LumaStandard, St::TransferFn: IntoLinear, A: FromStimulus, { color.into_linear() } } impl_reference_component_methods!(Luma, [luma], standard); impl_struct_of_arrays_methods!(Luma, [luma], standard); impl FromColorUnclamped> for Luma where S1: LumaStandard + 'static, S2: LumaStandard + 'static, S1::TransferFn: FromLinear, S2::TransferFn: IntoLinear, { fn from_color_unclamped(color: Luma) -> Self { if TypeId::of::() == TypeId::of::() { color.reinterpret_as() } else { Self::from_linear(color.into_linear().reinterpret_as()) } } } impl FromColorUnclamped> for Luma where S: LumaStandard, S::TransferFn: FromLinear, { fn from_color_unclamped(color: Xyz) -> Self { Self::from_linear(Luma { luma: color.y, standard: PhantomData, }) } } impl FromColorUnclamped> for Luma where S: LumaStandard, S::TransferFn: FromLinear, { fn from_color_unclamped(color: Yxy) -> Self { Self::from_linear(Luma { luma: color.luma, standard: PhantomData, }) } } impl_tuple_conversion!(Luma as (T)); impl_is_within_bounds! { Luma { luma => [Self::min_luma(), Self::max_luma()] } where T: Stimulus } impl_clamp! { Luma { luma => [Self::min_luma(), Self::max_luma()] } other {standard} where T: Stimulus } impl_mix!(Luma); impl_lighten!(Luma increase {luma => [Self::min_luma(), Self::max_luma()]} other {} phantom: standard where T: Stimulus); impl_premultiply!(Luma {luma} phantom: standard); impl_euclidean_distance!(Luma {luma}); impl StimulusColor for Luma where T: Stimulus {} impl HasBoolMask for Luma where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Luma where T: Stimulus, { fn default() -> Luma { Luma::new(Self::min_luma()) } } impl_color_add!(Luma, [luma], standard); impl_color_sub!(Luma, [luma], standard); impl_color_mul!(Luma, [luma], standard); impl_color_div!(Luma, [luma], standard); impl_array_casts!(Luma, [T; 1]); impl AsRef for Luma { #[inline] fn as_ref(&self) -> &T { &self.luma } } impl AsMut for Luma { #[inline] fn as_mut(&mut self) -> &mut T { &mut self.luma } } impl From for Luma { #[inline] fn from(luma: T) -> Self { Self::new(luma) } } macro_rules! impl_luma_cast_other { ($($other: ty),+) => { $( impl<'a, S> From<&'a $other> for &'a Luma where $other: AsRef>, { #[inline] fn from(luma: &'a $other) -> Self { luma.as_ref() } } impl<'a, S> From<&'a mut $other> for &'a mut Luma where $other: AsMut>, { #[inline] fn from(luma: &'a mut $other) -> Self { luma.as_mut() } } impl AsRef> for $other { #[inline] fn as_ref(&self) -> &Luma { core::slice::from_ref(self).try_into().unwrap() } } impl AsMut> for $other { #[inline] fn as_mut(&mut self) -> &mut Luma { core::slice::from_mut(self).try_into().unwrap() } } impl From> for $other { #[inline] fn from(color: Luma) -> Self { color.luma } } impl<'a, S> From<&'a Luma> for &'a $other { #[inline] fn from(color: &'a Luma) -> Self { color.as_ref() } } impl<'a, S> From<&'a mut Luma> for &'a mut $other { #[inline] fn from(color: &'a mut Luma) -> Self { color.as_mut() } } )+ }; } impl_luma_cast_other!(u8, u16, u32, u64, u128, f32, f64); impl From> for Packed where O: ComponentOrder, P>, Lumaa: From>, { #[inline] fn from(color: Luma) -> Self { Self::from(Lumaa::from(color)) } } impl From> for Packed where O: ComponentOrder, P>, { #[inline] fn from(color: Lumaa) -> Self { Packed::pack(color) } } impl From> for Luma where O: ComponentOrder, P>, { #[inline] fn from(packed: Packed) -> Self { Lumaa::from(packed).color } } impl From> for Lumaa where O: ComponentOrder, P>, { #[inline] fn from(packed: Packed) -> Self { packed.unpack() } } impl From for Luma { #[inline] fn from(color: u16) -> Self { Self::from_u16::(color) } } impl From for Lumaa { #[inline] fn from(color: u16) -> Self { Self::from_u16::(color) } } impl From> for u16 { #[inline] fn from(color: Luma) -> Self { Luma::into_u16::(color) } } impl From> for u16 { #[inline] fn from(color: Lumaa) -> Self { Lumaa::into_u16::(color) } } impl_simd_array_conversion!(Luma, [luma], standard); impl_struct_of_array_traits!(Luma, [luma], standard); impl_copy_clone!(Luma, [luma], standard); impl_eq!(Luma, [luma]); impl fmt::LowerHex for Luma where T: fmt::LowerHex, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let size = f.width().unwrap_or(::core::mem::size_of::() * 2); write!(f, "{:0width$x}", self.luma, width = size) } } impl fmt::UpperHex for Luma where T: fmt::UpperHex, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let size = f.width().unwrap_or(::core::mem::size_of::() * 2); write!(f, "{:0width$X}", self.luma, width = size) } } #[allow(deprecated)] impl crate::RelativeContrast for Luma where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, S: LumaStandard, S::TransferFn: IntoLinear, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let luma1 = self.into_linear(); let luma2 = other.into_linear(); crate::contrast_ratio(luma1.luma, luma2.luma) } } impl Wcag21RelativeContrast for Luma where Self: IntoColor, T>>, S: LumaStandard, T: Real + Add + Div + PartialCmp + MinMax, { type Scalar = T; fn relative_luminance(self) -> Luma, Self::Scalar> { self.into_color() } } impl_rand_traits_cartesian!(UniformLuma, Luma {luma} phantom: standard: PhantomData); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Luma where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Luma where T: bytemuck::Pod {} #[cfg(test)] mod test { use crate::encoding::Srgb; use crate::Luma; test_convert_into_from_xyz!(Luma); #[test] fn ranges() { assert_ranges! { Luma; clamped { luma: 0.0 => 1.0 } clamped_min {} unclamped {} } } raw_pixel_conversion_tests!(Luma: luma); #[test] fn lower_hex() { assert_eq!(format!("{:x}", Luma::::new(161)), "a1"); } #[test] fn lower_hex_small_numbers() { assert_eq!(format!("{:x}", Luma::::new(1)), "01"); assert_eq!(format!("{:x}", Luma::::new(1)), "0001"); assert_eq!(format!("{:x}", Luma::::new(1)), "00000001"); assert_eq!( format!("{:x}", Luma::::new(1)), "0000000000000001" ); } #[test] fn lower_hex_custom_width() { assert_eq!(format!("{:03x}", Luma::::new(1)), "001"); assert_eq!(format!("{:03x}", Luma::::new(1)), "001"); assert_eq!(format!("{:03x}", Luma::::new(1)), "001"); assert_eq!(format!("{:03x}", Luma::::new(1)), "001"); } #[test] fn upper_hex() { assert_eq!(format!("{:X}", Luma::::new(161)), "A1"); } #[test] fn upper_hex_small_numbers() { assert_eq!(format!("{:X}", Luma::::new(1)), "01"); assert_eq!(format!("{:X}", Luma::::new(1)), "0001"); assert_eq!(format!("{:X}", Luma::::new(1)), "00000001"); assert_eq!( format!("{:X}", Luma::::new(1)), "0000000000000001" ); } #[test] fn upper_hex_custom_width() { assert_eq!(format!("{:03X}", Luma::::new(1)), "001"); assert_eq!(format!("{:03X}", Luma::::new(1)), "001"); assert_eq!(format!("{:03X}", Luma::::new(1)), "001"); assert_eq!(format!("{:03X}", Luma::::new(1)), "001"); } #[test] fn check_min_max_components() { assert_eq!(Luma::::min_luma(), 0.0); assert_eq!(Luma::::max_luma(), 1.0); } struct_of_arrays_tests!( Luma[luma] phantom: standard, super::Lumaa::new(0.1f32, 0.4), super::Lumaa::new(0.2, 0.5), super::Lumaa::new(0.3, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Luma::::new(0.3)).unwrap(); assert_eq!(serialized, r#"{"luma":0.3}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Luma = ::serde_json::from_str(r#"{"luma":0.3}"#).unwrap(); assert_eq!(deserialized, Luma::::new(0.3)); } test_uniform_distribution! { Luma { luma: (0.0, 1.0) }, min: Luma::new(0.0f32), max: Luma::new(1.0) } } palette-0.7.5/src/luma.rs000064400000000000000000000025731046102023000133750ustar 00000000000000//! Types for luma and luminance (grayscale) values. pub mod channels; #[allow(clippy::module_inception)] mod luma; use crate::encoding::{Gamma, Linear, Srgb}; use crate::white_point::D65; pub use self::luma::{Iter, Luma, Lumaa}; /// sRGB encoded luminance. pub type SrgbLuma = Luma; /// sRGB encoded luminance with an alpha component. pub type SrgbLumaa = Lumaa; /// Linear luminance. #[doc(alias = "linear")] pub type LinLuma = Luma, T>; /// Linear luminance with an alpha component. #[doc(alias = "linear")] pub type LinLumaa = Lumaa, T>; /// Gamma 2.2 encoded luminance. pub type GammaLuma = Luma, T>; /// Gamma 2.2 encoded luminance with an alpha component. pub type GammaLumaa = Lumaa, T>; /// A white point and a transfer function. pub trait LumaStandard { /// The white point of the color space. type WhitePoint; /// The transfer function for the luminance component. type TransferFn; } impl LumaStandard for (Wp, Tf) { type WhitePoint = Wp; type TransferFn = Tf; } /// A packed representation of Luma+Alpha in LA order. pub type PackedLumaa

= crate::cast::Packed; /// A packed representation of Luma+Alpha in AL order. pub type PackedAluma

= crate::cast::Packed; /// A packed representation of RGBA in ARGB order. pub type PackedArgb

= crate::cast::Packed; /// A packed representation of RGBA in BGRA order. pub type PackedBgra

= crate::cast::Packed; /// A packed representation of RGBA in ABGR order. pub type PackedAbgr

= crate::cast::Packed; palette-0.7.5/src/serde/alpha_deserializer.rs000064400000000000000000000500461046102023000173660ustar 00000000000000use core::marker::PhantomData; use serde::{ de::{DeserializeSeed, MapAccess, Visitor}, Deserialize, Deserializer, }; /// Deserializes a color with an attached alpha value. The alpha value is /// expected to be found alongside the other values in a flattened structure. pub(crate) struct AlphaDeserializer<'a, D, A> { pub inner: D, pub alpha: &'a mut Option, } impl<'de, 'a, D, A> Deserializer<'de> for AlphaDeserializer<'a, D, A> where D: Deserializer<'de>, A: Deserialize<'de>, { type Error = D::Error; fn deserialize_seq(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_seq(AlphaSeqVisitor { inner: visitor, alpha: self.alpha, }) } fn deserialize_tuple(self, len: usize, visitor: V) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_tuple( len + 1, AlphaMapVisitor { inner: visitor, alpha: self.alpha, field_count: Some(len), }, ) } fn deserialize_tuple_struct( self, name: &'static str, len: usize, visitor: V, ) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_tuple_struct( name, len + 1, AlphaMapVisitor { inner: visitor, alpha: self.alpha, field_count: Some(len), }, ) } fn deserialize_map(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_map(AlphaMapVisitor { inner: visitor, alpha: self.alpha, field_count: None, }) } fn deserialize_struct( self, name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_struct( name, fields, // We can't add to the expected fields so we just hope it works anyway. AlphaMapVisitor { inner: visitor, alpha: self.alpha, field_count: Some(fields.len()), }, ) } fn deserialize_ignored_any(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_ignored_any(AlphaSeqVisitor { inner: visitor, alpha: self.alpha, }) } fn deserialize_unit(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_tuple( 1, AlphaMapVisitor { inner: visitor, alpha: self.alpha, field_count: None, }, ) } fn deserialize_unit_struct( self, name: &'static str, visitor: V, ) -> Result where V: serde::de::Visitor<'de>, { self.inner.deserialize_newtype_struct( name, AlphaMapVisitor { inner: visitor, alpha: self.alpha, field_count: Some(0), }, ) } fn deserialize_newtype_struct( self, name: &'static str, visitor: V, ) -> Result where V: serde::de::Visitor<'de>, { self.deserialize_tuple_struct(name, 1, visitor) } // Unsupported methods: fn deserialize_any(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_bool(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_i8(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_i16(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_i32(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_i64(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_u8(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_u16(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_u32(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_u64(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_f32(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_f64(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_char(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_str(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_string(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_bytes(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_byte_buf(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_option(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_enum( self, _name: &'static str, _variants: &'static [&'static str], _visitor: V, ) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } fn deserialize_identifier(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { alpha_deserializer_error() } } fn alpha_deserializer_error() -> ! { unimplemented!("AlphaDeserializer can only deserialize structs, maps and sequences") } /// Deserializes a sequence with the alpha value last. struct AlphaSeqVisitor<'a, D, A> { inner: D, alpha: &'a mut Option, } impl<'de, 'a, D, A> Visitor<'de> for AlphaSeqVisitor<'a, D, A> where D: Visitor<'de>, A: Deserialize<'de>, { type Value = D::Value; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { self.inner.expecting(formatter)?; write!(formatter, " with an alpha value") } fn visit_seq(self, mut seq: T) -> Result where T: serde::de::SeqAccess<'de>, { let color = self.inner.visit_seq(&mut seq)?; *self.alpha = seq.next_element()?; Ok(color) } } /// Deserializes a map or a struct with an "alpha" key, or a tuple with the /// alpha value as the last value. struct AlphaMapVisitor<'a, D, A> { inner: D, alpha: &'a mut Option, field_count: Option, } impl<'de, 'a, D, A> Visitor<'de> for AlphaMapVisitor<'a, D, A> where D: Visitor<'de>, A: Deserialize<'de>, { type Value = D::Value; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { self.inner.expecting(formatter)?; write!(formatter, " with an alpha value") } fn visit_seq(self, mut seq: T) -> Result where T: serde::de::SeqAccess<'de>, { let color = if self.field_count.is_none() { self.inner.visit_unit()? } else { self.inner.visit_seq(&mut seq)? }; *self.alpha = seq.next_element()?; Ok(color) } fn visit_map(self, map: T) -> Result where T: serde::de::MapAccess<'de>, { self.inner.visit_map(MapWrapper { inner: map, alpha: self.alpha, field_count: self.field_count, }) } fn visit_newtype_struct(self, deserializer: T) -> Result where T: Deserializer<'de>, { *self.alpha = Some(A::deserialize(deserializer)?); self.inner.visit_unit() } } /// Intercepts map deserializing to catch the alpha value while deserializing /// the entries. struct MapWrapper<'a, T, A> { inner: T, alpha: &'a mut Option, field_count: Option, } impl<'a, 'de, T, A> MapAccess<'de> for MapWrapper<'a, T, A> where T: MapAccess<'de>, A: Deserialize<'de>, { type Error = T::Error; fn next_key_seed(&mut self, mut seed: K) -> Result, Self::Error> where K: serde::de::DeserializeSeed<'de>, { // Look for and extract the alpha value if its key is found, then return // the next key after that. The first key that isn't alpha is // immediately returned to the wrapped type's visitor. loop { seed = match self.inner.next_key_seed(AlphaFieldDeserializerSeed { inner: seed, field_count: self.field_count, }) { Ok(Some(AlphaField::Alpha(seed))) => { // We found the alpha value, so deserialize it... if self.alpha.is_some() { return Err(serde::de::Error::duplicate_field("alpha")); } *self.alpha = Some(self.inner.next_value()?); // ...then give the seed back for the next key seed } Ok(Some(AlphaField::Other(other))) => return Ok(Some(other)), Ok(None) => return Ok(None), Err(error) => return Err(error), }; } } fn next_value_seed(&mut self, seed: V) -> Result where V: serde::de::DeserializeSeed<'de>, { self.inner.next_value_seed(seed) } } struct AlphaFieldDeserializerSeed { inner: T, field_count: Option, } impl<'de, T> DeserializeSeed<'de> for AlphaFieldDeserializerSeed where T: DeserializeSeed<'de>, { type Value = AlphaField; fn deserialize(self, deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_identifier(AlphaFieldVisitor { inner: self.inner, field_count: self.field_count, }) } } /// An alpha struct field or another struct field. enum AlphaField { Alpha(A), Other(O), } /// A struct field name that hasn't been serialized yet. enum StructField<'de> { Unsigned(u64), Str(&'de str), Bytes(&'de [u8]), } struct AlphaFieldVisitor { inner: T, field_count: Option, } impl<'de, T> Visitor<'de> for AlphaFieldVisitor where T: DeserializeSeed<'de>, { type Value = AlphaField; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { write!(formatter, "alpha field") } fn visit_u64(self, v: u64) -> Result where E: serde::de::Error, { // We need the field count here to get the last tuple field. No field // count implies that we definitely expected a struct or a map. let field_count = self.field_count.ok_or(serde::de::Error::invalid_type( serde::de::Unexpected::Unsigned(v), &"map key or struct field", ))?; // Assume that it's the alpha value if it's after the expected number of // fields. Otherwise, pass on to the wrapped type's deserializer. if v == field_count as u64 { Ok(AlphaField::Alpha(self.inner)) } else { Ok(AlphaField::Other(self.inner.deserialize( StructFieldDeserializer { struct_field: StructField::Unsigned(v), error: PhantomData, }, )?)) } } fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { // Assume that it's the alpha value if it's named "alpha". Otherwise, // pass on to the wrapped type's deserializer. if v == "alpha" { Ok(AlphaField::Alpha(self.inner)) } else { Ok(AlphaField::Other(self.inner.deserialize( StructFieldDeserializer { struct_field: StructField::Str(v), error: PhantomData, }, )?)) } } fn visit_bytes(self, v: &[u8]) -> Result where E: serde::de::Error, { // Assume that it's the alpha value if it's named "alpha". Otherwise, // pass on to the wrapped type's deserializer. if v == b"alpha" { Ok(AlphaField::Alpha(self.inner)) } else { Ok(AlphaField::Other(self.inner.deserialize( StructFieldDeserializer { struct_field: StructField::Bytes(v), error: PhantomData, }, )?)) } } } /// Deserializes a non-alpha struct field name. struct StructFieldDeserializer<'a, E> { struct_field: StructField<'a>, error: PhantomData E>, } impl<'a, 'de, E> Deserializer<'de> for StructFieldDeserializer<'a, E> where E: serde::de::Error, { type Error = E; fn deserialize_identifier(self, visitor: V) -> Result where V: Visitor<'de>, { match self.struct_field { StructField::Unsigned(v) => visitor.visit_u64(v), StructField::Str(v) => visitor.visit_str(v), StructField::Bytes(v) => visitor.visit_bytes(v), } } fn deserialize_ignored_any(self, visitor: V) -> Result where V: Visitor<'de>, { self.deserialize_identifier(visitor) } fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { self.deserialize_identifier(visitor) } // Unsupported methods:: fn deserialize_bool(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_i8(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_i16(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_i32(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_i64(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_u8(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_u16(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_u32(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_u64(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_f32(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_f64(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_char(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_str(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_string(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_bytes(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_byte_buf(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_option(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_unit(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_unit_struct( self, _name: &'static str, _visitor: V, ) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_newtype_struct( self, _name: &'static str, _visitor: V, ) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_seq(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_tuple(self, _len: usize, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_tuple_struct( self, _name: &'static str, _len: usize, _visitor: V, ) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_map(self, _visitor: V) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_struct( self, _name: &'static str, _fields: &'static [&'static str], _visitor: V, ) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } fn deserialize_enum( self, _name: &'static str, _variants: &'static [&'static str], _visitor: V, ) -> Result where V: Visitor<'de>, { struct_field_deserializer_error() } } fn struct_field_deserializer_error() -> ! { unimplemented!("StructFieldDeserializer can only deserialize identifiers") } palette-0.7.5/src/serde/alpha_serializer.rs000064400000000000000000000243401046102023000170530ustar 00000000000000use serde::{ ser::{ SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, }, Serialize, Serializer, }; /// Serializes a color with an attached alpha value. The alpha value is added /// alongside the other values in a flattened structure. pub(crate) struct AlphaSerializer<'a, S, A> { pub inner: S, pub alpha: &'a A, } impl<'a, S, A> Serializer for AlphaSerializer<'a, S, A> where S: Serializer, A: Serialize, { type Ok = S::Ok; type Error = S::Error; type SerializeSeq = AlphaSerializer<'a, S::SerializeSeq, A>; type SerializeTuple = AlphaSerializer<'a, S::SerializeTuple, A>; type SerializeTupleStruct = AlphaSerializer<'a, S::SerializeTupleStruct, A>; type SerializeTupleVariant = AlphaSerializer<'a, S::SerializeTupleVariant, A>; type SerializeMap = AlphaSerializer<'a, S::SerializeMap, A>; type SerializeStruct = AlphaSerializer<'a, S::SerializeStruct, A>; type SerializeStructVariant = AlphaSerializer<'a, S::SerializeStructVariant, A>; fn serialize_seq(self, len: Option) -> Result { Ok(AlphaSerializer { inner: self.inner.serialize_seq(len.map(|len| len + 1))?, alpha: self.alpha, }) } fn serialize_tuple(self, len: usize) -> Result { Ok(AlphaSerializer { inner: self.inner.serialize_tuple(len + 1)?, alpha: self.alpha, }) } fn serialize_tuple_struct( self, name: &'static str, len: usize, ) -> Result { Ok(AlphaSerializer { inner: self.inner.serialize_tuple_struct(name, len + 1)?, alpha: self.alpha, }) } fn serialize_map(self, len: Option) -> Result { Ok(AlphaSerializer { inner: self.inner.serialize_map(len.map(|len| len + 1))?, alpha: self.alpha, }) } fn serialize_struct( self, name: &'static str, len: usize, ) -> Result { Ok(AlphaSerializer { inner: self.inner.serialize_struct(name, len + 1)?, alpha: self.alpha, }) } fn serialize_newtype_struct( self, name: &'static str, value: &T, ) -> Result where T: serde::Serialize, { let mut serializer = self.serialize_tuple_struct(name, 1)?; serializer.serialize_field(value)?; serializer.end() } fn serialize_unit_struct(self, name: &'static str) -> Result { self.inner.serialize_newtype_struct(name, self.alpha) } fn serialize_unit(self) -> Result { self.serialize_tuple(0)?.end() } // Unsupported methods: fn serialize_bool(self, _v: bool) -> Result { alpha_serializer_error() } fn serialize_i8(self, _v: i8) -> Result { alpha_serializer_error() } fn serialize_i16(self, _v: i16) -> Result { alpha_serializer_error() } fn serialize_i32(self, _v: i32) -> Result { alpha_serializer_error() } fn serialize_i64(self, _v: i64) -> Result { alpha_serializer_error() } fn serialize_u8(self, _v: u8) -> Result { alpha_serializer_error() } fn serialize_u16(self, _v: u16) -> Result { alpha_serializer_error() } fn serialize_u32(self, _v: u32) -> Result { alpha_serializer_error() } fn serialize_u64(self, _v: u64) -> Result { alpha_serializer_error() } fn serialize_f32(self, _v: f32) -> Result { alpha_serializer_error() } fn serialize_f64(self, _v: f64) -> Result { alpha_serializer_error() } fn serialize_char(self, _v: char) -> Result { alpha_serializer_error() } fn serialize_str(self, _v: &str) -> Result { alpha_serializer_error() } fn serialize_bytes(self, _v: &[u8]) -> Result { alpha_serializer_error() } fn serialize_none(self) -> Result { alpha_serializer_error() } fn serialize_some(self, _value: &T) -> Result where T: serde::Serialize, { alpha_serializer_error() } fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, ) -> Result { alpha_serializer_error() } fn serialize_struct_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, ) -> Result { alpha_serializer_error() } fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, ) -> Result { alpha_serializer_error() } fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _value: &T, ) -> Result where T: serde::Serialize, { alpha_serializer_error() } fn serialize_i128(self, v: i128) -> Result { let _ = v; alpha_serializer_error() } fn serialize_u128(self, v: u128) -> Result { let _ = v; alpha_serializer_error() } fn is_human_readable(&self) -> bool { self.inner.is_human_readable() } } fn alpha_serializer_error() -> ! { unimplemented!("AlphaSerializer can only serialize structs, maps and sequences") } impl<'a, S, A> SerializeSeq for AlphaSerializer<'a, S, A> where S: SerializeSeq, A: Serialize, { type Ok = S::Ok; type Error = S::Error; fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_element(value) } fn end(mut self) -> Result { self.inner.serialize_element(self.alpha)?; self.inner.end() } } impl<'a, S, A> SerializeTuple for AlphaSerializer<'a, S, A> where S: SerializeTuple, A: Serialize, { type Ok = S::Ok; type Error = S::Error; fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_element(value) } fn end(mut self) -> Result { self.inner.serialize_element(self.alpha)?; self.inner.end() } } impl<'a, S, A> SerializeTupleStruct for AlphaSerializer<'a, S, A> where S: SerializeTupleStruct, A: Serialize, { type Ok = S::Ok; type Error = S::Error; fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_field(value) } fn end(mut self) -> Result { self.inner.serialize_field(self.alpha)?; self.inner.end() } } impl<'a, S, A> SerializeTupleVariant for AlphaSerializer<'a, S, A> where S: SerializeTupleVariant, A: Serialize, { type Ok = S::Ok; type Error = S::Error; fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_field(value) } fn end(mut self) -> Result { self.inner.serialize_field(self.alpha)?; self.inner.end() } } impl<'a, S, A> SerializeMap for AlphaSerializer<'a, S, A> where S: SerializeMap, A: Serialize, { type Ok = S::Ok; type Error = S::Error; fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_key(key) } fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_value(value) } fn serialize_entry( &mut self, key: &K, value: &V, ) -> Result<(), Self::Error> where K: Serialize, V: Serialize, { self.inner.serialize_entry(key, value) } fn end(mut self) -> Result { self.inner.serialize_entry("alpha", self.alpha)?; self.inner.end() } } impl<'a, S, A> SerializeStruct for AlphaSerializer<'a, S, A> where S: SerializeStruct, A: Serialize, { type Ok = S::Ok; type Error = S::Error; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_field(key, value) } fn skip_field(&mut self, key: &'static str) -> Result<(), Self::Error> { self.inner.skip_field(key) } fn end(mut self) -> Result { self.inner.serialize_field("alpha", self.alpha)?; self.inner.end() } } impl<'a, S, A> SerializeStructVariant for AlphaSerializer<'a, S, A> where S: SerializeStructVariant, A: Serialize, { type Ok = S::Ok; type Error = S::Error; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<(), Self::Error> where T: Serialize, { self.inner.serialize_field(key, value) } fn skip_field(&mut self, key: &'static str) -> Result<(), Self::Error> { self.inner.skip_field(key) } fn end(mut self) -> Result { self.inner.serialize_field("alpha", self.alpha)?; self.inner.end() } } palette-0.7.5/src/serde.rs000064400000000000000000000221011046102023000135260ustar 00000000000000//! Utilities for serializing and deserializing with `serde`. //! //! These modules and functions can be combined with `serde`'s [field //! attributes](https://serde.rs/field-attrs.html) to better control how to //! serialize and deserialize colors. See each item's examples for more details. use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{ blend::{PreAlpha, Premultiply}, cast::{self, ArrayCast, UintCast}, stimulus::Stimulus, Alpha, }; pub(crate) use self::{alpha_deserializer::AlphaDeserializer, alpha_serializer::AlphaSerializer}; mod alpha_deserializer; mod alpha_serializer; /// Combines [`serialize_as_array`] and [`deserialize_as_array`] as a module for `#[serde(with = "...")]`. /// /// ``` /// use serde::{Serialize, Deserialize}; /// use palette::{Srgb, Srgba}; /// /// #[derive(Serialize, Deserialize, PartialEq, Debug)] /// struct MyColors { /// #[serde(with = "palette::serde::as_array")] /// opaque: Srgb, /// #[serde(with = "palette::serde::as_array")] /// transparent: Srgba, /// } /// /// let my_colors = MyColors { /// opaque: Srgb::new(0.6, 0.8, 0.3), /// transparent: Srgba::new(0.6, 0.8, 0.3, 0.5), /// }; /// /// let json = serde_json::to_string(&my_colors).unwrap(); /// /// assert_eq!( /// json, /// r#"{"opaque":[0.6,0.8,0.3],"transparent":[0.6,0.8,0.3,0.5]}"# /// ); /// /// assert_eq!( /// serde_json::from_str::(&json).unwrap(), /// my_colors /// ); /// ``` pub mod as_array { pub use super::deserialize_as_array as deserialize; pub use super::serialize_as_array as serialize; } /// Serialize the value as an array of its components. /// /// ``` /// use serde::Serialize; /// use palette::{Srgb, Srgba}; /// /// #[derive(Serialize)] /// struct MyColors { /// #[serde(serialize_with = "palette::serde::serialize_as_array")] /// opaque: Srgb, /// #[serde(serialize_with = "palette::serde::serialize_as_array")] /// transparent: Srgba, /// } /// /// let my_colors = MyColors { /// opaque: Srgb::new(0.6, 0.8, 0.3), /// transparent: Srgba::new(0.6, 0.8, 0.3, 0.5), /// }; /// /// assert_eq!( /// serde_json::to_string(&my_colors).unwrap(), /// r#"{"opaque":[0.6,0.8,0.3],"transparent":[0.6,0.8,0.3,0.5]}"# /// ); /// ``` pub fn serialize_as_array(value: &T, serializer: S) -> Result where T: ArrayCast, T::Array: Serialize, S: Serializer, { cast::into_array_ref(value).serialize(serializer) } /// Deserialize a value from an array of its components. /// /// ``` /// use serde::Deserialize; /// use palette::{Srgb, Srgba}; /// /// #[derive(Deserialize, PartialEq, Debug)] /// struct MyColors { /// #[serde(deserialize_with = "palette::serde::deserialize_as_array")] /// opaque: Srgb, /// #[serde(deserialize_with = "palette::serde::deserialize_as_array")] /// transparent: Srgba, /// } /// /// let my_colors = MyColors { /// opaque: Srgb::new(0.6, 0.8, 0.3), /// transparent: Srgba::new(0.6, 0.8, 0.3, 0.5), /// }; /// /// let json = r#"{"opaque":[0.6,0.8,0.3],"transparent":[0.6,0.8,0.3,0.5]}"#; /// assert_eq!( /// serde_json::from_str::(json).unwrap(), /// my_colors /// ); /// ``` pub fn deserialize_as_array<'de, T, D>(deserializer: D) -> Result where T: ArrayCast, T::Array: Deserialize<'de>, D: Deserializer<'de>, { Ok(cast::from_array(T::Array::deserialize(deserializer)?)) } /// Combines [`serialize_as_uint`] and [`deserialize_as_uint`] as a module for `#[serde(with = "...")]`. /// /// ``` /// use serde::{Serialize, Deserialize}; /// use palette::{Srgb, Srgba, rgb::{PackedArgb, PackedRgba}}; /// /// #[derive(Serialize, Deserialize, PartialEq, Debug)] /// struct MyColors { /// #[serde(with = "palette::serde::as_uint")] /// argb: PackedArgb, /// #[serde(with = "palette::serde::as_uint")] /// rgba: PackedRgba, /// } /// /// let my_colors = MyColors { /// argb: Srgb::new(0x17, 0xC6, 0x4C).into(), /// rgba: Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// }; /// /// let json = serde_json::to_string(&my_colors).unwrap(); /// /// assert_eq!( /// json, /// r#"{"argb":4279748172,"rgba":398871807}"# /// ); /// /// assert_eq!( /// serde_json::from_str::(&json).unwrap(), /// my_colors /// ); /// ``` pub mod as_uint { pub use super::deserialize_as_uint as deserialize; pub use super::serialize_as_uint as serialize; } /// Serialize the value as an unsigned integer. /// /// ``` /// use serde::Serialize; /// use palette::{Srgb, Srgba, rgb::{PackedArgb, PackedRgba}}; /// /// #[derive(Serialize)] /// struct MyColors { /// #[serde(serialize_with = "palette::serde::serialize_as_uint")] /// argb: PackedArgb, /// #[serde(serialize_with = "palette::serde::serialize_as_uint")] /// rgba: PackedRgba, /// } /// /// let my_colors = MyColors { /// argb: Srgb::new(0x17, 0xC6, 0x4C).into(), /// rgba: Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// }; /// /// assert_eq!( /// serde_json::to_string(&my_colors).unwrap(), /// r#"{"argb":4279748172,"rgba":398871807}"# /// ); /// ``` pub fn serialize_as_uint(value: &T, serializer: S) -> Result where T: UintCast, T::Uint: Serialize, S: Serializer, { cast::into_uint_ref(value).serialize(serializer) } /// Deserialize a value from an unsigned integer. /// /// ``` /// use serde::Deserialize; /// use palette::{Srgb, Srgba, rgb::{PackedArgb, PackedRgba}}; /// /// #[derive(Deserialize, PartialEq, Debug)] /// struct MyColors { /// #[serde(deserialize_with = "palette::serde::deserialize_as_uint")] /// argb: PackedArgb, /// #[serde(deserialize_with = "palette::serde::deserialize_as_uint")] /// rgba: PackedRgba, /// } /// /// let my_colors = MyColors { /// argb: Srgb::new(0x17, 0xC6, 0x4C).into(), /// rgba: Srgba::new(0x17, 0xC6, 0x4C, 0xFF).into(), /// }; /// /// let json = r#"{"argb":4279748172,"rgba":398871807}"#; /// assert_eq!( /// serde_json::from_str::(json).unwrap(), /// my_colors /// ); /// ``` pub fn deserialize_as_uint<'de, T, D>(deserializer: D) -> Result where T: UintCast, T::Uint: Deserialize<'de>, D: Deserializer<'de>, { Ok(cast::from_uint(T::Uint::deserialize(deserializer)?)) } /// Deserialize a transparent color without requiring the alpha to be specified. /// /// A color with missing alpha will be interpreted as fully opaque. /// /// ``` /// use serde::Deserialize; /// use palette::Srgba; /// /// #[derive(Deserialize, PartialEq, Debug)] /// struct MyColors { /// #[serde(deserialize_with = "palette::serde::deserialize_with_optional_alpha")] /// opaque: Srgba, /// #[serde(deserialize_with = "palette::serde::deserialize_with_optional_alpha")] /// transparent: Srgba, /// } /// /// let my_colors = MyColors { /// opaque: Srgba::new(0.6, 0.8, 0.3, 1.0), /// transparent: Srgba::new(0.6, 0.8, 0.3, 0.5), /// }; /// /// let json = r#"{ /// "opaque":{"red":0.6,"green":0.8,"blue":0.3}, /// "transparent":{"red":0.6,"green":0.8,"blue":0.3,"alpha":0.5} /// }"#; /// assert_eq!( /// serde_json::from_str::(json).unwrap(), /// my_colors /// ); /// ``` pub fn deserialize_with_optional_alpha<'de, T, A, D>( deserializer: D, ) -> Result, D::Error> where T: Deserialize<'de>, A: Stimulus + Deserialize<'de>, D: Deserializer<'de>, { let mut alpha: Option = None; let color = T::deserialize(crate::serde::AlphaDeserializer { inner: deserializer, alpha: &mut alpha, })?; Ok(Alpha { color, alpha: alpha.unwrap_or_else(A::max_intensity), }) } /// Deserialize a premultiplied transparent color without requiring the alpha to be specified. /// /// A color with missing alpha will be interpreted as fully opaque. /// /// ``` /// use serde::Deserialize; /// use palette::{LinSrgba, LinSrgb, blend::PreAlpha}; /// /// type PreRgba = PreAlpha>; /// /// #[derive(Deserialize, PartialEq, Debug)] /// struct MyColors { /// #[serde(deserialize_with = "palette::serde::deserialize_with_optional_pre_alpha")] /// opaque: PreRgba, /// #[serde(deserialize_with = "palette::serde::deserialize_with_optional_pre_alpha")] /// transparent: PreRgba, /// } /// /// let my_colors = MyColors { /// opaque: LinSrgba::new(0.6, 0.8, 0.3, 1.0).into(), /// transparent: LinSrgba::new(0.6, 0.8, 0.3, 0.5).into(), /// }; /// /// let json = r#"{ /// "opaque":{"red":0.6,"green":0.8,"blue":0.3}, /// "transparent":{"red":0.3,"green":0.4,"blue":0.15,"alpha":0.5} /// }"#; /// assert_eq!( /// serde_json::from_str::(json).unwrap(), /// my_colors /// ); /// ``` pub fn deserialize_with_optional_pre_alpha<'de, T, D>( deserializer: D, ) -> Result, D::Error> where T: Premultiply + Deserialize<'de>, T::Scalar: Stimulus + Deserialize<'de>, D: Deserializer<'de>, { let mut alpha: Option = None; let color = T::deserialize(crate::serde::AlphaDeserializer { inner: deserializer, alpha: &mut alpha, })?; Ok(PreAlpha { color, alpha: alpha.unwrap_or_else(T::Scalar::max_intensity), }) } palette-0.7.5/src/stimulus.rs000064400000000000000000000265371046102023000143320ustar 00000000000000//! Traits for working with stimulus colors and values, such as RGB and XYZ. use crate::{ clamp, num::{One, Real, Round, Zero}, }; /// Color components that represent a stimulus intensity. /// /// The term "stimulus" comes from "tristimulus", literally a set of three /// stimuli, which is a term for color spaces that measure the intensity of /// three primary color values. Classic examples of tristimulus color space are /// XYZ and RGB. /// /// Stimulus values are expected to have these properties: /// * Has a typical range from `0` to some finite maximum, the "max intensity". /// This represents a range from `0%` to `100%`. For example `0u8` to /// `255u8`, `0.0f32` to `1.0f32`. /// * Values below `0` are considered invalid for display purposes, but may /// still be used in calculations. /// * Values above the "max intensity" are sometimes supported, depending on /// the application. For example in 3D rendering, where high values represent /// intense light. /// * Unsigned integer values (`u8`, `u16`, `u32`, etc.) have a range from `0` /// to their largest representable value. For example `0u8` to `255u8` or /// `0u16` to `65535u16`. /// * Real values (`f32`, `f64`, fixed point types, etc.) have a range from /// `0.0` to `1.0`. pub trait Stimulus: Zero { /// The highest displayable value this component type can reach. Integers /// types are expected to return their maximum value, while real numbers /// (like floats) return 1.0. Higher values are allowed, but they may be /// lowered to this before converting to another format. #[must_use] fn max_intensity() -> Self; } impl Stimulus for T where T: Real + One + Zero, { #[inline] fn max_intensity() -> Self { Self::one() } } macro_rules! impl_uint_components { ($($ty: ident),+) => { $( impl Stimulus for $ty { #[inline] fn max_intensity() -> Self { $ty::MAX } } )* }; } impl_uint_components!(u8, u16, u32, u64, u128); /// A marker trait for colors where all components are stimuli. /// /// Typical stimulus colors are RGB and XYZ. pub trait StimulusColor {} /// Converts from a stimulus color component type, while performing the /// appropriate scaling, rounding and clamping. /// /// ``` /// use palette::stimulus::FromStimulus; /// /// // Scales the value up to u8::MAX while converting. /// let u8_component = u8::from_stimulus(1.0f32); /// assert_eq!(u8_component, 255); /// ``` pub trait FromStimulus { /// Converts `other` into `Self`, while performing the appropriate scaling, /// rounding and clamping. #[must_use] fn from_stimulus(other: T) -> Self; } impl> FromStimulus for T { #[inline] fn from_stimulus(other: U) -> T { other.into_stimulus() } } /// Converts into a stimulus color component type, while performing the /// appropriate scaling, rounding and clamping. /// /// ``` /// use palette::stimulus::IntoStimulus; /// /// // Scales the value up to u8::MAX while converting. /// let u8_component: u8 = 1.0f32.into_stimulus(); /// assert_eq!(u8_component, 255); /// ``` pub trait IntoStimulus { /// Converts `self` into `T`, while performing the appropriate scaling, /// rounding and clamping. #[must_use] fn into_stimulus(self) -> T; } impl IntoStimulus for T { #[inline] fn into_stimulus(self) -> T { self } } // C23 = 2^23, in f32 // C52 = 2^52, in f64 const C23: u32 = 0x4b00_0000; const C52: u64 = 0x4330_0000_0000_0000; // Float to uint conversion with rounding to nearest even number. Formula // follows the form (x_f32 + C23_f32) - C23_u32, where x is the component. From // Hacker's Delight, p. 378-380. // Works on the range of [-0.25, 2^23] for f32, [-0.25, 2^52] for f64. // // Special cases: // NaN -> uint::MAX // inf -> uint::MAX // -inf -> 0 // Greater than 2^23 for f64, 2^52 for f64 -> uint::MAX macro_rules! convert_float_to_uint { ($float: ident; direct ($($direct_target: ident),+); $(via $temporary: ident ($($target: ident),+);)*) => { $( impl IntoStimulus<$direct_target> for $float { #[inline] fn into_stimulus(self) -> $direct_target { let max = $direct_target::max_intensity() as $float; let scaled = (self * max).min(max); let f = scaled + f32::from_bits(C23); (f.to_bits().saturating_sub(C23)) as $direct_target } } )+ $( $( impl IntoStimulus<$target> for $float { #[inline] fn into_stimulus(self) -> $target { let max = $target::max_intensity() as $temporary; let scaled = (self as $temporary * max).min(max); let f = scaled + f64::from_bits(C52); (f.to_bits().saturating_sub(C52)) as $target } } )+ )* }; } // Double to uint conversion with rounding to nearest even number. Formula // follows the form (x_f64 + C52_f64) - C52_u64, where x is the component. macro_rules! convert_double_to_uint { ($double: ident; direct ($($direct_target: ident),+);) => { $( impl IntoStimulus<$direct_target> for $double { #[inline] fn into_stimulus(self) -> $direct_target { let max = $direct_target::max_intensity() as $double; let scaled = (self * max).min(max); let f = scaled + f64::from_bits(C52); (f.to_bits().saturating_sub(C52)) as $direct_target } } )+ }; } // Uint to float conversion with the formula (x_u32 + C23_u32) - C23_f32, where // x is the component. We convert the component to f32 then multiply it by the // reciprocal of the float representation max value for u8. // Works on the range of [0, 2^23] for f32, [0, 2^52 - 1] for f64. impl IntoStimulus for u8 { #[inline] fn into_stimulus(self) -> f32 { let comp_u = u32::from(self) + C23; let comp_f = f32::from_bits(comp_u) - f32::from_bits(C23); let max_u = u32::from(core::u8::MAX) + C23; let max_f = (f32::from_bits(max_u) - f32::from_bits(C23)).recip(); comp_f * max_f } } // Uint to f64 conversion with the formula (x_u64 + C23_u64) - C23_f64. impl IntoStimulus for u8 { #[inline] fn into_stimulus(self) -> f64 { let comp_u = u64::from(self) + C52; let comp_f = f64::from_bits(comp_u) - f64::from_bits(C52); let max_u = u64::from(core::u8::MAX) + C52; let max_f = (f64::from_bits(max_u) - f64::from_bits(C52)).recip(); comp_f * max_f } } macro_rules! convert_uint_to_float { ($uint: ident; $(via $temporary: ident ($($target: ident),+);)*) => { $( $( impl IntoStimulus<$target> for $uint { #[inline] fn into_stimulus(self) -> $target { let max = $uint::max_intensity() as $temporary; let scaled = self as $temporary / max; scaled as $target } } )+ )* }; } macro_rules! convert_uint_to_uint { ($uint: ident; $(via $temporary: ident ($($target: ident),+);)*) => { $( $( impl IntoStimulus<$target> for $uint { #[inline] fn into_stimulus(self) -> $target { let target_max = $target::max_intensity() as $temporary; let own_max = $uint::max_intensity() as $temporary; let scaled = (self as $temporary / own_max) * target_max; clamp(Round::round(scaled), 0.0, target_max) as $target } } )+ )* }; } impl IntoStimulus for f32 { #[inline] fn into_stimulus(self) -> f64 { f64::from(self) } } convert_float_to_uint!(f32; direct (u8, u16); via f64 (u32, u64, u128);); impl IntoStimulus for f64 { #[inline] fn into_stimulus(self) -> f32 { self as f32 } } convert_double_to_uint!(f64; direct (u8, u16, u32, u64, u128);); convert_uint_to_uint!(u8; via f32 (u16); via f64 (u32, u64, u128);); convert_uint_to_float!(u16; via f32 (f32); via f64 (f64);); convert_uint_to_uint!(u16; via f32 (u8); via f64 (u32, u64, u128);); convert_uint_to_float!(u32; via f64 (f32, f64);); convert_uint_to_uint!(u32; via f64 (u8, u16, u64, u128);); convert_uint_to_float!(u64; via f64 (f32, f64);); convert_uint_to_uint!(u64; via f64 (u8, u16, u32, u128);); convert_uint_to_float!(u128; via f64 (f32, f64);); convert_uint_to_uint!(u128; via f64 (u8, u16, u32, u64);); #[cfg(test)] mod test { use crate::stimulus::IntoStimulus; #[test] fn float_to_uint() { let data = vec![ -800.0, -0.3, 0.0, 0.005, 0.024983, 0.01, 0.15, 0.3, 0.5, 0.6, 0.7, 0.8, 0.8444, 0.9, 0.955, 0.999, 1.0, 1.4, f32::from_bits(0x4b44_0000), core::f32::MAX, core::f32::MIN, core::f32::NAN, core::f32::INFINITY, core::f32::NEG_INFINITY, ]; let expected = vec![ 0u8, 0, 0, 1, 6, 3, 38, 76, 128, 153, 178, 204, 215, 230, 244, 255, 255, 255, 255, 255, 0, 255, 255, 0, ]; for (d, e) in data.into_iter().zip(expected) { assert_eq!(IntoStimulus::::into_stimulus(d), e); } } #[test] fn double_to_uint() { let data = vec![ -800.0, -0.3, 0.0, 0.005, 0.024983, 0.01, 0.15, 0.3, 0.5, 0.6, 0.7, 0.8, 0.8444, 0.9, 0.955, 0.999, 1.0, 1.4, f64::from_bits(0x4334_0000_0000_0000), core::f64::MAX, core::f64::MIN, core::f64::NAN, core::f64::INFINITY, core::f64::NEG_INFINITY, ]; let expected = vec![ 0u8, 0, 0, 1, 6, 3, 38, 76, 128, 153, 178, 204, 215, 230, 244, 255, 255, 255, 255, 255, 0, 255, 255, 0, ]; for (d, e) in data.into_iter().zip(expected) { assert_eq!(IntoStimulus::::into_stimulus(d), e); } } #[cfg(feature = "approx")] #[test] fn uint_to_float() { fn into_stimulus_old(n: u8) -> f32 { let max = core::u8::MAX as f32; n as f32 / max } for n in (0..=255).step_by(5) { assert_relative_eq!(IntoStimulus::::into_stimulus(n), into_stimulus_old(n)) } } #[cfg(feature = "approx")] #[test] fn uint_to_double() { fn into_stimulus_old(n: u8) -> f64 { let max = core::u8::MAX as f64; n as f64 / max } for n in (0..=255).step_by(5) { assert_relative_eq!(IntoStimulus::::into_stimulus(n), into_stimulus_old(n)) } } } palette-0.7.5/src/visual.rs000064400000000000000000000033171046102023000137370ustar 00000000000000use approx::AbsDiffEq; use core::borrow::Borrow; /// Methods to tell the tell about the visual characteristics of the color pub trait VisualColor where T: AbsDiffEq, { /// Returns `true`, if the color is grey within the bounds of `epsilon`-tolerance, /// i.e. visually only the luminance value matters, not the color's hue. fn is_grey(&self, epsilon: T::Epsilon) -> bool; /// Returns `true`, if the color is white within the bounds of `epsilon`-tolerance, /// i.e. the color's hue is irrelevant **and** it is at or beyond the /// `sRGB` maximum brightness. /// A color that is *only* at or beyond maximum brightness isn't /// necessarily white. It may also be a bright shining hue. fn is_white(&self, epsilon: T::Epsilon) -> bool; /// Returns `true`, if the color is black within the bounds of `epsilon`-tolerance. fn is_black(&self, epsilon: T::Epsilon) -> bool; } /// Methods to compare visual characteristics of two colors pub trait VisuallyEqual: VisualColor where T: AbsDiffEq + Clone, S: Borrow, O: Borrow, { /// Returns true, if `self` and `other` are either both white or both black fn both_black_or_both_white(s: S, o: O, epsilon: T::Epsilon) -> bool { s.borrow().is_white(epsilon.clone()) && o.borrow().is_white(epsilon.clone()) || s.borrow().is_black(epsilon.clone()) && o.borrow().is_black(epsilon) } /// Returns true, if `self` and `other` are both fully desaturated fn both_greyscale(s: S, o: O, epsilon: T::Epsilon) -> bool { s.borrow().is_grey(epsilon.clone()) && o.borrow().is_grey(epsilon) } fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool; } palette-0.7.5/src/white_point.rs000064400000000000000000000164011046102023000147630ustar 00000000000000//! Defines the tristimulus values of the CIE Illuminants. //! //! White point is the reference white or target white as seen by a standard //! observer under a standard illuminant. For example, photographs taken indoors //! may be lit by incandescent lights, which are relatively orange compared to //! daylight. Defining "white" as daylight will give unacceptable results when //! attempting to color-correct a photograph taken with incandescent lighting. use crate::{num::Real, Xyz}; /// Represents an unspecified reference white point. /// /// Some color spaces (such as `Xyz` and `Yxy`) or operations don't necessarily /// need a known intended white point. `Any` may be used as a placeholder type /// in those situations. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Any; /// WhitePoint defines the Xyz color co-ordinates for a given white point. /// /// A white point (often referred to as reference white or target white in /// technical documents) is a set of tristimulus values or chromaticity /// coordinates that serve to define the color "white" in image capture, /// encoding, or reproduction. /// /// Custom white points can be easily defined on an empty struct with the /// tristimulus values and can be used in place of the ones defined in this /// library. pub trait WhitePoint: 'static { /// Get the Xyz chromaticity co-ordinates for the white point. fn get_xyz() -> Xyz; } /// CIE standard illuminant A /// /// CIE standard illuminant A is intended to represent typical, domestic, /// tungsten-filament lighting. Its relative spectral power distribution is that /// of a Planckian radiator at a temperature of approximately 2856 K. Uses the /// CIE 1932 2° Standard Observer #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct A; impl WhitePoint for A { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(1.09850), T::from_f64(1.0), T::from_f64(0.35585)) } } /// CIE standard illuminant B /// /// CIE standard illuminant B represents noon sunlight, with a correlated color /// temperature (CCT) of 4874 K Uses the CIE 1932 2° Standard Observer #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct B; impl WhitePoint for B { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.99072), T::from_f64(1.0), T::from_f64(0.85223)) } } /// CIE standard illuminant C /// /// CIE standard illuminant C represents the average day light with a CCT of /// 6774 K Uses the CIE 1932 2° Standard Observer #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct C; impl WhitePoint for C { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.98074), T::from_f64(1.0), T::from_f64(1.18232)) } } /// CIE D series standard illuminant - D50 /// /// D50 White Point is the natural daylight with a color temperature of around /// 5000K for 2° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D50; impl WhitePoint for D50 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.96422), T::from_f64(1.0), T::from_f64(0.82521)) } } /// CIE D series standard illuminant - D55 /// /// D55 White Point is the natural daylight with a color temperature of around /// 5500K for 2° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D55; impl WhitePoint for D55 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.95682), T::from_f64(1.0), T::from_f64(0.92149)) } } /// CIE D series standard illuminant - D65 /// /// D65 White Point is the natural daylight with a color temperature of 6500K /// for 2° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D65; impl WhitePoint for D65 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.95047), T::from_f64(1.0), T::from_f64(1.08883)) } } /// CIE D series standard illuminant - D75 /// /// D75 White Point is the natural daylight with a color temperature of around /// 7500K for 2° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D75; impl WhitePoint for D75 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.94972), T::from_f64(1.0), T::from_f64(1.22638)) } } /// CIE standard illuminant E /// /// CIE standard illuminant E represents the equal energy radiator /// Uses the CIE 1932 2° Standard Observer #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct E; impl WhitePoint for E { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(1.0), T::from_f64(1.0), T::from_f64(1.0)) } } /// CIE fluorescent illuminant series - F2 /// /// F2 represents a semi-broadband fluorescent lamp for 2° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct F2; impl WhitePoint for F2 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.99186), T::from_f64(1.0), T::from_f64(0.67393)) } } /// CIE fluorescent illuminant series - F7 /// /// F7 represents a broadband fluorescent lamp for 2° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct F7; impl WhitePoint for F7 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.95041), T::from_f64(1.0), T::from_f64(1.08747)) } } /// CIE fluorescent illuminant series - F11 /// /// F11 represents a narrowband fluorescent lamp for 2° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct F11; impl WhitePoint for F11 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(1.00962), T::from_f64(1.0), T::from_f64(0.64350)) } } /// CIE D series standard illuminant - D50 /// /// D50 White Point is the natural daylight with a color temperature of around /// 5000K for 10° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D50Degree10; impl WhitePoint for D50Degree10 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.9672), T::from_f64(1.0), T::from_f64(0.8143)) } } /// CIE D series standard illuminant - D55 /// /// D55 White Point is the natural daylight with a color temperature of around /// 5500K for 10° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D55Degree10; impl WhitePoint for D55Degree10 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.958), T::from_f64(1.0), T::from_f64(0.9093)) } } /// CIE D series standard illuminant - D65 /// /// D65 White Point is the natural daylight with a color temperature of 6500K /// for 10° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D65Degree10; impl WhitePoint for D65Degree10 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.9481), T::from_f64(1.0), T::from_f64(1.073)) } } /// CIE D series standard illuminant - D75 /// /// D75 White Point is the natural daylight with a color temperature of around /// 7500K for 10° Standard Observer. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct D75Degree10; impl WhitePoint for D75Degree10 { #[inline] fn get_xyz() -> Xyz { Xyz::new(T::from_f64(0.94416), T::from_f64(1.0), T::from_f64(1.2064)) } } palette-0.7.5/src/xyz.rs000064400000000000000000000370351046102023000132720ustar 00000000000000//! Types for the CIE 1931 XYZ color space. use core::{marker::PhantomData, ops::Mul}; use crate::{ bool_mask::{HasBoolMask, LazySelect}, convert::{FromColorUnclamped, IntoColorUnclamped}, encoding::IntoLinear, luma::LumaStandard, matrix::{matrix_map, multiply_rgb_to_xyz, multiply_xyz, rgb_to_xyz_matrix}, num::{Arithmetics, FromScalar, IsValidDivisor, One, PartialCmp, Powi, Real, Recip, Zero}, oklab, rgb::{Primaries, Rgb, RgbSpace, RgbStandard}, stimulus::{Stimulus, StimulusColor}, white_point::{Any, WhitePoint, D65}, Alpha, Lab, Luma, Luv, Oklab, Yxy, }; /// CIE 1931 XYZ with an alpha component. See the [`Xyza` implementation in /// `Alpha`](crate::Alpha#Xyza). pub type Xyza = Alpha, T>; /// The CIE 1931 XYZ color space. /// /// XYZ links the perceived colors to their wavelengths and simply makes it /// possible to describe the way we see colors as numbers. It's often used when /// converting from one color space to an other, and requires a standard /// illuminant and a standard observer to be defined. /// /// Conversions and operations on this color space depend on the defined white /// point #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "Wp", component = "T", skip_derives(Xyz, Yxy, Luv, Rgb, Lab, Oklab, Luma) )] #[repr(C)] pub struct Xyz { /// X is the scale of what can be seen as a response curve for the cone /// cells in the human eye. Its range depends /// on the white point and goes from 0.0 to 0.95047 for the default D65. pub x: T, /// Y is the luminance of the color, where 0.0 is black and 1.0 is white. pub y: T, /// Z is the scale of what can be seen as the blue stimulation. Its range /// depends on the white point and goes from 0.0 to 1.08883 for the /// default D65. pub z: T, /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } impl Xyz { /// Create a CIE XYZ color. pub const fn new(x: T, y: T, z: T) -> Xyz { Xyz { x, y, z, white_point: PhantomData, } } /// Convert to a `(X, Y, Z)` tuple. pub fn into_components(self) -> (T, T, T) { (self.x, self.y, self.z) } /// Convert from a `(X, Y, Z)` tuple. pub fn from_components((x, y, z): (T, T, T)) -> Self { Self::new(x, y, z) } /// Changes the reference white point without changing the color value. /// /// This function doesn't change the numerical values, and thus the color it /// represents in an absolute sense. However, the appearance of the color /// may not be the same when observed with the new white point. The effect /// would be similar to taking a photo with an incorrect white balance. /// /// See [chromatic_adaptation](crate::chromatic_adaptation) for operations /// that can change the white point while preserving the color's appearance. #[inline] pub fn with_white_point(self) -> Xyz { Xyz::new(self.x, self.y, self.z) } } impl Xyz where T: Zero, Wp: WhitePoint, { /// Return the `x` value minimum. pub fn min_x() -> T { T::zero() } /// Return the `x` value maximum. pub fn max_x() -> T { Wp::get_xyz().x } /// Return the `y` value minimum. pub fn min_y() -> T { T::zero() } /// Return the `y` value maximum. pub fn max_y() -> T { Wp::get_xyz().y } /// Return the `z` value minimum. pub fn min_z() -> T { T::zero() } /// Return the `z` value maximum. pub fn max_z() -> T { Wp::get_xyz().z } } ///[`Xyza`](crate::Xyza) implementations. impl Alpha, A> { /// Create a CIE XYZ color with transparency. pub const fn new(x: T, y: T, z: T, alpha: A) -> Self { Alpha { color: Xyz::new(x, y, z), alpha, } } /// Convert to a `(X, Y, Z, alpha)` tuple. pub fn into_components(self) -> (T, T, T, A) { (self.color.x, self.color.y, self.color.z, self.alpha) } /// Convert from a `(X, Y, Z, alpha)` tuple. pub fn from_components((x, y, z, alpha): (T, T, T, A)) -> Self { Self::new(x, y, z, alpha) } /// Changes the reference white point without changing the color value. /// /// This function doesn't change the numerical values, and thus the color it /// represents in an absolute sense. However, the appearance of the color /// may not be the same when observed with the new white point. The effect /// would be similar to taking a photo with an incorrect white balance. /// /// See [chromatic_adaptation](crate::chromatic_adaptation) for operations /// that can change the white point while preserving the color's appearance. #[inline] pub fn with_white_point(self) -> Alpha, A> { Alpha::, A>::new(self.color.x, self.color.y, self.color.z, self.alpha) } } impl_reference_component_methods!(Xyz, [x, y, z], white_point); impl_struct_of_arrays_methods!(Xyz, [x, y, z], white_point); impl FromColorUnclamped> for Xyz { fn from_color_unclamped(color: Xyz) -> Self { color } } impl FromColorUnclamped> for Xyz where T: Arithmetics + FromScalar, T::Scalar: Real + Recip + IsValidDivisor + Arithmetics + FromScalar + Clone, Wp: WhitePoint, S: RgbStandard, S::TransferFn: IntoLinear, S::Space: RgbSpace, ::Primaries: Primaries, Yxy: IntoColorUnclamped>, { fn from_color_unclamped(color: Rgb) -> Self { let transform_matrix = S::Space::rgb_to_xyz_matrix() .map_or_else(rgb_to_xyz_matrix::, |matrix| { matrix_map(matrix, T::Scalar::from_f64) }); multiply_rgb_to_xyz(transform_matrix, color.into_linear()) } } impl FromColorUnclamped> for Xyz where T: Zero + One + IsValidDivisor + Arithmetics + Clone, T::Mask: LazySelect + Clone, { fn from_color_unclamped(color: Yxy) -> Self { let Yxy { x, y, luma, .. } = color; // If denominator is zero, NAN or INFINITE leave x and z at the default 0 let mask = y.is_valid_divisor(); let xyz = Xyz { z: lazy_select! { if mask.clone() => (T::one() - &x - &y) / &y, else => T::zero(), }, x: lazy_select! { if mask => x / y, else => T::zero(), }, y: T::one(), white_point: PhantomData, }; xyz * luma } } impl FromColorUnclamped> for Xyz where T: Real + Recip + Powi + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, Wp: WhitePoint, { fn from_color_unclamped(color: Lab) -> Self { // Recip call shows performance benefits in benchmarks for this function let y = (color.l + T::from_f64(16.0)) * T::from_f64(116.0).recip(); let x = y.clone() + (color.a * T::from_f64(500.0).recip()); let z = y.clone() - (color.b * T::from_f64(200.0).recip()); let epsilon: T = T::from_f64(6.0 / 29.0); let kappa: T = T::from_f64(108.0 / 841.0); let delta: T = T::from_f64(4.0 / 29.0); let convert = |c: T| { lazy_select! { if c.gt(&epsilon) => c.clone().powi(3), else => (c.clone() - &delta) * &kappa } }; Xyz::new(convert(x), convert(y), convert(z)) * Wp::get_xyz().with_white_point() } } impl FromColorUnclamped> for Xyz where T: Real + Zero + Recip + Powi + Arithmetics + PartialOrd + Clone + HasBoolMask, Wp: WhitePoint, { fn from_color_unclamped(color: Luv) -> Self { let kappa = T::from_f64(29.0 / 3.0).powi(3); let w = Wp::get_xyz(); let ref_denom_recip = (w.x.clone() + T::from_f64(15.0) * &w.y + T::from_f64(3.0) * w.z).recip(); let u_ref = T::from_f64(4.0) * w.x * &ref_denom_recip; let v_ref = T::from_f64(9.0) * &w.y * ref_denom_recip; if color.l < T::from_f64(1e-5) { return Xyz::new(T::zero(), T::zero(), T::zero()); } let y = if color.l > T::from_f64(8.0) { ((color.l.clone() + T::from_f64(16.0)) * T::from_f64(116.0).recip()).powi(3) } else { color.l.clone() * kappa.recip() } * w.y; let u_prime = color.u / (T::from_f64(13.0) * &color.l) + u_ref; let v_prime = color.v / (T::from_f64(13.0) * color.l) + v_ref; let x = y.clone() * T::from_f64(2.25) * &u_prime / &v_prime; let z = y.clone() * (T::from_f64(3.0) - T::from_f64(0.75) * u_prime - T::from_f64(5.0) * &v_prime) / v_prime; Xyz::new(x, y, z) } } impl FromColorUnclamped> for Xyz where T: Real + Powi + Arithmetics, { fn from_color_unclamped(color: Oklab) -> Self { let m1_inv = oklab::m1_inv(); let m2_inv = oklab::m2_inv(); let Xyz { x: l, y: m, z: s, .. } = multiply_xyz(m2_inv, Xyz::new(color.l, color.a, color.b)); let lms = Xyz::new(l.powi(3), m.powi(3), s.powi(3)); multiply_xyz(m1_inv, lms).with_white_point() } } impl FromColorUnclamped> for Xyz where Self: Mul, Wp: WhitePoint, S: LumaStandard, S::TransferFn: IntoLinear, { fn from_color_unclamped(color: Luma) -> Self { Wp::get_xyz().with_white_point::() * color.into_linear().luma } } impl_tuple_conversion!(Xyz as (T, T, T)); impl_is_within_bounds! { Xyz { x => [Self::min_x(), Self::max_x()], y => [Self::min_y(), Self::max_y()], z => [Self::min_z(), Self::max_z()] } where T: Zero, Wp: WhitePoint } impl_clamp! { Xyz { x => [Self::min_x(), Self::max_x()], y => [Self::min_y(), Self::max_y()], z => [Self::min_z(), Self::max_z()] } other {white_point} where T: Zero, Wp: WhitePoint } impl_mix!(Xyz); impl_lighten! { Xyz increase { x => [Self::min_x(), Self::max_x()], y => [Self::min_y(), Self::max_y()], z => [Self::min_z(), Self::max_z()] } other {} phantom: white_point where Wp: WhitePoint } impl_premultiply!(Xyz {x, y, z} phantom: white_point); impl_euclidean_distance!(Xyz {x, y, z}); impl StimulusColor for Xyz where T: Stimulus {} impl HasBoolMask for Xyz where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Xyz where T: Zero, { fn default() -> Xyz { Xyz::new(T::zero(), T::zero(), T::zero()) } } impl_color_add!(Xyz, [x, y, z], white_point); impl_color_sub!(Xyz, [x, y, z], white_point); impl_color_mul!(Xyz, [x, y, z], white_point); impl_color_div!(Xyz, [x, y, z], white_point); impl_array_casts!(Xyz, [T; 3]); impl_simd_array_conversion!(Xyz, [x, y, z], white_point); impl_struct_of_array_traits!(Xyz, [x, y, z], white_point); impl_copy_clone!(Xyz, [x, y, z], white_point); impl_eq!(Xyz, [x, y, z]); #[allow(deprecated)] impl crate::RelativeContrast for Xyz where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { crate::contrast_ratio(self.y, other.y) } } impl_rand_traits_cartesian!( UniformXyz, Xyz { x => [|x| x * Wp::get_xyz().x], y => [|y| y * Wp::get_xyz().y], z => [|z| z * Wp::get_xyz().z] } phantom: white_point: PhantomData where T: core::ops::Mul, Wp: WhitePoint ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Xyz where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Xyz where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Xyz; use crate::white_point::D65; #[cfg(feature = "random")] use crate::white_point::WhitePoint; const X_N: f64 = 0.95047; const Y_N: f64 = 1.0; const Z_N: f64 = 1.08883; test_convert_into_from_xyz!(Xyz); #[cfg(feature = "approx")] mod conversion { use crate::{white_point::D65, FromColor, LinLuma, LinSrgb, Xyz}; #[test] fn luma() { let a = Xyz::::from_color(LinLuma::new(0.5)); let b = Xyz::new(0.475235, 0.5, 0.544415); assert_relative_eq!(a, b, epsilon = 0.0001); } #[test] fn red() { let a = Xyz::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Xyz::new(0.41240, 0.21260, 0.01930); assert_relative_eq!(a, b, epsilon = 0.0001); } #[test] fn green() { let a = Xyz::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Xyz::new(0.35760, 0.71520, 0.11920); assert_relative_eq!(a, b, epsilon = 0.0001); } #[test] fn blue() { let a = Xyz::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Xyz::new(0.18050, 0.07220, 0.95030); assert_relative_eq!(a, b, epsilon = 0.0001); } } #[test] fn ranges() { assert_ranges! { Xyz; clamped { x: 0.0 => X_N, y: 0.0 => Y_N, z: 0.0 => Z_N } clamped_min {} unclamped {} } } raw_pixel_conversion_tests!(Xyz: x, y, z); raw_pixel_conversion_fail_tests!(Xyz: x, y, z); #[test] fn check_min_max_components() { assert_eq!(Xyz::::min_x(), 0.0); assert_eq!(Xyz::::min_y(), 0.0); assert_eq!(Xyz::::min_z(), 0.0); assert_eq!(Xyz::::max_x(), X_N); assert_eq!(Xyz::::max_y(), Y_N); assert_eq!(Xyz::::max_z(), Z_N); } struct_of_arrays_tests!( Xyz[x, y, z] phantom: white_point, super::Xyza::new(0.1f32, 0.2, 0.3, 0.4), super::Xyza::new(0.2, 0.3, 0.4, 0.5), super::Xyza::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Xyz::::new(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"x":0.3,"y":0.8,"z":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Xyz = ::serde_json::from_str(r#"{"x":0.3,"y":0.8,"z":0.1}"#).unwrap(); assert_eq!(deserialized, Xyz::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Xyz { x: (0.0, D65::get_xyz().x), y: (0.0, D65::get_xyz().y), z: (0.0, D65::get_xyz().z) }, min: Xyz::new(0.0f32, 0.0, 0.0), max: D65::get_xyz().with_white_point() } } palette-0.7.5/src/yxy.rs000064400000000000000000000272511046102023000132700ustar 00000000000000//! Types for the CIE 1931 Yxy (xyY) color space. use core::marker::PhantomData; use crate::{ bool_mask::{HasBoolMask, LazySelect}, convert::{FromColorUnclamped, IntoColorUnclamped}, encoding::IntoLinear, luma::LumaStandard, num::{Arithmetics, IsValidDivisor, One, PartialCmp, Real, Zero}, white_point::{WhitePoint, D65}, Alpha, Luma, Xyz, }; /// CIE 1931 Yxy (xyY) with an alpha component. See the [`Yxya` implementation /// in `Alpha`](crate::Alpha#Yxya). pub type Yxya = Alpha, T>; /// The CIE 1931 Yxy (xyY) color space. /// /// Yxy is a luminance-chromaticity color space derived from the CIE XYZ /// color space. It is widely used to define colors. The chromaticity diagrams /// for the color spaces are a plot of this color space's x and y coordinates. /// /// Conversions and operations on this color space depend on the white point. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "Wp", component = "T", skip_derives(Xyz, Yxy, Luma) )] #[repr(C)] #[doc(alias = "xyY")] pub struct Yxy { /// x chromaticity co-ordinate derived from XYZ color space as X/(X+Y+Z). /// Typical range is between 0 and 1 pub x: T, /// y chromaticity co-ordinate derived from XYZ color space as Y/(X+Y+Z). /// Typical range is between 0 and 1 pub y: T, /// luma (Y) was a measure of the brightness or luminance of a color. /// It is the same as the Y from the XYZ color space. Its range is from ///0 to 1, where 0 is black and 1 is white. pub luma: T, /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } impl Yxy { /// Create a CIE Yxy color. pub const fn new(x: T, y: T, luma: T) -> Yxy { Yxy { x, y, luma, white_point: PhantomData, } } /// Convert to a `(x, y, luma)`, a.k.a. `(x, y, Y)` tuple. pub fn into_components(self) -> (T, T, T) { (self.x, self.y, self.luma) } /// Convert from a `(x, y, luma)`, a.k.a. `(x, y, Y)` tuple. pub fn from_components((x, y, luma): (T, T, T)) -> Self { Self::new(x, y, luma) } /// Changes the reference white point without changing the color value. /// /// This function doesn't change the numerical values, and thus the color it /// represents in an absolute sense. However, the appearance of the color /// may not be the same when observed with the new white point. The effect /// would be similar to taking a photo with an incorrect white balance. /// /// See [chromatic_adaptation](crate::chromatic_adaptation) for operations /// that can change the white point while preserving the color's appearance. #[inline] pub fn with_white_point(self) -> Yxy { Yxy::new(self.x, self.y, self.luma) } } impl Yxy where T: Zero + One, { /// Return the `x` value minimum. pub fn min_x() -> T { T::zero() } /// Return the `x` value maximum. pub fn max_x() -> T { T::one() } /// Return the `y` value minimum. pub fn min_y() -> T { T::zero() } /// Return the `y` value maximum. pub fn max_y() -> T { T::one() } /// Return the `luma` value minimum. pub fn min_luma() -> T { T::zero() } /// Return the `luma` value maximum. pub fn max_luma() -> T { T::one() } } ///[`Yxya`](crate::Yxya) implementations. impl Alpha, A> { /// Create a CIE Yxy color with transparency. pub const fn new(x: T, y: T, luma: T, alpha: A) -> Self { Alpha { color: Yxy::new(x, y, luma), alpha, } } /// Convert to a `(x, y, luma)`, a.k.a. `(x, y, Y)` tuple. pub fn into_components(self) -> (T, T, T, A) { (self.color.x, self.color.y, self.color.luma, self.alpha) } /// Convert from a `(x, y, luma)`, a.k.a. `(x, y, Y)` tuple. pub fn from_components((x, y, luma, alpha): (T, T, T, A)) -> Self { Self::new(x, y, luma, alpha) } /// Changes the reference white point without changing the color value. /// /// This function doesn't change the numerical values, and thus the color it /// represents in an absolute sense. However, the appearance of the color /// may not be the same when observed with the new white point. The effect /// would be similar to taking a photo with an incorrect white balance. /// /// See [chromatic_adaptation](crate::chromatic_adaptation) for operations /// that can change the white point while preserving the color's appearance. #[inline] pub fn with_white_point(self) -> Alpha, A> { Alpha::, A>::new(self.color.x, self.color.y, self.color.luma, self.alpha) } } impl_reference_component_methods!(Yxy, [x, y, luma], white_point); impl_struct_of_arrays_methods!(Yxy, [x, y, luma], white_point); impl_tuple_conversion!(Yxy as (T, T, T)); impl FromColorUnclamped> for Yxy { fn from_color_unclamped(color: Yxy) -> Self { color } } impl FromColorUnclamped> for Yxy where T: Zero + IsValidDivisor + Arithmetics + Clone, T::Mask: LazySelect + Clone, { fn from_color_unclamped(xyz: Xyz) -> Self { let Xyz { x, y, z, .. } = xyz; let sum = x.clone() + &y + z; // If denominator is zero, NAN or INFINITE leave x and y at the default 0 let mask = sum.is_valid_divisor(); Yxy { x: lazy_select! { if mask.clone() => x / &sum, else => T::zero(), }, y: lazy_select! { if mask => y.clone() / sum, else => T::zero() }, luma: y, white_point: PhantomData, } } } impl FromColorUnclamped> for Yxy where S: LumaStandard, S::TransferFn: IntoLinear, Self: Default, { fn from_color_unclamped(luma: Luma) -> Self { Yxy { luma: luma.into_linear().luma, ..Default::default() } } } impl_is_within_bounds! { Yxy { x => [Self::min_x(), Self::max_x()], y => [Self::min_y(), Self::max_y()], luma => [Self::min_luma(), Self::max_luma()] } where T: Zero + One } impl_clamp! { Yxy { x => [Self::min_x(), Self::max_x()], y => [Self::min_y(), Self::max_y()], luma => [Self::min_luma(), Self::max_luma()] } other {white_point} where T: Zero + One } impl_mix!(Yxy); impl_lighten!(Yxy increase {luma => [Self::min_luma(), Self::max_luma()]} other {x, y} phantom: white_point where T: One); impl_premultiply!(Yxy {x, y, luma} phantom: white_point); impl_euclidean_distance!(Yxy {x, y, luma}); impl HasBoolMask for Yxy where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Yxy where T: Zero, Wp: WhitePoint, Xyz: IntoColorUnclamped, { fn default() -> Yxy { // The default for x and y are the white point x and y ( from the default D65). // Since Y (luma) is 0.0, this makes the default color black just like for // other colors. The reason for not using 0 for x and y is that this // outside the usual color gamut and might cause scaling issues. Yxy { luma: T::zero(), ..Wp::get_xyz().with_white_point().into_color_unclamped() } } } impl_color_add!(Yxy, [x, y, luma], white_point); impl_color_sub!(Yxy, [x, y, luma], white_point); impl_color_mul!(Yxy, [x, y, luma], white_point); impl_color_div!(Yxy, [x, y, luma], white_point); impl_array_casts!(Yxy, [T; 3]); impl_simd_array_conversion!(Yxy, [x, y, luma], white_point); impl_struct_of_array_traits!(Yxy, [x, y, luma], white_point); impl_eq!(Yxy, [x, y, luma]); impl_copy_clone!(Yxy, [x, y, luma], white_point); #[allow(deprecated)] impl crate::RelativeContrast for Yxy where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { crate::contrast_ratio(self.luma, other.luma) } } impl_rand_traits_cartesian!(UniformYxy, Yxy {x, y, luma} phantom: white_point: PhantomData); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Yxy where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Yxy where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Yxy; use crate::white_point::D65; test_convert_into_from_xyz!(Yxy); #[cfg(feature = "approx")] mod conversion { use crate::{white_point::D65, FromColor, LinLuma, LinSrgb, Yxy}; #[test] fn luma() { let a = Yxy::::from_color(LinLuma::new(0.5)); let b = Yxy::new(0.312727, 0.329023, 0.5); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn red() { let a = Yxy::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Yxy::new(0.64, 0.33, 0.212673); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn green() { let a = Yxy::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Yxy::new(0.3, 0.6, 0.715152); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn blue() { let a = Yxy::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Yxy::new(0.15, 0.06, 0.072175); assert_relative_eq!(a, b, epsilon = 0.000001); } } #[test] fn ranges() { assert_ranges! { Yxy; clamped { x: 0.0 => 1.0, y: 0.0 => 1.0, luma: 0.0 => 1.0 } clamped_min {} unclamped {} } } raw_pixel_conversion_tests!(Yxy: x, y, luma); raw_pixel_conversion_fail_tests!(Yxy: x, y, luma); #[test] fn check_min_max_components() { assert_eq!(Yxy::::min_x(), 0.0); assert_eq!(Yxy::::min_y(), 0.0); assert_eq!(Yxy::::min_luma(), 0.0); assert_eq!(Yxy::::max_x(), 1.0); assert_eq!(Yxy::::max_y(), 1.0); assert_eq!(Yxy::::max_luma(), 1.0); } struct_of_arrays_tests!( Yxy[x, y, luma] phantom: white_point, super::Yxya::new(0.1f32, 0.2, 0.3, 0.4), super::Yxya::new(0.2, 0.3, 0.4, 0.5), super::Yxya::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Yxy::::new(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"x":0.3,"y":0.8,"luma":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Yxy = ::serde_json::from_str(r#"{"x":0.3,"y":0.8,"luma":0.1}"#).unwrap(); assert_eq!(deserialized, Yxy::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Yxy { x: (0.0, 1.0), y: (0.0, 1.0), luma: (0.0, 1.0) }, min: Yxy::new(0.0f32, 0.0, 0.0), max: Yxy::new(1.0, 1.0, 1.0), } }

= crate::cast::Packed; palette-0.7.5/src/luv.rs000064400000000000000000000272321046102023000132440ustar 00000000000000//! Types for the CIE L\*u\*v\* (CIELUV) color space. use core::{ marker::PhantomData, ops::{Add, Mul, Neg}, }; use crate::{ angle::RealAngle, bool_mask::{HasBoolMask, LazySelect}, convert::FromColorUnclamped, num::{Arithmetics, MinMax, PartialCmp, Powf, Powi, Real, Recip, Trigonometry, Zero}, white_point::{WhitePoint, D65}, Alpha, FromColor, GetHue, Lchuv, LuvHue, Xyz, }; /// CIE L\*u\*v\* (CIELUV) with an alpha component. See the [`Luva` /// implementation in `Alpha`](crate::Alpha#Luva). pub type Luva = Alpha, T>; /// The CIE L\*u\*v\* (CIELUV) color space. /// /// CIE L\*u\*v\* is a device independent color space. It is a simple /// transformation of the CIE XYZ color space with the additional /// property of being more perceptually uniform. In contrast to /// CIELAB, CIELUV is also linear for a fixed lightness: additive /// mixtures of colors (at a fixed lightness) will fall on a line in /// CIELUV-space. /// /// As a result, CIELUV is used more frequently for additive settings. #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "Wp", component = "T", skip_derives(Xyz, Luv, Lchuv) )] #[repr(C)] pub struct Luv { /// L\* is the lightness of the color. 0.0 gives absolute black and 100 /// give the brightest white. pub l: T, /// The range of valid u\* varies depending on the values of L\* /// and v\*, but at its limits u\* is within the interval (-84.0, /// 176.0). pub u: T, /// The range of valid v\* varies depending on the values of L\* /// and u\*, but at its limits v\* is within the interval (-135.0, /// 108.0). pub v: T, /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } impl Luv { /// Create a CIE L\*u\*v\* color. pub const fn new(l: T, u: T, v: T) -> Self { Luv { l, u, v, white_point: PhantomData, } } /// Convert to a `(L\*, u\*, v\*)` tuple. pub fn into_components(self) -> (T, T, T) { (self.l, self.u, self.v) } /// Convert from a `(L\*, u\*, v\*)` tuple. pub fn from_components((l, u, v): (T, T, T)) -> Self { Self::new(l, u, v) } } impl Luv where T: Zero + Real, { /// Return the `l` value minimum. pub fn min_l() -> T { T::zero() } /// Return the `l` value maximum. pub fn max_l() -> T { T::from_f64(100.0) } /// Return the `u` value minimum. pub fn min_u() -> T { T::from_f64(-84.0) } /// Return the `u` value maximum. pub fn max_u() -> T { T::from_f64(176.0) } /// Return the `v` value minimum. pub fn min_v() -> T { T::from_f64(-135.0) } /// Return the `v` value maximum. pub fn max_v() -> T { T::from_f64(108.0) } } ///[`Luva`](crate::Luva) implementations. impl Alpha, A> { /// Create a CIE L\*u\*v\* color with transparency. pub const fn new(l: T, u: T, v: T, alpha: A) -> Self { Alpha { color: Luv::new(l, u, v), alpha, } } /// Convert to u `(L\*, u\*, v\*, alpha)` tuple. pub fn into_components(self) -> (T, T, T, A) { (self.color.l, self.color.u, self.color.v, self.alpha) } /// Convert from u `(L\*, u\*, v\*, alpha)` tuple. pub fn from_components((l, u, v, alpha): (T, T, T, A)) -> Self { Self::new(l, u, v, alpha) } } impl_reference_component_methods!(Luv, [l, u, v], white_point); impl_struct_of_arrays_methods!(Luv, [l, u, v], white_point); impl FromColorUnclamped> for Luv { fn from_color_unclamped(color: Luv) -> Self { color } } impl FromColorUnclamped> for Luv where T: RealAngle + Zero + MinMax + Trigonometry + Mul + Clone, { fn from_color_unclamped(color: Lchuv) -> Self { let (sin_hue, cos_hue) = color.hue.into_raw_radians().sin_cos(); let chroma = color.chroma.max(T::zero()); Luv::new(color.l, chroma.clone() * cos_hue, chroma * sin_hue) } } impl FromColorUnclamped> for Luv where Wp: WhitePoint, T: Real + Zero + Powi + Powf + Recip + Arithmetics + PartialOrd + Clone + HasBoolMask, { fn from_color_unclamped(color: Xyz) -> Self { let w = Wp::get_xyz(); let kappa = T::from_f64(29.0 / 3.0).powi(3); let epsilon = T::from_f64(6.0 / 29.0).powi(3); let prime_denom = color.x.clone() + T::from_f64(15.0) * &color.y + T::from_f64(3.0) * color.z; if prime_denom == T::from_f64(0.0) { return Luv::new(T::zero(), T::zero(), T::zero()); } let prime_denom_recip = prime_denom.recip(); let prime_ref_denom_recip = (w.x.clone() + T::from_f64(15.0) * &w.y + T::from_f64(3.0) * w.z).recip(); let u_prime: T = T::from_f64(4.0) * color.x * &prime_denom_recip; let u_ref_prime = T::from_f64(4.0) * w.x * &prime_ref_denom_recip; let v_prime: T = T::from_f64(9.0) * &color.y * prime_denom_recip; let v_ref_prime = T::from_f64(9.0) * &w.y * prime_ref_denom_recip; let y_r = color.y / w.y; let l = if y_r > epsilon { T::from_f64(116.0) * y_r.powf(T::from_f64(1.0 / 3.0)) - T::from_f64(16.0) } else { kappa * y_r }; Luv { u: T::from_f64(13.0) * &l * (u_prime - u_ref_prime), v: T::from_f64(13.0) * &l * (v_prime - v_ref_prime), l, white_point: PhantomData, } } } impl_tuple_conversion!(Luv as (T, T, T)); impl_is_within_bounds! { Luv { l => [Self::min_l(), Self::max_l()], u => [Self::min_u(), Self::max_u()], v => [Self::min_v(), Self::max_v()] } where T: Real + Zero } impl_clamp! { Luv { l => [Self::min_l(), Self::max_l()], u => [Self::min_u(), Self::max_u()], v => [Self::min_v(), Self::max_v()] } other {white_point} where T: Real + Zero } impl_mix!(Luv); impl_lighten!(Luv increase {l => [Self::min_l(), Self::max_l()]} other {u, v} phantom: white_point); impl_premultiply!(Luv {l, u, v} phantom: white_point); impl_euclidean_distance!(Luv {l, u, v}); impl_hyab!(Luv {lightness: l, chroma1: u, chroma2: v}); impl GetHue for Luv where T: RealAngle + Trigonometry + Add + Neg + Clone, { type Hue = LuvHue; fn get_hue(&self) -> LuvHue { LuvHue::from_cartesian(self.u.clone(), self.v.clone()) } } impl HasBoolMask for Luv where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Luv where T: Zero, { fn default() -> Luv { Luv::new(T::zero(), T::zero(), T::zero()) } } impl_color_add!(Luv, [l, u, v], white_point); impl_color_sub!(Luv, [l, u, v], white_point); impl_color_mul!(Luv, [l, u, v], white_point); impl_color_div!(Luv, [l, u, v], white_point); impl_array_casts!(Luv, [T; 3]); impl_simd_array_conversion!(Luv, [l, u, v], white_point); impl_struct_of_array_traits!(Luv, [l, u, v], white_point); impl_eq!(Luv, [l, u, v]); impl_copy_clone!(Luv, [l, u, v], white_point); #[allow(deprecated)] impl crate::RelativeContrast for Luv where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Wp: WhitePoint, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_rand_traits_cartesian!( UniformLuv, Luv { l => [|x| x * T::from_f64(100.0)], u => [|x| x * T::from_f64(260.0) - T::from_f64(84.0)], v => [|x| x * T::from_f64(243.0) - T::from_f64(135.0)] } phantom: white_point: PhantomData where T: Real + core::ops::Sub + core::ops::Mul ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Luv where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Luv where T: bytemuck::Pod {} #[cfg(test)] mod test { use super::Luv; use crate::white_point::D65; test_convert_into_from_xyz!(Luv); #[cfg(feature = "approx")] mod conversion { use crate::{FromColor, LinSrgb, Luv}; #[test] fn red() { let u = Luv::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let v = Luv::new(53.237116, 175.0098, 37.7650); assert_relative_eq!(u, v, epsilon = 0.01); } #[test] fn green() { let u = Luv::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let v = Luv::new(87.73703, -83.07975, 107.40136); assert_relative_eq!(u, v, epsilon = 0.01); } #[test] fn blue() { let u = Luv::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let v = Luv::new(32.30087, -9.40241, -130.35109); assert_relative_eq!(u, v, epsilon = 0.01); } } #[test] fn ranges() { assert_ranges! { Luv; clamped { l: 0.0 => 100.0, u: -84.0 => 176.0, v: -135.0 => 108.0 } clamped_min {} unclamped {} } } /// Check that the arithmetic operations (add/sub) are all /// implemented. #[test] fn test_arithmetic() { let luv = Luv::::new(120.0, 40.0, 30.0); let luv2 = Luv::new(200.0, 30.0, 40.0); let mut _luv3 = luv + luv2; _luv3 += luv2; let mut _luv4 = luv2 + 0.3; _luv4 += 0.1; _luv3 = luv2 - luv; _luv3 = _luv4 - 0.1; _luv4 -= _luv3; _luv3 -= 0.1; } raw_pixel_conversion_tests!(Luv: l, u, v); raw_pixel_conversion_fail_tests!(Luv: l, u, v); #[test] fn check_min_max_components() { assert_eq!(Luv::::min_l(), 0.0); assert_eq!(Luv::::min_u(), -84.0); assert_eq!(Luv::::min_v(), -135.0); assert_eq!(Luv::::max_l(), 100.0); assert_eq!(Luv::::max_u(), 176.0); assert_eq!(Luv::::max_v(), 108.0); } struct_of_arrays_tests!( Luv[l, u, v] phantom: white_point, super::Luva::new(0.1f32, 0.2, 0.3, 0.4), super::Luva::new(0.2, 0.3, 0.4, 0.5), super::Luva::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Luv::::new(80.0, 20.0, 30.0)).unwrap(); assert_eq!(serialized, r#"{"l":80.0,"u":20.0,"v":30.0}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Luv = ::serde_json::from_str(r#"{"l":80.0,"u":20.0,"v":30.0}"#).unwrap(); assert_eq!(deserialized, Luv::new(80.0, 20.0, 30.0)); } test_uniform_distribution! { Luv { l: (0.0, 100.0), u: (-84.0, 176.0), v: (-135.0, 108.0) }, min: Luv::new(0.0f32, -84.0, -135.0), max: Luv::new(100.0, 176.0, 108.0) } } palette-0.7.5/src/luv_bounds.rs000064400000000000000000000121601046102023000146100ustar 00000000000000//! Utility functions for computing in-gamut regions for CIELuv color space. use crate::{ angle::RealAngle, num::{Abs, Powi, Real, Sqrt, Trigonometry}, LuvHue, }; /// Boundary line in the u-v plane of the Luv color space. struct BoundaryLine { slope: f64, intercept: f64, } impl BoundaryLine { /// Given array starting at the origin at angle theta, determine /// the signed length at which the ray intersects with the /// boundary. fn intersect_length_at_angle(&self, theta: f64) -> Option { let (sin_theta, cos_theta) = Trigonometry::sin_cos(theta); let denom = sin_theta - self.slope * cos_theta; if denom.abs() > 1.0e-6 { Some(self.intercept / denom) } else { None } } /// Return the distance from this line to the origin. #[allow(unused)] fn distance_to_origin(&self) -> f64 { Abs::abs(self.intercept) / Sqrt::sqrt(self.slope * self.slope + 1.0) } } /// `LuvBounds` represents the convex polygon formed by the in-gamut /// region in the uv plane at a given lightness. pub(crate) struct LuvBounds { bounds: [BoundaryLine; 6], } const M: [[f64; 3]; 3] = [ [3.240969941904521, -1.537383177570093, -0.498610760293], [-0.96924363628087, 1.87596750150772, 0.041555057407175], [0.055630079696993, -0.20397695888897, 1.056971514242878], ]; const KAPPA: f64 = 903.2962962; const EPSILON: f64 = 0.0088564516; impl LuvBounds { pub fn from_lightness(l: T) -> Self where T: Into + Powi, { let l: f64 = l.into(); let sub1 = (l + 16.0).powi(3) / 1560896.0; let sub2 = if sub1 > EPSILON { sub1 } else { l / KAPPA }; let line = |c: usize, t: f64| { let m: &[f64; 3] = &M[c]; let top1 = (284517.0 * m[0] - 94839.0 * m[2]) * sub2; let top2 = (838422.0 * m[2] + 769860.0 * m[1] + 731718.0 * m[0]) * l * sub2 - 769860.0 * t * l; let bottom = (632260.0 * m[2] - 126452.0 * m[1]) * sub2 + 126452.0 * t; BoundaryLine { slope: top1 / bottom, intercept: top2 / bottom, } }; Self { bounds: [ line(0, 0.0), line(0, 1.0), line(1, 0.0), line(1, 1.0), line(2, 0.0), line(2, 1.0), ], } } /// Given a particular hue, return the distance to the boundary at /// the angle determined by the hue. pub fn max_chroma_at_hue + RealAngle>(&self, hue: LuvHue) -> T { let mut min_chroma = f64::MAX; let h = hue.into_raw_radians().into(); // minimize the distance across all individual boundaries for b in &self.bounds { if let Some(t) = b.intersect_length_at_angle(h) { if t >= 0.0 && min_chroma > t { min_chroma = t; } } } T::from_f64(min_chroma) } /// Return the minimum chroma such that, at any hue, the chroma is /// in-gamut. /// /// This is equivalent to finding the minimum distance to the /// origin across all boundaries. /// /// # Remarks /// This is useful for a n HPLuv implementation. #[allow(unused)] pub fn max_safe_chroma(&self) -> T where T: Real, { let mut min_dist = f64::MAX; // minimize the distance across all individual boundaries for b in &self.bounds { let d = b.distance_to_origin(); if min_dist > d { min_dist = d; } } T::from_f64(min_dist) } } #[cfg(feature = "approx")] #[cfg(test)] mod tests { use super::BoundaryLine; #[test] fn boundary_intersect() { let line = BoundaryLine { slope: -1.0, intercept: 1.0, }; assert_relative_eq!(line.intersect_length_at_angle(0.0).unwrap(), 1.0); assert_relative_eq!( line.intersect_length_at_angle(core::f64::consts::FRAC_PI_4) .unwrap(), core::f64::consts::FRAC_1_SQRT_2 ); assert_eq!( line.intersect_length_at_angle(-core::f64::consts::FRAC_PI_4), None ); let line = BoundaryLine { slope: 0.0, intercept: 2.0, }; assert_eq!(line.intersect_length_at_angle(0.0), None); assert_relative_eq!( line.intersect_length_at_angle(core::f64::consts::FRAC_PI_2) .unwrap(), 2.0 ); assert_relative_eq!( line.intersect_length_at_angle(2.0 * core::f64::consts::FRAC_PI_3) .unwrap(), 4.0 / 3.0f64.sqrt() ); } #[test] fn line_distance() { let line = BoundaryLine { slope: 0.0, intercept: 2.0, }; assert_relative_eq!(line.distance_to_origin(), 2.0); let line = BoundaryLine { slope: 1.0, intercept: 2.0, }; assert_relative_eq!(line.distance_to_origin(), core::f64::consts::SQRT_2); } } palette-0.7.5/src/macros/arithmetics.rs000064400000000000000000000213631046102023000162350ustar 00000000000000macro_rules! impl_color_add { ($self_ty: ident , [$($element: ident),+]) => { impl_color_add!($self_ty<>, [$($element),+]); }; ($self_ty: ident < $($ty_param: ident),* > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($ty_param,)* T> core::ops::Add for $self_ty<$($ty_param,)* T> where T: core::ops::Add { type Output = Self; fn add(self, other: Self) -> Self::Output { $self_ty { $($element: self.$element + other.$element,)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::Add for $self_ty<$($ty_param,)* T> where T: core::ops::Add + Clone { type Output = Self; fn add(self, c: T) -> Self::Output { $self_ty { $($element: self.$element + c.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::AddAssign for $self_ty<$($ty_param,)* T> where T: core::ops::AddAssign, { fn add_assign(&mut self, other: Self) { $( self.$element += other.$element; )+ } } impl<$($ty_param,)* T> core::ops::AddAssign for $self_ty<$($ty_param,)* T> where T: core::ops::AddAssign + Clone { fn add_assign(&mut self, c: T) { $( self.$element += c.clone(); )+ } } impl<$($ty_param,)* T> $crate::num::SaturatingAdd for $self_ty<$($ty_param,)* T> where T: $crate::num::SaturatingAdd { type Output = Self; fn saturating_add(self, other: Self) -> Self::Output { $self_ty { $($element: self.$element.saturating_add(other.$element),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> $crate::num::SaturatingAdd for $self_ty<$($ty_param,)* T> where T: $crate::num::SaturatingAdd + Clone { type Output = Self; fn saturating_add(self, c: T) -> Self::Output { $self_ty { $($element: self.$element.saturating_add(c.clone()),)+ $($phantom: core::marker::PhantomData,)? } } } }; } /// Implement `Sub` and `SubAssign` traits for a color space. /// /// Both scalars and color arithmetic are implemented. macro_rules! impl_color_sub { ($self_ty: ident , [$($element: ident),+]) => { impl_color_sub!($self_ty<>, [$($element),+]); }; ($self_ty: ident < $($ty_param: ident),* > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($ty_param,)* T> core::ops::Sub for $self_ty<$($ty_param,)* T> where T: core::ops::Sub { type Output = Self; fn sub(self, other: Self) -> Self::Output { $self_ty { $($element: self.$element - other.$element,)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::Sub for $self_ty<$($ty_param,)* T> where T: core::ops::Sub + Clone { type Output = Self; fn sub(self, c: T) -> Self::Output { $self_ty { $($element: self.$element - c.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::SubAssign for $self_ty<$($ty_param,)* T> where T: core::ops::SubAssign, { fn sub_assign(&mut self, other: Self) { $( self.$element -= other.$element; )+ } } impl<$($ty_param,)* T> core::ops::SubAssign for $self_ty<$($ty_param,)* T> where T: core::ops::SubAssign + Clone { fn sub_assign(&mut self, c: T) { $( self.$element -= c.clone(); )+ } } impl<$($ty_param,)* T> $crate::num::SaturatingSub for $self_ty<$($ty_param,)* T> where T: $crate::num::SaturatingSub { type Output = Self; fn saturating_sub(self, other: Self) -> Self::Output { $self_ty { $($element: self.$element.saturating_sub(other.$element),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> $crate::num::SaturatingSub for $self_ty<$($ty_param,)* T> where T: $crate::num::SaturatingSub + Clone { type Output = Self; fn saturating_sub(self, c: T) -> Self::Output { $self_ty { $($element: self.$element.saturating_sub(c.clone()),)+ $($phantom: core::marker::PhantomData,)? } } } }; } /// Implement `Mul` and `MulAssign` traits for a color space. /// /// Both scalars and color arithmetic are implemented. macro_rules! impl_color_mul { ($self_ty: ident , [$($element: ident),+]) => { impl_color_mul!($self_ty<>, [$($element),+]); }; ($self_ty: ident < $($ty_param: ident),* > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($ty_param,)* T> core::ops::Mul for $self_ty<$($ty_param,)* T> where T: core::ops::Mul { type Output = Self; fn mul(self, other: Self) -> Self::Output { $self_ty { $($element: self.$element * other.$element,)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::Mul for $self_ty<$($ty_param,)* T> where T: core::ops::Mul + Clone { type Output = Self; fn mul(self, c: T) -> Self::Output { $self_ty { $($element: self.$element * c.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::MulAssign for $self_ty<$($ty_param,)* T> where T: core::ops::MulAssign, { fn mul_assign(&mut self, other: Self) { $( self.$element *= other.$element; )+ } } impl<$($ty_param,)* T> core::ops::MulAssign for $self_ty<$($ty_param,)* T> where T: core::ops::MulAssign + Clone { fn mul_assign(&mut self, c: T) { $( self.$element *= c.clone(); )+ } } }; } /// Implement `Div` and `DivAssign` traits for a color space. /// /// Both scalars and color arithmetic are implemented. macro_rules! impl_color_div { ($self_ty: ident , [$($element: ident),+]) => { impl_color_div!($self_ty<>, [$($element),+]); }; ($self_ty: ident < $($ty_param: ident),* > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($ty_param,)* T> core::ops::Div for $self_ty<$($ty_param,)* T> where T: core::ops::Div { type Output = Self; fn div(self, other: Self) -> Self::Output { $self_ty { $($element: self.$element / other.$element,)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::Div for $self_ty<$($ty_param,)* T> where T: core::ops::Div + Clone { type Output = Self; fn div(self, c: T) -> Self::Output { $self_ty { $($element: self.$element / c.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T> core::ops::DivAssign for $self_ty<$($ty_param,)* T> where T: core::ops::DivAssign, { fn div_assign(&mut self, other: Self) { $( self.$element /= other.$element; )+ } } impl<$($ty_param,)* T> core::ops::DivAssign for $self_ty<$($ty_param,)* T> where T: core::ops::DivAssign + Clone { fn div_assign(&mut self, c: T) { $( self.$element /= c.clone(); )+ } } }; } palette-0.7.5/src/macros/blend.rs000064400000000000000000000043261046102023000150050ustar 00000000000000macro_rules! impl_premultiply { ($ty: ident {$($component: ident),+} $(phantom: $phantom: ident)? $(where $($where: tt)+)?) => { impl_premultiply!($ty<> {$($component),+} $(phantom: $phantom)? $(where $($where)+)?); }; ($ty: ident <$($ty_param: ident),*> {$($component: ident),+} $(phantom: $phantom: ident)? $(where $($where: tt)+)?) => { impl<$($ty_param,)* T> crate::blend::Premultiply for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::stimulus::Stimulus + crate::num::Zero + crate::num::IsValidDivisor + core::ops::Mul + core::ops::Div + Clone, T::Mask: crate::bool_mask::LazySelect + Clone, $($($where)+)? { type Scalar = T; #[inline] fn premultiply(self, alpha: T) -> crate::blend::PreAlpha { crate::blend::PreAlpha { color: self * alpha.clone(), alpha } } #[inline] fn unpremultiply(premultiplied: crate::blend::PreAlpha) -> (Self, T) { let crate::blend::PreAlpha { color: $ty { $($component,)+ .. }, alpha, } = premultiplied; let is_valid_divisor = alpha.is_valid_divisor(); let color = Self { $( $component: lazy_select! { if is_valid_divisor.clone() => $component / alpha.clone(), else => T::zero() }, )+ $($phantom: core::marker::PhantomData,)? }; (color, alpha) } } impl<$($ty_param,)* T> From> for $ty<$($ty_param,)* T> where Self: crate::blend::Premultiply, { fn from(premultiplied: crate::blend::PreAlpha) -> Self { use crate::blend::Premultiply; Self::unpremultiply(premultiplied).0 } } }; } palette-0.7.5/src/macros/casting.rs000064400000000000000000000321521046102023000153470ustar 00000000000000#[cfg(test)] macro_rules! raw_pixel_conversion_tests { ($name: ident <$($ty_param: path),+> : $($component: ident),+) => { #[test] fn convert_from_f32_array() { raw_pixel_conversion_tests!(@float_array_test f32, $name<$($ty_param),+>: $($component),+); } #[test] fn convert_from_f64_array() { raw_pixel_conversion_tests!(@float_array_test f64, $name<$($ty_param),+>: $($component),+); } #[test] fn convert_from_f32_slice() { raw_pixel_conversion_tests!(@float_slice_test f32, $name<$($ty_param),+>: $($component),+); } #[test] fn convert_from_f64_slice() { raw_pixel_conversion_tests!(@float_slice_test f64, $name<$($ty_param),+>: $($component),+); } }; (@float_array_test $float: ty, $name: ident <$($ty_param: path),+> : $($component: ident),+) => { use crate::cast::ArrayCast; use crate::Alpha; let mut counter: $float = 0.0; $( counter += 0.1; let $component = counter; )+ let alpha = counter + 0.1; let raw: <$name<$($ty_param,)+ $float> as ArrayCast>::Array = [$($component),+]; let raw_plus_1: , $float> as ArrayCast>::Array = [ $($component,)+ alpha ]; let color: $name<$($ty_param,)+ $float> = crate::cast::from_array(raw); let color_alpha: Alpha<$name<$($ty_param,)+ $float>, $float> = crate::cast::from_array(raw_plus_1); assert_eq!(color, $name::new($($component),+)); assert_eq!(color_alpha, Alpha::<$name<$($ty_param,)+ $float>, $float>::new($($component,)+ alpha)); }; (@float_slice_test $float: ty, $name: ident <$($ty_param: path),+> : $($component: ident),+) => { use core::convert::{TryInto, TryFrom}; use crate::Alpha; let mut counter: $float = 0.0; $( counter += 0.1; let $component = counter; )+ let alpha = counter + 0.1; let extra = counter + 0.2; let raw: &[$float] = &[$($component),+]; let raw_plus_1: &[$float] = &[ $($component,)+ alpha ]; let raw_plus_2: &[$float] = &[ $($component,)+ alpha, extra ]; let color: &$name<$($ty_param,)+ $float> = raw.try_into().unwrap(); assert!(<&$name<$($ty_param,)+ $float>>::try_from(raw_plus_1).is_err()); let color_alpha: &Alpha<$name<$($ty_param,)+ $float>, $float> = raw_plus_1.try_into().unwrap(); assert!(<&Alpha<$name<$($ty_param,)+ $float>, $float>>::try_from(raw_plus_2).is_err()); assert_eq!(color, &$name::new($($component),+)); assert_eq!(color_alpha, &Alpha::<$name<$($ty_param,)+ $float>, $float>::new($($component,)+ alpha)); }; } #[cfg(test)] macro_rules! raw_pixel_conversion_fail_tests { ($name: ident <$($ty_param: path),+> : $($component: ident),+) => { #[test] #[should_panic(expected = "TryFromSliceError")] fn convert_from_short_f32_slice() { raw_pixel_conversion_fail_tests!(@float_slice_test f32, $name<$($ty_param),+>); } #[test] #[should_panic(expected = "TryFromSliceError")] fn convert_from_short_f64_slice() { raw_pixel_conversion_fail_tests!(@float_slice_test f64, $name<$($ty_param),+>); } }; (@float_slice_test $float: ty, $name: ident <$($ty_param: path),+>) => { use core::convert::TryInto; let raw: &[$float] = &[0.1]; let _: &$name<$($ty_param,)+ $float> = raw.try_into().unwrap(); }; } macro_rules! impl_array_casts { ($self_ty: ident < $($ty_param: ident),+ > $($rest: tt)*) => { impl_array_casts!([$($ty_param),+] $self_ty < $($ty_param),+ > $($rest)*); }; ([$($ty_param: tt)+] $self_ty: ident < $($self_ty_param: ty),+ > , [$array_item: ty; $array_len: expr] $(, where $($where: tt)+)?) => { impl<$($ty_param)+> AsRef<[$array_item; $array_len]> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn as_ref(&self) -> &[$array_item; $array_len] { crate::cast::into_array_ref(self) } } impl<$($ty_param)+> AsRef<$self_ty<$($self_ty_param),+>> for [$array_item; $array_len] $(where $($where)+)? { #[inline] fn as_ref(&self) -> &$self_ty<$($self_ty_param),+> { crate::cast::from_array_ref(self) } } impl<$($ty_param)+> AsMut<[$array_item; $array_len]> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn as_mut(&mut self) -> &mut [$array_item; $array_len] { crate::cast::into_array_mut(self) } } impl<$($ty_param)+> AsMut<$self_ty<$($self_ty_param),+>> for [$array_item; $array_len] $(where $($where)+)? { #[inline] fn as_mut(&mut self) -> &mut $self_ty<$($self_ty_param),+> { crate::cast::from_array_mut(self) } } impl<$($ty_param)+> AsRef<[$array_item]> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn as_ref(&self) -> &[$array_item] { &*AsRef::<[$array_item; $array_len]>::as_ref(self) } } impl<$($ty_param)+> AsMut<[$array_item]> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn as_mut(&mut self) -> &mut [$array_item] { &mut *AsMut::<[$array_item; $array_len]>::as_mut(self) } } impl<$($ty_param)+> From<$self_ty<$($self_ty_param),+>> for [$array_item; $array_len] $(where $($where)+)? { #[inline] fn from(color: $self_ty<$($self_ty_param),+>) -> Self { crate::cast::into_array(color) } } impl<$($ty_param)+> From<[$array_item; $array_len]> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn from(array: [$array_item; $array_len]) -> Self { crate::cast::from_array(array) } } impl<'a, $($ty_param)+> From<&'a $self_ty<$($self_ty_param),+>> for &'a [$array_item; $array_len] $(where $($where)+)? { #[inline] fn from(color: &'a $self_ty<$($self_ty_param),+>) -> Self { color.as_ref() } } impl<'a, $($ty_param)+> From<&'a [$array_item; $array_len]> for &'a $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn from(array: &'a [$array_item; $array_len]) -> Self{ array.as_ref() } } impl<'a, $($ty_param)+> From<&'a $self_ty<$($self_ty_param),+>> for &'a [$array_item] $(where $($where)+)? { #[inline] fn from(color: &'a $self_ty<$($self_ty_param),+>) -> Self { color.as_ref() } } impl<'a, $($ty_param)+> core::convert::TryFrom<&'a [$array_item]> for &'a $self_ty<$($self_ty_param),+> $(where $($where)+)? { type Error = <&'a [$array_item; $array_len] as core::convert::TryFrom<&'a [$array_item]>>::Error; #[inline] fn try_from(slice: &'a [$array_item]) -> Result { use core::convert::TryInto; slice.try_into().map(crate::cast::from_array_ref) } } impl<'a, $($ty_param)+> From<&'a mut $self_ty<$($self_ty_param),+>> for &'a mut [$array_item; $array_len] $(where $($where)+)? { #[inline] fn from(color: &'a mut $self_ty<$($self_ty_param),+>) -> Self { color.as_mut() } } impl<'a, $($ty_param)+> From<&'a mut [$array_item; $array_len]> for &'a mut $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn from(array: &'a mut [$array_item; $array_len]) -> Self{ array.as_mut() } } impl<'a, $($ty_param)+> From<&'a mut $self_ty<$($self_ty_param),+>> for &'a mut [$array_item] $(where $($where)+)? { #[inline] fn from(color: &'a mut $self_ty<$($self_ty_param),+>) -> Self { color.as_mut() } } impl<'a, $($ty_param)+> core::convert::TryFrom<&'a mut [$array_item]> for &'a mut $self_ty<$($self_ty_param),+> $(where $($where)+)? { type Error = <&'a mut [$array_item; $array_len] as core::convert::TryFrom<&'a mut [$array_item]>>::Error; #[inline] fn try_from(slice: &'a mut [$array_item]) -> Result { use core::convert::TryInto; slice.try_into().map(crate::cast::from_array_mut) } } #[cfg(feature = "alloc")] impl<$($ty_param)+> From>> for alloc::boxed::Box<[$array_item; $array_len]> $(where $($where)+)? { #[inline] fn from(color: alloc::boxed::Box<$self_ty<$($self_ty_param),+>>) -> Self { crate::cast::into_array_box(color) } } #[cfg(feature = "alloc")] impl<$($ty_param)+> From> for alloc::boxed::Box<$self_ty<$($self_ty_param),+>> $(where $($where)+)? { #[inline] fn from(array: alloc::boxed::Box<[$array_item; $array_len]>) -> Self{ crate::cast::from_array_box(array) } } } } macro_rules! impl_uint_casts_self { ($self_ty: ident < $($ty_param: ident),+ > $($rest: tt)*) => { impl_uint_casts_self!([$($ty_param),+] $self_ty < $($ty_param),+ > $($rest)*); }; ([$($ty_param: tt)+] $self_ty: ident < $($self_ty_param: ty),+ >, $uint: ty $(, where $($where: tt)+)?) => { impl<$($ty_param)+> AsRef<$uint> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn as_ref(&self) -> &$uint { crate::cast::into_uint_ref(self) } } impl<$($ty_param)+> AsMut<$uint> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn as_mut(&mut self) -> &mut $uint { crate::cast::into_uint_mut(self) } } impl<$($ty_param)+> From<$uint> for $self_ty<$($self_ty_param),+> $(where $($where)+)? { #[inline] fn from(uint: $uint) -> Self { crate::cast::from_uint(uint) } } impl<'a, $($ty_param)+> From<&'a $uint> for &'a $self_ty<$($self_ty_param),+> where $uint: AsRef<$self_ty<$($self_ty_param),+>> $(, $($where)+)? { #[inline] fn from(uint: &'a $uint) -> Self{ uint.as_ref() } } impl<'a, $($ty_param)+> From<&'a mut $uint> for &'a mut $self_ty<$($self_ty_param),+> where $uint: AsMut<$self_ty<$($self_ty_param),+>> $(, $($where)+)? { #[inline] fn from(uint: &'a mut $uint) -> Self{ uint.as_mut() } } } } macro_rules! impl_uint_casts_other { /* ($self_ty: ident < $($ty_param: ident),+ > $($rest: tt)*) => { impl_uint_casts_other!([$($ty_param),+] $self_ty < $($ty_param),+ > $($rest)*); }; */ ([$($ty_param: ident),+] $self_ty: ident < $($self_ty_param: ty),+ >, $uint: ty $(, where $($where: tt)+)?) => { impl<$($ty_param)+> AsRef<$self_ty<$($self_ty_param),+>> for $uint $(where $($where)+)? { #[inline] fn as_ref(&self) -> &$self_ty<$($self_ty_param),+> { crate::cast::from_uint_ref(self) } } impl<$($ty_param)+> AsMut<$self_ty<$($self_ty_param),+>> for $uint $(where $($where)+)? { #[inline] fn as_mut(&mut self) -> &mut $self_ty<$($self_ty_param),+> { crate::cast::from_uint_mut(self) } } impl<$($ty_param)+> From<$self_ty<$($self_ty_param),+>> for $uint $(where $($where)+)? { #[inline] fn from(color: $self_ty<$($self_ty_param),+>) -> Self { crate::cast::into_uint(color) } } impl<'a, $($ty_param)+> From<&'a $self_ty<$($self_ty_param),+>> for &'a $uint $(where $($where)+)? { #[inline] fn from(color: &'a $self_ty<$($self_ty_param),+>) -> Self { color.as_ref() } } impl<'a, $($ty_param)+> From<&'a mut $self_ty<$($self_ty_param),+>> for &'a mut $uint $(where $($where)+)? { #[inline] fn from(color: &'a mut $self_ty<$($self_ty_param),+>) -> Self { color.as_mut() } } } } palette-0.7.5/src/macros/clamp.rs000064400000000000000000000300221046102023000150050ustar 00000000000000macro_rules! impl_is_within_bounds { ( $ty: ident {$($component: ident => [$get_min: expr, $get_max: expr]),+} $(where $($where: tt)+)? ) => { // add empty generics brackets impl_is_within_bounds!($ty<> {$($component => [$get_min, $get_max]),+} $(where $($where)+)?); }; ( $ty: ident <$($ty_param: ident),*> {$($component: ident => [$get_min: expr, $get_max: expr]),+} $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::IsWithinBounds for $ty<$($ty_param,)* T> where T: crate::num::PartialCmp, T::Mask: core::ops::BitAnd, $($($where)+)? { #[inline] fn is_within_bounds(&self) -> T::Mask { $( self.$component.gt_eq(&$get_min) & Option::from($get_max).map_or(crate::BoolMask::from_bool(true), |max|self.$component.lt_eq(&max)) )&+ } } }; } macro_rules! impl_is_within_bounds_hwb { ( $ty: ident $(where $($where: tt)+)? ) => { // add empty generics brackets impl_is_within_bounds_hwb!($ty<> $(where $($where)+)?); }; ( $ty: ident <$($ty_param: ident),*> $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::IsWithinBounds for $ty<$($ty_param,)* T> where T: crate::num::PartialCmp + core::ops::Add + Clone, T::Mask: core::ops::BitAnd, $($($where)+)? { #[inline] fn is_within_bounds(&self) -> T::Mask { self.blackness.gt_eq(&Self::min_blackness()) & self.blackness.lt_eq(&Self::max_blackness()) & self.whiteness.gt_eq(&Self::min_whiteness()) & self.whiteness.lt_eq(&Self::max_blackness()) & (self.whiteness.clone() + self.blackness.clone()).lt_eq(&T::max_intensity()) } } }; } macro_rules! _clamp_value { ($value: expr, $min: expr) => { crate::clamp_min($value, $min) }; ($value: expr, $min: expr, $max: expr) => { crate::clamp($value, $min, $max) }; (@assign $value: expr, $min: expr) => { crate::clamp_min_assign($value, $min) }; (@assign $value: expr, $min: expr, $max: expr) => { crate::clamp_assign($value, $min, $max) }; } macro_rules! impl_clamp { ( $ty: ident {$($component: ident => [$get_min: expr $(, $get_max: expr)?]),+} $(other {$($other: ident),+})? $(where $($where: tt)+)? ) => { // add empty generics brackets impl_clamp!($ty<> {$($component => [$get_min $(, $get_max)?]),+} $(other {$($other),+})? $(where $($where)+)?); }; ( $ty: ident <$($ty_param: ident),*> {$($component: ident => [$get_min: expr $(, $get_max: expr)?]),+} $(other {$($other: ident),+})? $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::Clamp for $ty<$($ty_param,)* T> where T: crate::num::Clamp, $($($where)+)? { #[inline] fn clamp(self) -> Self { Self { $($component: _clamp_value!(self.$component, $get_min $(, $get_max)?),)+ $($($other: self.$other,)+)? } } } impl<$($ty_param,)* T> crate::ClampAssign for $ty<$($ty_param,)* T> where T: crate::num::ClampAssign, $($($where)+)? { #[inline] fn clamp_assign(&mut self) { $(_clamp_value!(@assign &mut self.$component, $get_min $(, $get_max)?);)+ } } }; } macro_rules! impl_clamp_hwb { ( $ty: ident $(phantom: $phantom: ident)? $(where $($where: tt)+)? ) => { // add empty generics brackets impl_clamp_hwb!($ty<> $(phantom: $phantom)? $(where $($where)+)?); }; ( $ty: ident <$($ty_param: ident),*> $(phantom: $phantom: ident)? $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::Clamp for $ty<$($ty_param,)* T> where T: crate::num::One + crate::num::Clamp + crate::num::PartialCmp + core::ops::Add + core::ops::DivAssign + Clone, T::Mask: crate::bool_mask::Select, $($($where)+)? { #[inline] fn clamp(self) -> Self { let mut whiteness = crate::clamp_min(self.whiteness.clone(), Self::min_whiteness()); let mut blackness = crate::clamp_min(self.blackness.clone(), Self::min_blackness()); let sum = self.blackness + self.whiteness; let divisor = sum.gt(&T::max_intensity()).select(sum, T::one()); whiteness /= divisor.clone(); blackness /= divisor; Self {hue: self.hue, whiteness, blackness $(, $phantom: self.$phantom)?} } } impl<$($ty_param,)* T> crate::ClampAssign for $ty<$($ty_param,)* T> where T: crate::num::One + crate::num::ClampAssign + crate::num::PartialCmp + core::ops::Add + core::ops::DivAssign + Clone, T::Mask: crate::bool_mask::Select, $($($where)+)? { #[inline] fn clamp_assign(&mut self) { crate::clamp_min_assign(&mut self.whiteness, Self::min_whiteness()); crate::clamp_min_assign(&mut self.blackness, Self::min_blackness()); let sum = self.blackness.clone() + self.whiteness.clone(); let divisor = sum.gt(&T::max_intensity()).select(sum, T::one()); self.whiteness /= divisor.clone(); self.blackness /= divisor; } } }; } //Helper macro for checking ranges and clamping. #[cfg(test)] macro_rules! assert_ranges { (@make_tuple $first:pat, $next:ident,) => (($first, $next)); (@make_tuple $first:pat, $next:ident, $($rest:ident,)*) => ( assert_ranges!(@make_tuple ($first, $next), $($rest,)*) ); ( $ty:ident < $($ty_params:ty),+ >; clamped {$($clamped:ident: $clamped_from:expr => $clamped_to:expr),+} clamped_min {$($clamped_min:ident: $clamped_min_from:expr => $clamped_min_to:expr),*} unclamped {$($unclamped:ident: $unclamped_from:expr => $unclamped_to:expr),*} ) => ( { use core::iter::repeat; use crate::{Clamp, IsWithinBounds}; { print!("checking below clamp bounds... "); $( let from = $clamped_from; let to = $clamped_to; let diff = to - from; let $clamped = (1..11).map(|i| from - (i as f64 / 10.0) * diff); )+ $( let from = $clamped_min_from; let to = $clamped_min_to; let diff = to - from; let $clamped_min = (1..11).map(|i| from - (i as f64 / 10.0) * diff); )* $( let from = $unclamped_from; let to = $unclamped_to; let diff = to - from; let $unclamped = (1..11).map(|i| from - (i as f64 / 10.0) * diff); )* for assert_ranges!(@make_tuple (), $($clamped,)+ $($clamped_min,)* $($unclamped,)* ) in repeat(()) $(.zip($clamped))+ $(.zip($clamped_min))* $(.zip($unclamped))* { let color: $ty<$($ty_params),+> = $ty { $($clamped: $clamped.into(),)+ $($clamped_min: $clamped_min.into(),)* $($unclamped: $unclamped.into(),)* ..$ty::default() //This prevents exhaustiveness checking }; let clamped = color.clamp(); let expected: $ty<$($ty_params),+> = $ty { $($clamped: $clamped_from.into(),)+ $($clamped_min: $clamped_min_from.into(),)* $($unclamped: $unclamped.into(),)* ..$ty::default() //This prevents exhaustiveness checking }; assert!(!color.is_within_bounds()); assert_eq!(clamped, expected); } println!("ok") } { print!("checking within clamp bounds... "); $( let from = $clamped_from; let to = $clamped_to; let diff = to - from; let $clamped = (0..11).map(|i| from + (i as f64 / 10.0) * diff); )+ $( let from = $clamped_min_from; let to = $clamped_min_to; let diff = to - from; let $clamped_min = (0..11).map(|i| from + (i as f64 / 10.0) * diff); )* $( let from = $unclamped_from; let to = $unclamped_to; let diff = to - from; let $unclamped = (0..11).map(|i| from + (i as f64 / 10.0) * diff); )* for assert_ranges!(@make_tuple (), $($clamped,)+ $($clamped_min,)* $($unclamped,)* ) in repeat(()) $(.zip($clamped))+ $(.zip($clamped_min))* $(.zip($unclamped))* { let color: $ty<$($ty_params),+> = $ty { $($clamped: $clamped.into(),)+ $($clamped_min: $clamped_min.into(),)* $($unclamped: $unclamped.into(),)* ..$ty::default() //This prevents exhaustiveness checking }; let clamped = color.clamp(); assert!(color.is_within_bounds()); assert_eq!(clamped, color); } println!("ok") } { print!("checking above clamp bounds... "); $( let from = $clamped_from; let to = $clamped_to; let diff = to - from; let $clamped = (1..11).map(|i| to + (i as f64 / 10.0) * diff); )+ $( let from = $clamped_min_from; let to = $clamped_min_to; let diff = to - from; let $clamped_min = (1..11).map(|i| to + (i as f64 / 10.0) * diff); )* $( let from = $unclamped_from; let to = $unclamped_to; let diff = to - from; let $unclamped = (1..11).map(|i| to + (i as f64 / 10.0) * diff); )* for assert_ranges!(@make_tuple (), $($clamped,)+ $($clamped_min,)* $($unclamped,)* ) in repeat(()) $(.zip($clamped))+ $(.zip($clamped_min))* $(.zip($unclamped))* { let color: $ty<$($ty_params),+> = $ty { $($clamped: $clamped.into(),)+ $($clamped_min: $clamped_min.into(),)* $($unclamped: $unclamped.into(),)* ..$ty::default() //This prevents exhaustiveness checking }; let clamped = color.clamp(); let expected: $ty<$($ty_params),+> = $ty { $($clamped: $clamped_to.into(),)+ $($clamped_min: $clamped_min.into(),)* $($unclamped: $unclamped.into(),)* ..$ty::default() //This prevents exhaustiveness checking }; assert!(!color.is_within_bounds()); assert_eq!(clamped, expected); } println!("ok") } } ); } palette-0.7.5/src/macros/color_difference.rs000064400000000000000000000042161046102023000172070ustar 00000000000000macro_rules! impl_euclidean_distance { ( $ty: ident {$($component: ident),+} $(where $($where: tt)+)? ) => { // add empty generics brackets impl_euclidean_distance!($ty<> {$($component),+} $(where $($where)+)?); }; ( $ty: ident <$($ty_param: ident),*> {$($component: ident),+} $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::color_difference::EuclideanDistance for $ty<$($ty_param,)* T> where T: crate::num::Real + core::ops::Sub + core::ops::Add + core::ops::Mul + Clone, $($($where)+)? { type Scalar = T; #[inline] fn distance_squared(self, other: Self) -> Self::Scalar { let difference = self - other; let difference_squared = difference.clone() * difference; strip_plus!($(+ difference_squared.$component)+) } } }; } macro_rules! impl_hyab { ( $ty: ident {$($components: tt)+} $(where $($where: tt)+)? ) => { // add empty generics brackets impl_hyab!($ty<> {$($components)+} $(where $($where)+)?); }; ( $ty: ident <$($ty_param: ident),*> {lightness: $lightness:ident, chroma1: $chroma1:ident, chroma2: $chroma2:ident $(,)? } $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::color_difference::HyAb for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::Abs + crate::num::Sqrt + core::ops::Sub + core::ops::Add + core::ops::Mul + Clone, $($($where)+)? { type Scalar = T; #[inline] fn hybrid_distance(self, other: Self) -> Self::Scalar { let lightness = self.$lightness - other.$lightness; let chroma1 = self.$chroma1 - other.$chroma1; let chroma2 = self.$chroma2 - other.$chroma2; lightness.abs() + (chroma1.clone() * chroma1 + chroma2.clone() * chroma2).sqrt() } } }; } palette-0.7.5/src/macros/convert.rs000064400000000000000000000063051046102023000154000ustar 00000000000000/// Check that traits for converting to and from XYZ have been implemented. #[cfg(test)] macro_rules! test_convert_into_from_xyz { ($ty:ty) => { #[test] fn convert_from_xyz() { use crate::FromColor; let _: $ty = <$ty>::from_color(crate::Xyz::default()); } #[test] fn convert_into_xyz() { use crate::FromColor; let _: crate::Xyz = crate::Xyz::from_color(<$ty>::default()); } }; } macro_rules! impl_tuple_conversion { ($ty: ident as ($($component_ty: ident),+)) => { impl_tuple_conversion!($ty<> as ($($component_ty),+)); }; ($ty: ident <$($ty_param: ident),*> as ($($component_ty: ident),+)) => { impl<$($ty_param,)* T> From<($($component_ty,)+)> for $ty<$($ty_param,)* T> { fn from(components: ($($component_ty,)+)) -> Self { Self::from_components(components) } } impl<$($ty_param,)* T> From<$ty<$($ty_param,)* T>> for ($($component_ty,)+) { fn from(color: $ty<$($ty_param,)* T>) -> ($($component_ty,)+) { color.into_components() } } impl<$($ty_param,)* T, A> From<($($component_ty,)+ A)> for crate::Alpha<$ty<$($ty_param,)* T>, A> { fn from(components: ($($component_ty,)+ A)) -> Self { Self::from_components(components) } } impl<$($ty_param,)* T, A> From, A>> for ($($component_ty,)+ A) { fn from(color: crate::Alpha<$ty<$($ty_param,)* T>, A>) -> ($($component_ty,)+ A) { color.into_components() } } }; } macro_rules! __replace_generic_hue { (H, $hue_ty: ident) => {$hue_ty}; ($other: ident, $hue_ty: ident) => {$other}; } macro_rules! impl_tuple_conversion_hue { ($ty: ident as ($($component_ty: ident),+), $hue_ty: ident) => { impl_tuple_conversion_hue!($ty<> as ($($component_ty),+), $hue_ty); }; ($ty: ident <$($ty_param: ident),*> as ($($component_ty: ident),+), $hue_ty: ident) => { impl<$($ty_param,)* T, H: Into<$hue_ty>> From<($($component_ty,)+)> for $ty<$($ty_param,)* T> { fn from(components: ($($component_ty,)+)) -> Self { Self::from_components(components) } } impl<$($ty_param,)* T> From<$ty<$($ty_param,)* T>> for ($(__replace_generic_hue!($component_ty, $hue_ty),)+) { fn from(color: $ty<$($ty_param,)* T>) -> ($(__replace_generic_hue!($component_ty, $hue_ty),)+) { color.into_components() } } impl<$($ty_param,)* T, H: Into<$hue_ty>, A> From<($($component_ty,)+ A)> for crate::Alpha<$ty<$($ty_param,)* T>, A> { fn from(components: ($($component_ty,)+ A)) -> Self { Self::from_components(components) } } impl<$($ty_param,)* T, A> From, A>> for ($(__replace_generic_hue!($component_ty, $hue_ty),)+ A) { fn from(color: crate::Alpha<$ty<$($ty_param,)* T>, A>) -> ($(__replace_generic_hue!($component_ty, $hue_ty),)+ A) { color.into_components() } } }; } palette-0.7.5/src/macros/copy_clone.rs000064400000000000000000000013571046102023000160540ustar 00000000000000macro_rules! impl_copy_clone { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_copy_clone!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($phantom_ty: ident)? > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($phantom_ty,)? T> Copy for $self_ty<$($phantom_ty,)? T> where T: Copy {} impl<$($phantom_ty,)? T> Clone for $self_ty<$($phantom_ty,)? T> where T: Clone, { fn clone(&self) -> $self_ty<$($phantom_ty,)? T> { $self_ty { $($element: self.$element.clone(),)* $($phantom: core::marker::PhantomData,)? } } } } } palette-0.7.5/src/macros/equality.rs000064400000000000000000000202071046102023000155520ustar 00000000000000macro_rules! impl_eq { ( $self_ty: ident , [$element: ident]) => { impl_eq!($self_ty<>, [$element]); }; ( $self_ty: ident < $($ty_param: ident),* > , [$element: ident]) => { impl<$($ty_param,)* T> PartialEq for $self_ty<$($ty_param,)* T> where T: PartialEq, { fn eq(&self, other: &Self) -> bool { self.$element == other.$element } } impl<$($ty_param,)* T> Eq for $self_ty<$($ty_param,)* T> where T: Eq {} #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::AbsDiffEq for $self_ty<$($ty_param,)* T> where T: approx::AbsDiffEq, { type Epsilon = T::Epsilon; fn default_epsilon() -> Self::Epsilon { T::default_epsilon() } fn abs_diff_eq(&self, other: &Self, epsilon: T::Epsilon) -> bool { self.$element.abs_diff_eq(&other.$element, epsilon) } fn abs_diff_ne(&self, other: &Self, epsilon: T::Epsilon) -> bool { self.$element.abs_diff_ne(&other.$element, epsilon) } } #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::RelativeEq for $self_ty<$($ty_param,)* T> where T: approx::RelativeEq, { fn default_max_relative() -> T::Epsilon { T::default_max_relative() } fn relative_eq(&self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon) -> bool { self.$element.relative_eq(&other.$element, epsilon, max_relative) } fn relative_ne(&self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon) -> bool { self.$element.relative_ne(&other.$element, epsilon, max_relative) } } #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::UlpsEq for $self_ty<$($ty_param,)* T> where T: approx::UlpsEq, { fn default_max_ulps() -> u32 { T::default_max_ulps() } fn ulps_eq(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool { self.$element.ulps_eq(&other.$element, epsilon, max_ulps) } fn ulps_ne(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool { self.$element.ulps_ne(&other.$element, epsilon, max_ulps) } } }; ( $self_ty: ident , [$($element: ident),+]) => { impl_eq!($self_ty<>, [$($element),+]); }; ( $self_ty: ident < $($ty_param: ident),* > , [$($element: ident),+]) => { impl<$($ty_param,)* T> PartialEq for $self_ty<$($ty_param,)* T> where T: PartialEq, { fn eq(&self, other: &Self) -> bool { $( self.$element == other.$element )&&+ } } impl<$($ty_param,)* T> Eq for $self_ty<$($ty_param,)* T> where T: Eq {} #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::AbsDiffEq for $self_ty<$($ty_param,)* T> where T: approx::AbsDiffEq, T::Epsilon: Clone, { type Epsilon = T::Epsilon; fn default_epsilon() -> Self::Epsilon { T::default_epsilon() } fn abs_diff_eq(&self, other: &Self, epsilon: T::Epsilon) -> bool { $( self.$element.abs_diff_eq(&other.$element, epsilon.clone()) )&&+ } fn abs_diff_ne(&self, other: &Self, epsilon: T::Epsilon) -> bool { $( self.$element.abs_diff_ne(&other.$element, epsilon.clone()) )||+ } } #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::RelativeEq for $self_ty<$($ty_param,)* T> where T: approx::RelativeEq, T::Epsilon: Clone, { fn default_max_relative() -> T::Epsilon { T::default_max_relative() } fn relative_eq(&self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon) -> bool { $( self.$element.relative_eq(&other.$element, epsilon.clone(), max_relative.clone()) )&&+ } fn relative_ne(&self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon) -> bool { $( self.$element.relative_ne(&other.$element, epsilon.clone(), max_relative.clone()) )||+ } } #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::UlpsEq for $self_ty<$($ty_param,)* T> where T: approx::UlpsEq, T::Epsilon: Clone, { fn default_max_ulps() -> u32 { T::default_max_ulps() } fn ulps_eq(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool { $( self.$element.ulps_eq(&other.$element, epsilon.clone(), max_ulps) )&&+ } fn ulps_ne(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool { $( self.$element.ulps_ne(&other.$element, epsilon.clone(), max_ulps) )||+ } } } } macro_rules! impl_eq_hue { ( $self_ty: ident, $hue_ty: ident, [$($element: ident),+]) => { impl_eq_hue!($self_ty<>, $hue_ty, [$($element),+]); }; ( $self_ty: ident < $($ty_param: ident),* >, $hue_ty: ident, [$($element: ident),+]) => { impl<$($ty_param,)* T> PartialEq for $self_ty<$($ty_param,)* T> where T: PartialEq, $hue_ty: PartialEq, { fn eq(&self, other: &Self) -> bool { $( self.$element == other.$element )&&+ } } impl<$($ty_param,)* T> Eq for $self_ty<$($ty_param,)* T> where T: Eq, $hue_ty: Eq, {} #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::AbsDiffEq for $self_ty<$($ty_param,)* T> where T: approx::AbsDiffEq, T::Epsilon: Clone, $hue_ty: approx::AbsDiffEq, { type Epsilon = T::Epsilon; fn default_epsilon() -> Self::Epsilon { T::default_epsilon() } fn abs_diff_eq(&self, other: &Self, epsilon: T::Epsilon) -> bool { $( self.$element.abs_diff_eq(&other.$element, epsilon.clone()) )&&+ } fn abs_diff_ne(&self, other: &Self, epsilon: T::Epsilon) -> bool { $( self.$element.abs_diff_ne(&other.$element, epsilon.clone()) )||+ } } #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::RelativeEq for $self_ty<$($ty_param,)* T> where T: approx::RelativeEq, T::Epsilon: Clone, $hue_ty: approx::RelativeEq + approx::AbsDiffEq, { fn default_max_relative() -> T::Epsilon { T::default_max_relative() } fn relative_eq(&self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon) -> bool { $( self.$element.relative_eq(&other.$element, epsilon.clone(), max_relative.clone()) )&&+ } fn relative_ne(&self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon) -> bool { $( self.$element.relative_ne(&other.$element, epsilon.clone(), max_relative.clone()) )||+ } } #[cfg(feature = "approx")] impl<$($ty_param,)* T> approx::UlpsEq for $self_ty<$($ty_param,)* T> where T: approx::UlpsEq, T::Epsilon: Clone, $hue_ty: approx::UlpsEq + approx::AbsDiffEq, { fn default_max_ulps() -> u32 { T::default_max_ulps() } fn ulps_eq(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool { $( self.$element.ulps_eq(&other.$element, epsilon.clone(), max_ulps) )&&+ } fn ulps_ne(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool { $( self.$element.ulps_ne(&other.$element, epsilon.clone(), max_ulps) )||+ } } } } palette-0.7.5/src/macros/hue.rs000064400000000000000000000033111046102023000144730ustar 00000000000000macro_rules! impl_hue_ops { ( $self_ty: ident , $hue_ty: ident) => { impl_hue_ops!($self_ty<>, $hue_ty); }; ( $self_ty: ident < $($ty_param: ident),* > , $hue_ty: ident) => { impl<$($ty_param,)* T> crate::GetHue for $self_ty<$($ty_param,)* T> where T: Clone, { type Hue = $hue_ty; #[inline] fn get_hue(&self) -> $hue_ty { self.hue.clone() } } impl<$($ty_param,)* T, H> crate::WithHue for $self_ty<$($ty_param,)* T> where H: Into<$hue_ty>, { #[inline] fn with_hue(mut self, hue: H) -> Self { self.hue = hue.into(); self } } impl<$($ty_param,)* T, H> crate::SetHue for $self_ty<$($ty_param,)* T> where H: Into<$hue_ty>, { #[inline] fn set_hue(&mut self, hue: H) { self.hue = hue.into(); } } impl<$($ty_param,)* T> crate::ShiftHue for $self_ty<$($ty_param,)* T> where T: core::ops::Add, { type Scalar = T; #[inline] fn shift_hue(mut self, amount: Self::Scalar) -> Self { self.hue = self.hue + amount; self } } impl<$($ty_param,)* T> crate::ShiftHueAssign for $self_ty<$($ty_param,)* T> where T: core::ops::AddAssign, { type Scalar = T; #[inline] fn shift_hue_assign(&mut self, amount: Self::Scalar) { self.hue += amount; } } } } palette-0.7.5/src/macros/lazy_select.rs000064400000000000000000000012101046102023000162240ustar 00000000000000/// Chains calls to `LazySelect::lazy_select` to mimic an if-else chain. /// /// ```ignore /// let result = lazy_select! { /// if predicate1 => result1, /// if predicate2 => result2, /// else => result3, /// }; /// ``` macro_rules! lazy_select { ( if $if_pred:expr => $if_body:expr, $(if $else_if_pred:expr => $else_if_body:expr,)* else => $else_body:expr $(,)?) => { crate::bool_mask::LazySelect::lazy_select( $if_pred, || $if_body, || lazy_select!($(if $else_if_pred => $else_if_body,)* else => $else_body) ) }; (else => $else_body:expr) => { $else_body } } palette-0.7.5/src/macros/lighten_saturate.rs000064400000000000000000000175421046102023000172670ustar 00000000000000macro_rules! _impl_increase_value_trait { ( $trait: ident :: {$method: ident, $method_fixed: ident}, $assign_trait: ident :: {$assign_method: ident, $assign_method_fixed: ident}, $ty: ident <$($ty_param: ident),*> increase {$($component: ident => [$get_min: expr, $get_max: expr]),+} other {$($other_component: ident),*} $(phantom: $phantom: ident)? $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::$trait for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::Zero + crate::num::MinMax + crate::num::Clamp + crate::num::Arithmetics + crate::num::PartialCmp + Clone, T::Mask: crate::bool_mask::LazySelect, $($($where)+)? { type Scalar = T; #[inline] fn $method(self, factor: T) -> Self { $( let difference = lazy_select!{ if factor.gt_eq(&T::zero()) => $get_max - &self.$component, else => self.$component.clone(), }; let $component = difference.max(T::zero()) * &factor; )+ $ty { $($other_component: self.$other_component,)* $($component: crate::clamp(self.$component + $component, $get_min, $get_max),)+ $($phantom: PhantomData,)? } } #[inline] fn $method_fixed(self, amount: T) -> Self { $ty { $($other_component: self.$other_component,)* $($component: crate::clamp(self.$component + $get_max * &amount, $get_min, $get_max),)+ $($phantom: PhantomData,)? } } } impl<$($ty_param,)* T> crate::$assign_trait for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::Zero + crate::num::MinMax + crate::num::ClampAssign + core::ops::AddAssign + crate::num::Arithmetics + crate::num::PartialCmp + Clone, T::Mask: crate::bool_mask::LazySelect, $($($where)+)? { type Scalar = T; #[inline] fn $assign_method(&mut self, factor: T) { $( let difference = lazy_select!{ if factor.gt_eq(&T::zero()) => $get_max - &self.$component, else => self.$component.clone(), }; self.$component += difference.max(T::zero()) * &factor; crate::clamp_assign(&mut self.$component, $get_min, $get_max); )+ } #[inline] fn $assign_method_fixed(&mut self, amount: T) { $( self.$component += $get_max * &amount; crate::clamp_assign(&mut self.$component, $get_min, $get_max); )+ } } }; } macro_rules! impl_lighten { ($ty: ident increase $($input: tt)+) => { impl_lighten!($ty<> increase $($input)+); }; ($($input: tt)+) => { _impl_increase_value_trait!( Lighten::{lighten, lighten_fixed}, LightenAssign::{lighten_assign, lighten_fixed_assign}, $($input)+ ); }; } macro_rules! impl_saturate { ($ty: ident increase $($input: tt)+) => { // add empty generics brackets impl_saturate!($ty<> increase $($input)+); }; ($($input: tt)+) => { _impl_increase_value_trait!( Saturate::{saturate, saturate_fixed}, SaturateAssign::{saturate_assign, saturate_fixed_assign}, $($input)+ ); }; } macro_rules! impl_lighten_hwb { ( $ty: ident $(phantom: $phantom: ident)? $(where $($where: tt)+)? ) => { impl_lighten_hwb!($ty<> $(phantom: $phantom)? $(where $($where)+ )?); }; ( $ty: ident <$($ty_param: ident),*> $(phantom: $phantom: ident)? $(where $($where: tt)+)? ) => { impl<$($ty_param,)* T> crate::Lighten for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::Zero + crate::num::MinMax + crate::num::Arithmetics + crate::num::PartialCmp + Clone, T::Mask: LazySelect, $($($where)+)? { type Scalar = T; #[inline] fn lighten(self, factor: T) -> Self { let difference_whiteness = lazy_select! { if factor.gt_eq(&T::zero()) => Self::max_whiteness() - &self.whiteness, else => self.whiteness.clone(), }; let delta_whiteness = difference_whiteness.max(T::zero()) * &factor; let difference_blackness = lazy_select! { if factor.gt_eq(&T::zero()) => self.blackness.clone(), else => Self::max_blackness() - &self.blackness, }; let delta_blackness = difference_blackness.max(T::zero()) * factor; Self { hue: self.hue, whiteness: (self.whiteness + delta_whiteness).max(Self::min_whiteness()), blackness: (self.blackness - delta_blackness).max(Self::min_blackness()), $($phantom: PhantomData,)? } } #[inline] fn lighten_fixed(self, amount: T) -> Self { Self { hue: self.hue, whiteness: (self.whiteness + Self::max_whiteness() * &amount) .max(Self::min_whiteness()), blackness: (self.blackness - Self::max_blackness() * amount).max(Self::min_blackness()), $($phantom: PhantomData,)? } } } impl<$($ty_param,)* T> crate::LightenAssign for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::Zero + crate::num::MinMax + crate::num::ClampAssign + core::ops::AddAssign + core::ops::SubAssign + crate::num::Arithmetics + crate::num::PartialCmp + Clone, T::Mask: LazySelect, $($($where)+)? { type Scalar = T; #[inline] fn lighten_assign(&mut self, factor: T) { let difference_whiteness = lazy_select! { if factor.gt_eq(&T::zero()) => Self::max_whiteness() - &self.whiteness, else => self.whiteness.clone(), }; self.whiteness += difference_whiteness.max(T::zero()) * &factor; crate::clamp_min_assign(&mut self.whiteness, Self::min_whiteness()); let difference_blackness = lazy_select! { if factor.gt_eq(&T::zero()) => self.blackness.clone(), else => Self::max_blackness() - &self.blackness, }; self.blackness -= difference_blackness.max(T::zero()) * factor; crate::clamp_min_assign(&mut self.blackness, Self::min_blackness()); } #[inline] fn lighten_fixed_assign(&mut self, amount: T) { self.whiteness += Self::max_whiteness() * &amount; crate::clamp_min_assign(&mut self.whiteness, Self::min_whiteness()); self.blackness -= Self::max_blackness() * amount; crate::clamp_min_assign(&mut self.blackness, Self::min_blackness()); } } }; } palette-0.7.5/src/macros/mix.rs000064400000000000000000000071341046102023000145160ustar 00000000000000macro_rules! impl_mix { ($ty: ident $(where $($where: tt)+)?) => { impl_mix!($ty<> $(where $($where)+)?); }; ($ty: ident <$($ty_param: ident),*> $(where $($where: tt)+)?) => { impl<$($ty_param,)* T> crate::Mix for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::Zero + crate::num::One + crate::num::Arithmetics + crate::num::Clamp + Clone, $($($where)+)? { type Scalar = T; #[inline] fn mix(self, other: Self, factor: T) -> Self { let factor = crate::clamp(factor, T::zero(), T::one()); self.clone() + (other - self) * factor } } impl<$($ty_param,)* T> crate::MixAssign for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::Zero + crate::num::One + core::ops::AddAssign + crate::num::Arithmetics + crate::num::Clamp + Clone, $($($where)+)? { type Scalar = T; #[inline] fn mix_assign(&mut self, other: Self, factor: T) { let factor = crate::clamp(factor, T::zero(), T::one()); *self += (other - self.clone()) * factor; } } }; } macro_rules! impl_mix_hue { ($ty: ident {$($other_field: ident),*} $(phantom: $phantom: ident)?) => { impl_mix_hue!($ty<> {$($other_field),*} $(phantom: $phantom)?); }; ($ty: ident <$($ty_param: ident),*> {$($other_field: ident),*} $(phantom: $phantom: ident)?) => { impl<$($ty_param,)* T> crate::Mix for $ty<$($ty_param,)* T> where T: crate::angle::RealAngle + crate::angle::SignedAngle + crate::num::Zero + crate::num::One + crate::num::Clamp + crate::num::Arithmetics + Clone, { type Scalar = T; #[inline] fn mix(self, other: Self, factor: T) -> Self { let factor = crate::clamp(factor, T::zero(), T::one()); let hue = (other.hue - self.hue.clone()).into_degrees(); $( let $other_field = other.$other_field - &self.$other_field; )* $ty { $( $other_field: self.$other_field + $other_field * &factor, )* hue: self.hue + hue * factor, $($phantom: PhantomData)? } } } impl<$($ty_param,)* T> crate::MixAssign for $ty<$($ty_param,)* T> where T: crate::angle::RealAngle + crate::angle::SignedAngle + crate::num::Zero + crate::num::One + crate::num::Clamp + core::ops::AddAssign + crate::num::Arithmetics + Clone, { type Scalar = T; #[inline] fn mix_assign(&mut self, other: Self, factor: T) { let factor = crate::clamp(factor, T::zero(), T::one()); let hue = (other.hue - self.hue.clone()).into_degrees(); $( let $other_field = other.$other_field - &self.$other_field; )* $( self.$other_field += $other_field * &factor; )* self.hue += hue * factor; } } }; } palette-0.7.5/src/macros/random.rs000064400000000000000000001061121046102023000151750ustar 00000000000000#[cfg(feature = "random")] #[cfg(test)] macro_rules! assert_uniform_distribution { ($bins:expr) => {{ let bins = &$bins; for (i, &bin) in bins.iter().enumerate() { if bin < 5 { panic!("{}[{}] < 5: {:?}", stringify!($bins), i, bins); } } const P_LIMIT: f64 = 0.01; // Keeping it low to account for the RNG noise let p_value = crate::random_sampling::test_utils::uniform_distribution_test(bins); if p_value < P_LIMIT { panic!( "distribution of {} is not uniform enough (p-value {} < {}): {:?}", stringify!($bins), p_value, P_LIMIT, bins ); } }}; } #[cfg(test)] macro_rules! test_uniform_distribution { ( $ty:path $(as $base_ty:path)? { $($component:ident: ($component_min:expr, $component_max:expr)),+$(,)? }, min: $min:expr, max: $max:expr$(,)? ) => { #[cfg(feature = "random")] #[test] fn uniform_distribution_rng_gen() { use rand::Rng; const BINS: usize = crate::random_sampling::test_utils::BINS; const SAMPLES: usize = crate::random_sampling::test_utils::SAMPLES; $(let mut $component = [0; BINS];)+ let mut rng = rand_mt::Mt::new(1234); // We want the same seed on every run to avoid random fails for _ in 0..SAMPLES { let color: $ty = rng.gen(); $(let color: $base_ty = crate::convert::IntoColorUnclamped::into_color_unclamped(color);)? if $(color.$component < $component_min || color.$component > $component_max)||+ { continue; } $({ let min: f32 = $component_min; let max: f32 = $component_max; let range = max - min; let normalized = (color.$component - min) / range; $component[((normalized * BINS as f32) as usize).min(BINS - 1)] += 1; })+ } $(assert_uniform_distribution!($component);)+ } #[cfg(feature = "random")] #[test] fn uniform_distribution_uniform_sample() { use rand::distributions::uniform::Uniform; use rand::Rng; const BINS: usize = crate::random_sampling::test_utils::BINS; const SAMPLES: usize = crate::random_sampling::test_utils::SAMPLES; $(let mut $component = [0; BINS];)+ let mut rng = rand_mt::Mt::new(1234); // We want the same seed on every run to avoid random fails let uniform_sampler = Uniform::new($min, $max); for _ in 0..SAMPLES { let color: $ty = rng.sample(&uniform_sampler); $(let color: $base_ty = crate::convert::IntoColorUnclamped::into_color_unclamped(color);)? if $(color.$component < $component_min || color.$component > $component_max)||+ { continue; } $({ let min: f32 = $component_min; let max: f32 = $component_max; let range = max - min; let normalized = (color.$component - min) / range; $component[((normalized * BINS as f32) as usize).min(BINS - 1)] += 1; })+ } $(assert_uniform_distribution!($component);)+ } #[cfg(feature = "random")] #[test] fn uniform_distribution_uniform_sample_inclusive() { use rand::distributions::uniform::Uniform; use rand::Rng; const BINS: usize = crate::random_sampling::test_utils::BINS; const SAMPLES: usize = crate::random_sampling::test_utils::SAMPLES; $(let mut $component = [0; BINS];)+ let mut rng = rand_mt::Mt::new(1234); // We want the same seed on every run to avoid random fails let uniform_sampler = Uniform::new_inclusive($min, $max); for _ in 0..SAMPLES { let color: $ty = rng.sample(&uniform_sampler); $(let color: $base_ty = crate::convert::IntoColorUnclamped::into_color_unclamped(color);)? if $(color.$component < $component_min || color.$component > $component_max)||+ { continue; } $({ let min: f32 = $component_min; let max: f32 = $component_max; let range = max - min; let normalized = (color.$component - min) / range; $component[((normalized * BINS as f32) as usize).min(BINS - 1)] += 1; })+ } $(assert_uniform_distribution!($component);)+ } }; } macro_rules! __apply_map_fn { ($value: expr) => { $value }; ($value: expr, $map_fn: expr) => { $map_fn($value) }; } macro_rules! impl_rand_traits_cartesian { ( $uniform_ty: ident, $ty: ident {$($component: ident $(=> [$map_fn: expr])?),+} $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { impl_rand_traits_cartesian!( $uniform_ty, $ty<> {$($component $( => [$map_fn])?),+} $(phantom: $phantom : PhantomData<$phantom_ty>)? $(where $($where)+)?); }; ( $uniform_ty: ident, $ty: ident <$($ty_param: ident),*> {$($component: ident $(=> [$map_fn: expr])?),+} $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::Distribution<$ty<$($ty_param,)* T>> for rand::distributions::Standard where rand::distributions::Standard: rand::distributions::Distribution, $($($where)+)? { #[allow(clippy::redundant_closure_call)] fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { $ty { $($component: __apply_map_fn!(rng.gen::() $(, $map_fn)?),)+ $($phantom: core::marker::PhantomData,)? } } } /// Samples colors uniformly. #[cfg(feature = "random")] pub struct $uniform_ty<$($ty_param,)* T> where T: rand::distributions::uniform::SampleUniform, { $($component: rand::distributions::uniform::Uniform,)+ $($phantom: core::marker::PhantomData<$phantom_ty>,)? } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::SampleUniform for $ty<$($ty_param,)* T> where T: rand::distributions::uniform::SampleUniform + Clone, { type Sampler = $uniform_ty<$($ty_param,)* T>; } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::UniformSampler for $uniform_ty<$($ty_param,)* T> where T: rand::distributions::uniform::SampleUniform + Clone, { type X = $ty<$($ty_param,)* T>; fn new(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow(); let high = high_b.borrow(); Self { $($component: rand::distributions::uniform::Uniform::new::<_, T>(low.$component.clone(), high.$component.clone()),)+ $($phantom: core::marker::PhantomData,)? } } fn new_inclusive(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow(); let high = high_b.borrow(); Self { $($component: rand::distributions::uniform::Uniform::new_inclusive::<_, T>(low.$component.clone(), high.$component.clone()),)+ $($phantom: core::marker::PhantomData,)? } } fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { use rand::distributions::Distribution; $ty { $($component: self.$component.sample(rng),)+ $($phantom: core::marker::PhantomData,)? } } } }; } macro_rules! impl_rand_traits_cylinder { ( $uniform_ty: ident, $ty: ident { hue: $hue_uniform_ty: ident => $hue_ty: ident, height: $height: ident $(=> [$height_map_fn: expr])?, radius: $radius: ident $(=> [$radius_map_fn: expr])? } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { impl_rand_traits_cylinder!( $uniform_ty, $ty<> { hue: $hue_uniform_ty => $hue_ty, height: $height $(=> [$height_map_fn])?, radius: $radius $(=> [$radius_map_fn])? } $(phantom: $phantom : PhantomData<$phantom_ty>)? $(where $($where)+)?); }; ( $uniform_ty: ident, $ty: ident <$($ty_param: ident),*> { hue: $hue_uniform_ty: ident => $hue_ty: ident, height: $height: ident $(=> [$height_map_fn: expr])?, radius: $radius: ident $(=> [$radius_map_fn: expr])? } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::Distribution<$ty<$($ty_param,)* T>> for rand::distributions::Standard where T: crate::num::Sqrt, rand::distributions::Standard: rand::distributions::Distribution + rand::distributions::Distribution<$hue_ty>, $($($where)+)? { #[allow(clippy::redundant_closure_call)] fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { $ty { hue: rng.gen::<$hue_ty>(), $height: __apply_map_fn!(rng.gen::() $(, $height_map_fn)?), $radius: __apply_map_fn!(rng.gen::().sqrt() $(, $radius_map_fn)?), $($phantom: core::marker::PhantomData,)? } } } /// Samples colors uniformly. #[cfg(feature = "random")] pub struct $uniform_ty<$($ty_param,)* T> where T: rand::distributions::uniform::SampleUniform, { hue: crate::hues::$hue_uniform_ty, $height: rand::distributions::uniform::Uniform, $radius: rand::distributions::uniform::Uniform, $($phantom: core::marker::PhantomData<$phantom_ty>,)? } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::SampleUniform for $ty<$($ty_param,)* T> where T: crate::num::Sqrt + core::ops::Mul + Clone + rand::distributions::uniform::SampleUniform, $hue_ty: rand::distributions::uniform::SampleBorrow<$hue_ty>, crate::hues::$hue_uniform_ty: rand::distributions::uniform::UniformSampler>, { type Sampler = $uniform_ty<$($ty_param,)* T>; } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::UniformSampler for $uniform_ty<$($ty_param,)* T> where T: crate::num::Sqrt + core::ops::Mul + Clone + rand::distributions::uniform::SampleUniform, $hue_ty: rand::distributions::uniform::SampleBorrow<$hue_ty>, crate::hues::$hue_uniform_ty: rand::distributions::uniform::UniformSampler>, { type X = $ty<$($ty_param,)* T>; fn new(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); $uniform_ty { $height: rand::distributions::uniform::Uniform::new::<_, T>(low.$height, high.$height), $radius: rand::distributions::uniform::Uniform::new::<_, T>( low.$radius.clone() * low.$radius, high.$radius.clone() * high.$radius, ), hue: crate::hues::$hue_uniform_ty::new(low.hue, high.hue), $($phantom: core::marker::PhantomData,)? } } fn new_inclusive(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); $uniform_ty { $height: rand::distributions::uniform::Uniform::new_inclusive::<_, T>(low.$height, high.$height), $radius: rand::distributions::uniform::Uniform::new_inclusive::<_, T>( low.$radius.clone() * low.$radius, high.$radius.clone() * high.$radius, ), hue: crate::hues::$hue_uniform_ty::new_inclusive(low.hue, high.hue), $($phantom: core::marker::PhantomData,)? } } fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { use rand::distributions::Distribution; $ty { $height: self.$height.sample(rng), $radius: self.$radius.sample(rng).sqrt(), hue: self.hue.sample(rng), $($phantom: core::marker::PhantomData,)? } } } }; } macro_rules! impl_rand_traits_hsv_cone { ( $uniform_ty: ident, $ty: ident { hue: $hue_uniform_ty: ident => $hue_ty: ident, height: $height: ident, radius: $radius: ident } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { impl_rand_traits_hsv_cone!( $uniform_ty, $ty<> { hue: $hue_uniform_ty => $hue_ty, height: $height, radius: $radius } $(phantom: $phantom : PhantomData<$phantom_ty>)? $(where $($where)+)?); }; ( $uniform_ty: ident, $ty: ident <$($ty_param: ident),*> { hue: $hue_uniform_ty: ident => $hue_ty: ident, height: $height: ident, radius: $radius: ident } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::Distribution<$ty<$($ty_param,)* T>> for rand::distributions::Standard where T: crate::num::Cbrt + crate::num::Sqrt, rand::distributions::Standard: rand::distributions::Distribution + rand::distributions::Distribution<$hue_ty>, { fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { let hue = rng.gen::<$hue_ty>(); let crate::random_sampling::HsvSample { saturation: $radius, value: $height } = crate::random_sampling::sample_hsv(rng.gen(), rng.gen()); $ty { hue, $radius, $height, $($phantom: core::marker::PhantomData,)? } } } /// Samples colors uniformly. #[cfg(feature = "random")] pub struct $uniform_ty<$($ty_param,)* T> where T: rand::distributions::uniform::SampleUniform, { hue: crate::hues::$hue_uniform_ty, u1: rand::distributions::uniform::Uniform, u2: rand::distributions::uniform::Uniform, $($phantom: core::marker::PhantomData<$phantom_ty>,)? } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::SampleUniform for $ty<$($ty_param,)* T> where T: crate::num::Cbrt + crate::num::Sqrt + crate::num::Powi + Clone + rand::distributions::uniform::SampleUniform, $hue_ty: rand::distributions::uniform::SampleBorrow<$hue_ty>, crate::hues::$hue_uniform_ty: rand::distributions::uniform::UniformSampler>, { type Sampler = $uniform_ty<$($ty_param,)* T>; } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::UniformSampler for $uniform_ty<$($ty_param,)* T> where T: crate::num::Cbrt + crate::num::Sqrt + crate::num::Powi + Clone + rand::distributions::uniform::SampleUniform, $hue_ty: rand::distributions::uniform::SampleBorrow<$hue_ty>, crate::hues::$hue_uniform_ty: rand::distributions::uniform::UniformSampler>, { type X = $ty<$($ty_param,)* T>; fn new(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); let (r1_min, r2_min) = crate::random_sampling::invert_hsv_sample(crate::random_sampling::HsvSample { value: low.$height, saturation: low.$radius, }); let (r1_max, r2_max) = crate::random_sampling::invert_hsv_sample(crate::random_sampling::HsvSample { value: high.$height, saturation: high.$radius, }); $uniform_ty { hue: crate::hues::$hue_uniform_ty::new(low.hue, high.hue), u1: rand::distributions::uniform::Uniform::new::<_, T>(r1_min, r1_max), u2: rand::distributions::uniform::Uniform::new::<_, T>(r2_min, r2_max), $($phantom: core::marker::PhantomData,)? } } fn new_inclusive(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); let (r1_min, r2_min) = crate::random_sampling::invert_hsv_sample(crate::random_sampling::HsvSample { value: low.$height, saturation: low.$radius, }); let (r1_max, r2_max) = crate::random_sampling::invert_hsv_sample(crate::random_sampling::HsvSample { value: high.$height, saturation: high.$radius, }); $uniform_ty { hue: crate::hues::$hue_uniform_ty::new_inclusive(low.hue, high.hue), u1: rand::distributions::uniform::Uniform::new_inclusive::<_, T>(r1_min, r1_max), u2: rand::distributions::uniform::Uniform::new_inclusive::<_, T>(r2_min, r2_max), $($phantom: core::marker::PhantomData,)? } } fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { use rand::distributions::Distribution; let hue = self.hue.sample(rng); let crate::random_sampling::HsvSample { saturation: $radius, value: $height } = crate::random_sampling::sample_hsv(self.u1.sample(rng), self.u2.sample(rng)); $ty { hue, $radius, $height, $($phantom: core::marker::PhantomData,)? } } } } } macro_rules! impl_rand_traits_hsl_bicone { ( $uniform_ty: ident, $ty: ident { hue: $hue_uniform_ty: ident => $hue_ty: ident, height: $height: ident $(=> [$height_map_fn: expr, $height_unmap_fn: expr])?, radius: $radius: ident $(=> [$radius_map_fn: expr, $radius_unmap_fn: expr])? } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { impl_rand_traits_hsl_bicone!( $uniform_ty, $ty<> { hue: $hue_uniform_ty => $hue_ty, height: $height $(=> [$height_map_fn, $height_unmap_fn])?, radius: $radius $(=> [$radius_map_fn, $radius_unmap_fn])? } $(phantom: $phantom : PhantomData<$phantom_ty>)? $(where $($where)+)?); }; ( $uniform_ty: ident, $ty: ident <$($ty_param: ident),*> { hue: $hue_uniform_ty: ident => $hue_ty: ident, height: $height: ident $(=> [$height_map_fn: expr, $height_unmap_fn: expr])?, radius: $radius: ident $(=> [$radius_map_fn: expr, $radius_unmap_fn: expr])? } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::Distribution<$ty<$($ty_param,)* T>> for rand::distributions::Standard where T: crate::num::Real + crate::num::One + crate::num::Cbrt + crate::num::Sqrt + crate::num::Arithmetics + crate::num::PartialCmp + Clone, T::Mask: crate::bool_mask::LazySelect + Clone, rand::distributions::Standard: rand::distributions::Distribution + rand::distributions::Distribution<$hue_ty>, { #[allow(clippy::redundant_closure_call)] fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { let hue = rng.gen::<$hue_ty>(); let crate::random_sampling::HslSample { saturation, lightness } = crate::random_sampling::sample_hsl(rng.gen(), rng.gen()); $ty { hue, $radius: __apply_map_fn!(saturation $(, $radius_map_fn)?), $height: __apply_map_fn!(lightness $(, $height_map_fn)?), $($phantom: core::marker::PhantomData,)? } } } /// Samples colors uniformly. #[cfg(feature = "random")] pub struct $uniform_ty<$($ty_param,)* T> where T: rand::distributions::uniform::SampleUniform, { hue: crate::hues::$hue_uniform_ty, u1: rand::distributions::uniform::Uniform, u2: rand::distributions::uniform::Uniform, $($phantom: core::marker::PhantomData<$phantom_ty>,)? } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::SampleUniform for $ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::One + crate::num::Cbrt + crate::num::Sqrt + crate::num::Powi + crate::num::Arithmetics + crate::num::PartialCmp + Clone + rand::distributions::uniform::SampleUniform, T::Mask: crate::bool_mask::LazySelect + Clone, $hue_ty: rand::distributions::uniform::SampleBorrow<$hue_ty>, crate::hues::$hue_uniform_ty: rand::distributions::uniform::UniformSampler>, { type Sampler = $uniform_ty<$($ty_param,)* T>; } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::UniformSampler for $uniform_ty<$($ty_param,)* T> where T: crate::num::Real + crate::num::One + crate::num::Cbrt + crate::num::Sqrt + crate::num::Powi + crate::num::Arithmetics + crate::num::PartialCmp + Clone + rand::distributions::uniform::SampleUniform, T::Mask: crate::bool_mask::LazySelect + Clone, $hue_ty: rand::distributions::uniform::SampleBorrow<$hue_ty>, crate::hues::$hue_uniform_ty: rand::distributions::uniform::UniformSampler>, { type X = $ty<$($ty_param,)* T>; #[allow(clippy::redundant_closure_call)] fn new(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); let (r1_min, r2_min) = crate::random_sampling::invert_hsl_sample(crate::random_sampling::HslSample { lightness: __apply_map_fn!(low.$height $(, $radius_unmap_fn)?), saturation: __apply_map_fn!(low.$radius $(, $height_unmap_fn)?), }); let (r1_max, r2_max) = crate::random_sampling::invert_hsl_sample(crate::random_sampling::HslSample { lightness: __apply_map_fn!(high.$height $(, $radius_unmap_fn)?), saturation: __apply_map_fn!(high.$radius $(, $height_unmap_fn)?), }); $uniform_ty { hue: crate::hues::$hue_uniform_ty::new(low.hue, high.hue), u1: rand::distributions::uniform::Uniform::new::<_, T>(r1_min, r1_max), u2: rand::distributions::uniform::Uniform::new::<_, T>(r2_min, r2_max), $($phantom: core::marker::PhantomData,)? } } #[allow(clippy::redundant_closure_call)] fn new_inclusive(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { let low = low_b.borrow().clone(); let high = high_b.borrow().clone(); let (r1_min, r2_min) = crate::random_sampling::invert_hsl_sample(crate::random_sampling::HslSample { lightness: __apply_map_fn!(low.$height $(, $radius_unmap_fn)?), saturation: __apply_map_fn!(low.$radius $(, $height_unmap_fn)?), }); let (r1_max, r2_max) = crate::random_sampling::invert_hsl_sample(crate::random_sampling::HslSample { lightness: __apply_map_fn!(high.$height $(, $radius_unmap_fn)?), saturation: __apply_map_fn!(high.$radius $(, $height_unmap_fn)?), }); $uniform_ty { hue: crate::hues::$hue_uniform_ty::new_inclusive(low.hue, high.hue), u1: rand::distributions::uniform::Uniform::new_inclusive::<_, T>(r1_min, r1_max), u2: rand::distributions::uniform::Uniform::new_inclusive::<_, T>(r2_min, r2_max), $($phantom: core::marker::PhantomData,)? } } #[allow(clippy::redundant_closure_call)] fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { use rand::distributions::Distribution; let hue = self.hue.sample(rng); let crate::random_sampling::HslSample { saturation, lightness } = crate::random_sampling::sample_hsl(self.u1.sample(rng), self.u2.sample(rng)); $ty { hue, $radius: __apply_map_fn!(saturation $(, $radius_map_fn)?), $height: __apply_map_fn!(lightness $(, $height_map_fn)?), $($phantom: core::marker::PhantomData,)? } } } } } macro_rules! impl_rand_traits_hwb_cone { ( $uniform_ty: ident, $ty: ident, $hsv_uniform_ty: ident, $hsv_ty: ident { height: $height: ident, radius: $radius: ident } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { impl_rand_traits_hwb_cone!( $uniform_ty, $ty<>, $hsv_uniform_ty, $hsv_ty { height: $height, radius: $radius } $(phantom: $phantom : PhantomData<$phantom_ty>)? $(where $($where)+)?); }; ( $uniform_ty: ident, $ty: ident <$($ty_param: ident),*>, $hsv_uniform_ty: ident, $hsv_ty: ident { height: $height: ident, radius: $radius: ident } $(phantom: $phantom: ident : PhantomData<$phantom_ty: ident>)? $(where $($where: tt)+)? ) => { #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::Distribution<$ty<$($ty_param,)* T>> for rand::distributions::Standard where rand::distributions::Standard: rand::distributions::Distribution<$hsv_ty<$($ty_param,)* T>>, $ty<$($ty_param,)* T>: crate::convert::FromColorUnclamped<$hsv_ty<$($ty_param,)* T>>, { fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { use crate::convert::FromColorUnclamped; $ty::from_color_unclamped(rng.gen::<$hsv_ty<$($ty_param,)* T>>()) } } /// Samples colors uniformly. #[cfg(feature = "random")] pub struct $uniform_ty<$($ty_param,)* T> where T: rand::distributions::uniform::SampleUniform, { sampler: $hsv_uniform_ty<$($ty_param,)* T>, $($phantom: core::marker::PhantomData<$phantom_ty>,)? } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::SampleUniform for $ty<$($ty_param,)* T> where T: crate::num::MinMax + Clone + rand::distributions::uniform::SampleUniform, $hsv_ty<$($ty_param,)* T>: crate::convert::FromColorUnclamped<$ty<$($ty_param,)* T>> + rand::distributions::uniform::SampleBorrow<$hsv_ty<$($ty_param,)* T>>, $ty<$($ty_param,)* T>: crate::convert::FromColorUnclamped<$hsv_ty<$($ty_param,)* T>>, $hsv_uniform_ty<$($ty_param,)* T>: rand::distributions::uniform::UniformSampler>, { type Sampler = $uniform_ty<$($ty_param,)* T>; } #[cfg(feature = "random")] impl<$($ty_param,)* T> rand::distributions::uniform::UniformSampler for $uniform_ty<$($ty_param,)* T> where T: crate::num::MinMax + Clone + rand::distributions::uniform::SampleUniform, $hsv_ty<$($ty_param,)* T>: crate::convert::FromColorUnclamped<$ty<$($ty_param,)* T>> + rand::distributions::uniform::SampleBorrow<$hsv_ty<$($ty_param,)* T>>, $ty<$($ty_param,)* T>: crate::convert::FromColorUnclamped<$hsv_ty<$($ty_param,)* T>>, $hsv_uniform_ty<$($ty_param,)* T>: rand::distributions::uniform::UniformSampler>, { type X = $ty<$($ty_param,)* T>; fn new(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { use crate::convert::FromColorUnclamped; let low_input = $hsv_ty::from_color_unclamped(low_b.borrow().clone()); let high_input = $hsv_ty::from_color_unclamped(high_b.borrow().clone()); let (low_saturation, high_saturation) = low_input.saturation.min_max(high_input.saturation); let (low_value, high_value) = low_input.value.min_max(high_input.value); let low = $hsv_ty{ hue: low_input.hue, $radius: low_saturation, $height: low_value, $($phantom: core::marker::PhantomData,)? }; let high = $hsv_ty{ hue: high_input.hue, $radius: high_saturation, $height: high_value, $($phantom: core::marker::PhantomData,)? }; let sampler = $hsv_uniform_ty::<$($ty_param,)* T>::new(low, high); $uniform_ty { sampler, $($phantom: core::marker::PhantomData,)? } } fn new_inclusive(low_b: B1, high_b: B2) -> Self where B1: rand::distributions::uniform::SampleBorrow + Sized, B2: rand::distributions::uniform::SampleBorrow + Sized, { use crate::convert::FromColorUnclamped; let low_input = $hsv_ty::from_color_unclamped(low_b.borrow().clone()); let high_input = $hsv_ty::from_color_unclamped(high_b.borrow().clone()); let (low_saturation, high_saturation) = low_input.saturation.min_max(high_input.saturation); let (low_value, high_value) = low_input.value.min_max(high_input.value); let low = $hsv_ty{ hue: low_input.hue, $radius: low_saturation, $height: low_value, $($phantom: core::marker::PhantomData,)? }; let high = $hsv_ty{ hue: high_input.hue, $radius: high_saturation, $height: high_value, $($phantom: core::marker::PhantomData,)? }; let sampler = $hsv_uniform_ty::<$($ty_param,)* T>::new_inclusive(low, high); $uniform_ty { sampler, $($phantom: core::marker::PhantomData,)? } } fn sample(&self, rng: &mut R) -> $ty<$($ty_param,)* T> { use crate::convert::FromColorUnclamped; $ty::from_color_unclamped(self.sampler.sample(rng)) } } }; } palette-0.7.5/src/macros/reference_component.rs000064400000000000000000000225441046102023000177430ustar 00000000000000macro_rules! impl_reference_component_methods { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_reference_component_methods!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($phantom_ty: ident)? > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($phantom_ty,)? T> $self_ty<$($phantom_ty,)? &T> { /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> $self_ty<$($phantom_ty,)? T> where T: Copy, { $self_ty { $($element: *self.$element,)+ $($phantom: core::marker::PhantomData,)? } } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> $self_ty<$($phantom_ty,)? T> where T: Clone, { $self_ty { $($element: self.$element.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($phantom_ty,)? T> $self_ty<$($phantom_ty,)? &mut T> { /// Update this color with new values. #[inline] pub fn set(&mut self, value: $self_ty<$($phantom_ty,)? T>) { $(*self.$element = value.$element;)+ } /// Borrow this color's components as shared references. #[inline] pub fn as_refs(&self) -> $self_ty<$($phantom_ty,)? &T> { $self_ty { $($element: &*self.$element,)+ $($phantom: core::marker::PhantomData,)? } } /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> $self_ty<$($phantom_ty,)? T> where T: Copy, { $self_ty { $($element: *self.$element,)+ $($phantom: core::marker::PhantomData,)? } } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> $self_ty<$($phantom_ty,)? T> where T: Clone, { $self_ty { $($element: self.$element.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($phantom_ty,)? T, A> crate::Alpha<$self_ty<$($phantom_ty,)? &T>, &A> { /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Copy, A: Copy, { crate::Alpha{color: self.color.copied(), alpha: *self.alpha} } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Clone, A: Clone, { crate::Alpha{color: self.color.cloned(), alpha: self.alpha.clone()} } } impl<$($phantom_ty,)? T, A> crate::Alpha<$self_ty<$($phantom_ty,)? &mut T>, &mut A> { /// Update this color with new values. #[inline] pub fn set(&mut self, value: crate::Alpha<$self_ty<$($phantom_ty,)? T>, A>) { self.color.set(value.color); *self.alpha = value.alpha; } /// Borrow this color's components as shared references. #[inline] pub fn as_refs(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? &T>, &A>{ crate::Alpha{color: self.color.as_refs(), alpha: &*self.alpha} } /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Copy, A: Copy, { crate::Alpha{color: self.color.copied(), alpha: *self.alpha} } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Clone, A: Clone, { crate::Alpha{color: self.color.cloned(), alpha: self.alpha.clone()} } } } } macro_rules! impl_reference_component_methods_hue { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_reference_component_methods_hue!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($phantom_ty: ident)? > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($phantom_ty,)? T> $self_ty<$($phantom_ty,)? &T> { /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> $self_ty<$($phantom_ty,)? T> where T: Copy, { $self_ty { hue: self.hue.copied(), $($element: *self.$element,)+ $($phantom: core::marker::PhantomData,)? } } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> $self_ty<$($phantom_ty,)? T> where T: Clone, { $self_ty { hue: self.hue.cloned(), $($element: self.$element.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($phantom_ty,)? T> $self_ty<$($phantom_ty,)? &mut T> { /// Update this color with new values. #[inline] pub fn set(&mut self, value: $self_ty<$($phantom_ty,)? T>) { self.hue.set(value.hue); $(*self.$element = value.$element;)+ } /// Borrow this color's components as shared references. #[inline] pub fn as_refs(&self) -> $self_ty<$($phantom_ty,)? &T> { $self_ty { hue: self.hue.as_ref(), $($element: &*self.$element,)+ $($phantom: core::marker::PhantomData,)? } } /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> $self_ty<$($phantom_ty,)? T> where T: Copy, { $self_ty { hue: self.hue.copied(), $($element: *self.$element,)+ $($phantom: core::marker::PhantomData,)? } } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> $self_ty<$($phantom_ty,)? T> where T: Clone, { $self_ty { hue: self.hue.cloned(), $($element: self.$element.clone(),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($phantom_ty,)? T, A> crate::Alpha<$self_ty<$($phantom_ty,)? &T>, &A> { /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Copy, A: Copy, { crate::Alpha{color: self.color.copied(), alpha: *self.alpha} } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Clone, A: Clone, { crate::Alpha{color: self.color.cloned(), alpha: self.alpha.clone()} } } impl<$($phantom_ty,)? T, A> crate::Alpha<$self_ty<$($phantom_ty,)? &mut T>, &mut A> { /// Update this color with new values. #[inline] pub fn set(&mut self, value: crate::Alpha<$self_ty<$($phantom_ty,)? T>, A>) { self.color.set(value.color); *self.alpha = value.alpha; } /// Borrow this color's components as shared references. #[inline] pub fn as_refs(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? &T>, &A>{ crate::Alpha{color: self.color.as_refs(), alpha: &*self.alpha} } /// Get an owned, copied version of this color. #[inline] pub fn copied(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Copy, A: Copy, { crate::Alpha{color: self.color.copied(), alpha: *self.alpha} } /// Get an owned, cloned version of this color. #[inline] pub fn cloned(&self) -> crate::Alpha<$self_ty<$($phantom_ty,)? T>, A> where T: Clone, A: Clone, { crate::Alpha{color: self.color.cloned(), alpha: self.alpha.clone()} } } } } palette-0.7.5/src/macros/simd.rs000064400000000000000000000253541046102023000146610ustar 00000000000000macro_rules! make_recursive_tuples { ($first:tt $(,$rest:tt)+) => { make_recursive_tuples!(@ $first [$($rest),+]) }; (@ $tuple:tt [$first:tt $(,$rest:tt)*]) => { make_recursive_tuples!(@ ($tuple, $first) [$($rest),*]) }; (@ $tuple:tt []) => { $tuple } } macro_rules! impl_simd_array_conversion { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_simd_array_conversion!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($ty_param: ident),* > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($ty_param,)* T, V, const N: usize> From<[$self_ty<$($ty_param,)* T>; N]> for $self_ty<$($ty_param,)* V> where [T; N]: Default, V: crate::num::FromScalarArray, { fn from(colors: [$self_ty<$($ty_param,)* T>; N]) -> Self { $(let mut $element: [T; N] = Default::default();)* for (index, color) in IntoIterator::into_iter(colors).enumerate() { $($element[index] = color.$element;)* } $self_ty { $($element: V::from_array($element),)* $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T, V, const N: usize> From<[crate::Alpha<$self_ty<$($ty_param,)* T>, T>; N]> for crate::Alpha<$self_ty<$($ty_param,)* V>, V> where [T; N]: Default, V: crate::num::FromScalarArray, { fn from(colors: [crate::Alpha<$self_ty<$($ty_param,)* T>, T>; N]) -> Self { $(let mut $element: [T; N] = Default::default();)* let mut alpha: [T; N] = Default::default(); for (index, color) in IntoIterator::into_iter(colors).enumerate() { $($element[index] = color.color.$element;)* alpha[index] = color.alpha } crate::Alpha { color: $self_ty { $($element: V::from_array($element),)* $($phantom: core::marker::PhantomData,)? }, alpha: V::from_array(alpha), } } } impl<$($ty_param,)* T, V, const N: usize> From<[crate::blend::PreAlpha<$self_ty<$($ty_param,)* T>>; N]> for crate::blend::PreAlpha<$self_ty<$($ty_param,)* V>> where [T; N]: Default, V: crate::num::FromScalarArray, $self_ty<$($ty_param,)* T>: crate::blend::Premultiply, $self_ty<$($ty_param,)* V>: crate::blend::Premultiply, { fn from(colors: [crate::blend::PreAlpha<$self_ty<$($ty_param,)* T>>; N]) -> Self { $(let mut $element: [T; N] = Default::default();)* let mut alpha: [T; N] = Default::default(); for (index, color) in IntoIterator::into_iter(colors).enumerate() { $($element[index] = color.color.$element;)* alpha[index] = color.alpha } crate::blend::PreAlpha { color: $self_ty { $($element: V::from_array($element),)* $($phantom: core::marker::PhantomData,)? }, alpha: V::from_array(alpha), } } } impl<$($ty_param,)* T, V, const N: usize> From<$self_ty<$($ty_param,)* V>> for [$self_ty<$($ty_param,)* T>; N] where Self: Default, V: crate::num::IntoScalarArray, { fn from(color: $self_ty<$($ty_param,)* V>) -> Self { let mut colors = Self::default(); $(let $element = color.$element.into_array();)* for make_recursive_tuples!(index $(,$element)*) in (0..)$(.zip($element))* { colors[index] = $self_ty { $($element,)* $($phantom: core::marker::PhantomData,)? }; } colors } } impl<$($ty_param,)* T, V, const N: usize> From, V>> for [crate::Alpha<$self_ty<$($ty_param,)* T>, T>; N] where Self: Default, V: crate::num::IntoScalarArray, { fn from(color: crate::Alpha<$self_ty<$($ty_param,)* V>, V>) -> Self { let mut colors = Self::default(); $(let $element = color.color.$element.into_array();)* let alpha = color.alpha.into_array(); for make_recursive_tuples!(index $(,$element)*, alpha) in (0..)$(.zip($element))*.zip(alpha) { colors[index] = crate::Alpha { color: $self_ty { $($element,)* $($phantom: core::marker::PhantomData,)? }, alpha, }; } colors } } impl<$($ty_param,)* T, V, const N: usize> From>> for [crate::blend::PreAlpha<$self_ty<$($ty_param,)* T>>; N] where Self: Default, V: crate::num::IntoScalarArray, $self_ty<$($ty_param,)* T>: crate::blend::Premultiply, $self_ty<$($ty_param,)* V>: crate::blend::Premultiply, { fn from(color: crate::blend::PreAlpha<$self_ty<$($ty_param,)* V>>) -> Self { let mut colors = Self::default(); $(let $element = color.color.$element.into_array();)* let alpha = color.alpha.into_array(); for make_recursive_tuples!(index $(,$element)*, alpha) in (0..)$(.zip($element))*.zip(alpha) { colors[index] = crate::blend::PreAlpha { color: $self_ty { $($element,)* $($phantom: core::marker::PhantomData,)? }, alpha, }; } colors } } }; } macro_rules! impl_simd_array_conversion_hue { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_simd_array_conversion_hue!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($ty_param: ident),* > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($ty_param,)* T, V, const N: usize> From<[$self_ty<$($ty_param,)* T>; N]> for $self_ty<$($ty_param,)* V> where [T; N]: Default, V: crate::num::FromScalarArray, { fn from(colors: [$self_ty<$($ty_param,)* T>; N]) -> Self { let mut hue: [T; N] = Default::default(); $(let mut $element: [T; N] = Default::default();)* for (index, color) in IntoIterator::into_iter(colors).enumerate() { hue[index] = color.hue.into_inner(); $($element[index] = color.$element;)* } $self_ty { hue: V::from_array(hue).into(), $($element: V::from_array($element),)* $($phantom: core::marker::PhantomData,)? } } } impl<$($ty_param,)* T, V, const N: usize> From<[crate::Alpha<$self_ty<$($ty_param,)* T>, T>; N]> for crate::Alpha<$self_ty<$($ty_param,)* V>, V> where [T; N]: Default, V: crate::num::FromScalarArray, { fn from(colors: [crate::Alpha<$self_ty<$($ty_param,)* T>, T>; N]) -> Self { let mut hue: [T; N] = Default::default(); $(let mut $element: [T; N] = Default::default();)* let mut alpha: [T; N] = Default::default(); for (index, color) in IntoIterator::into_iter(colors).enumerate() { hue[index] = color.color.hue.into_inner(); $($element[index] = color.color.$element;)* alpha[index] = color.alpha } crate::Alpha { color: $self_ty { hue: V::from_array(hue).into(), $($element: V::from_array($element),)* $($phantom: core::marker::PhantomData,)? }, alpha: V::from_array(alpha), } } } impl<$($ty_param,)* T, V, const N: usize> From<$self_ty<$($ty_param,)* V>> for [$self_ty<$($ty_param,)* T>; N] where Self: Default, V: crate::num::IntoScalarArray, { fn from(color: $self_ty<$($ty_param,)* V>) -> Self { let mut colors = Self::default(); let hue = color.hue.into_inner().into_array(); $(let $element = color.$element.into_array();)* for make_recursive_tuples!(index, hue $(,$element)*) in (0..).zip(hue)$(.zip($element))* { colors[index] = $self_ty { hue: hue.into(), $($element,)* $($phantom: core::marker::PhantomData,)? }; } colors } } impl<$($ty_param,)* T, V, const N: usize> From, V>> for [crate::Alpha<$self_ty<$($ty_param,)* T>, T>; N] where Self: Default, V: crate::num::IntoScalarArray, { fn from(color: crate::Alpha<$self_ty<$($ty_param,)* V>, V>) -> Self { let mut colors = Self::default(); let hue = color.color.hue.into_inner().into_array(); $(let $element = color.color.$element.into_array();)* let alpha = color.alpha.into_array(); for make_recursive_tuples!(index, hue $(,$element)*, alpha) in (0..).zip(hue)$(.zip($element))*.zip(alpha) { colors[index] = crate::Alpha { color: $self_ty { hue: hue.into(), $($element,)* $($phantom: core::marker::PhantomData,)? }, alpha, }; } colors } } }; } palette-0.7.5/src/macros/struct_of_arrays.rs000064400000000000000000002143131046102023000173110ustar 00000000000000macro_rules! first { (($($first: tt)+) $(, ($($rest: tt)+))*) => { $($first)+ }; } macro_rules! skip_first { (($($first: tt)+) $(, ($($rest: tt)+))*) => { $($($rest)+)* }; } macro_rules! impl_struct_of_array_traits { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_struct_of_array_traits!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($phantom_ty: ident)? > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($phantom_ty,)? T, C> Extend<$self_ty<$($phantom_ty,)? T>> for $self_ty<$($phantom_ty,)? C> where C: Extend, { #[inline(always)] fn extend>>(&mut self, iter: I) { let iter = iter.into_iter(); for color in iter { $(self.$element.extend(core::iter::once(color.$element));)+ } } } impl<$($phantom_ty,)? T, C> core::iter::FromIterator<$self_ty<$($phantom_ty,)? T>> for $self_ty<$($phantom_ty,)? C> where Self: Extend<$self_ty<$($phantom_ty,)? T>>, C: Default, { #[inline(always)] fn from_iter>>(iter: I) -> Self { let mut result = Self { $($element: C::default(),)+ $($phantom: core::marker::PhantomData)? }; result.extend(iter); result } } impl<$($phantom_ty,)? T, const N: usize> IntoIterator for $self_ty<$($phantom_ty,)? [T; N]> { type Item = $self_ty<$($phantom_ty,)? T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: IntoIterator::into_iter(self.$element),)+ $($phantom: core::marker::PhantomData)? } } } impl<$($phantom_ty,)? T, const N: usize> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? [T; N]>, [T; N]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? T>, T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::array::IntoIter>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: IntoIterator::into_iter(self.alpha) } } } impl<'a, $($phantom_ty,)? T> IntoIterator for $self_ty<$($phantom_ty,)? &'a [T]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a [T]>, &'a [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } impl<'a, $($phantom_ty,)? T> IntoIterator for $self_ty<$($phantom_ty,)? &'a mut [T]> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut [T]>, &'a mut [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } #[cfg(feature = "alloc")] impl<$($phantom_ty,)? T> IntoIterator for $self_ty<$($phantom_ty,)? alloc::vec::Vec> { type Item = $self_ty<$($phantom_ty,)? T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? T>, T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, alloc::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a $self_ty<$($phantom_ty,)? [T; N]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? [T; N]>, [T; N]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&self.alpha).into_iter(), } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? &'b [T]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'b [T]>, &'b [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? &'b mut [T]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&*self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'b mut [T]>, &'b mut [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&*self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? alloc::vec::Vec> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>>, alloc::boxed::Box<[T]>> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&self.alpha).into_iter(), } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? [T; N]> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&mut self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? [T; N]>, [T; N]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (&mut self.alpha).into_iter(), } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? &'b mut [T]> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'b mut [T]>, &'b mut [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? alloc::vec::Vec> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&mut self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (&mut self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>> where T: 'a { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { $($element: (&mut *self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>>, alloc::boxed::Box<[T]>> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (&mut *self.alpha).into_iter(), } } } #[doc = concat!("An iterator for [`", stringify!($self_ty), "`] values.")] pub struct Iter { $(pub(crate) $element: I,)+ $(pub(crate) $phantom: core::marker::PhantomData<$phantom_ty>)? } impl Iterator for Iter where I: Iterator, { type Item = $self_ty<$($phantom_ty,)? I::Item>; #[inline(always)] fn next(&mut self) -> Option { $(let $element = self.$element.next();)+ if let ($(Some($element),)+) = ($($element,)+) { Some($self_ty { $($element,)+ $($phantom: core::marker::PhantomData,)? }) } else { None } } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let hint = first!($((self.$element)),+).size_hint(); skip_first!($((debug_assert_eq!(self.$element.size_hint(), hint, "the component iterators have different size hints");)),+); hint } #[inline(always)] fn count(self) -> usize { let count = first!($((self.$element)),+).count(); skip_first!($((debug_assert_eq!(self.$element.count(), count, "the component iterators have different counts");)),+); count } } impl DoubleEndedIterator for Iter where I: DoubleEndedIterator, { #[inline(always)] fn next_back(&mut self) -> Option { $(let $element = self.$element.next_back();)+ if let ($(Some($element),)+) = ($($element,)+) { Some($self_ty { $($element,)+ $($phantom: core::marker::PhantomData,)? }) } else { None } } } impl ExactSizeIterator for Iter where I: ExactSizeIterator, { #[inline(always)] fn len(&self) -> usize { let len = first!($((self.$element)),+).len(); skip_first!($((debug_assert_eq!(self.$element.len(), len, "the component iterators have different lengths");)),+); len } } } } macro_rules! impl_struct_of_array_traits_hue { ( $self_ty: ident, $hue_iter_ty: ident, [$($element: ident),+] $(, $phantom: ident)?) => { impl_struct_of_array_traits_hue!($self_ty<>, $hue_iter_ty, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($phantom_ty: ident)? > , $hue_iter_ty: ident, [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($phantom_ty,)? T, C> Extend<$self_ty<$($phantom_ty,)? T>> for $self_ty<$($phantom_ty,)? C> where C: Extend, { #[inline(always)] fn extend>>(&mut self, iter: I) { let iter = iter.into_iter(); for color in iter { self.hue.extend(core::iter::once(color.hue.into_inner())); $(self.$element.extend(core::iter::once(color.$element));)+ } } } impl<$($phantom_ty,)? T, C> core::iter::FromIterator<$self_ty<$($phantom_ty,)? T>> for $self_ty<$($phantom_ty,)? C> where Self: Extend<$self_ty<$($phantom_ty,)? T>>, C: Default, { #[inline(always)] fn from_iter>>(iter: I) -> Self { let mut result = Self { hue: C::default().into(), $($element: C::default(),)+ $($phantom: core::marker::PhantomData)? }; result.extend(iter); result } } impl<$($phantom_ty,)? T, const N: usize> IntoIterator for $self_ty<$($phantom_ty,)? [T; N]> { type Item = $self_ty<$($phantom_ty,)? T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: self.hue.into_iter(), $($element: IntoIterator::into_iter(self.$element),)+ $($phantom: core::marker::PhantomData)? } } } impl<$($phantom_ty,)? T, const N: usize> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? [T; N]>, [T; N]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? T>, T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::array::IntoIter>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: IntoIterator::into_iter(self.alpha) } } } impl<'a, $($phantom_ty,)? T> IntoIterator for $self_ty<$($phantom_ty,)? &'a [T]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: self.hue.into_iter(), $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a [T]>, &'a [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } impl<'a, $($phantom_ty,)? T> IntoIterator for $self_ty<$($phantom_ty,)? &'a mut [T]> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: self.hue.into_iter(), $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut [T]>, &'a mut [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } #[cfg(feature = "alloc")] impl<$($phantom_ty,)? T> IntoIterator for $self_ty<$($phantom_ty,)? alloc::vec::Vec> { type Item = $self_ty<$($phantom_ty,)? T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: self.hue.into_iter(), $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? T>, T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, alloc::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a $self_ty<$($phantom_ty,)? [T; N]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&self.hue).into_iter(), $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? [T; N]>, [T; N]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&self.alpha).into_iter(), } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? &'b [T]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: self.hue.into_iter(), $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'b [T]>, &'b [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: self.color.into_iter(), alpha: self.alpha.into_iter(), } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? &'b mut [T]> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&self.hue).into_iter(), $($element: (&*self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'b mut [T]>, &'b mut [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&*self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? alloc::vec::Vec> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&self.hue).into_iter(), $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a $self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>> { type Item = $self_ty<$($phantom_ty,)? &'a T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&self.hue).into_iter(), $($element: (&self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>>, alloc::boxed::Box<[T]>> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a T>, &'a T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::Iter<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&self.color).into_iter(), alpha: (&self.alpha).into_iter(), } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? [T; N]> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&mut self.hue).into_iter(), $($element: (&mut self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, $($phantom_ty,)? T, const N: usize> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? [T; N]>, [T; N]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (&mut self.alpha).into_iter(), } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? &'b mut [T]> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&mut self.hue).into_iter(), $($element: self.$element.into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'b mut [T]>, &'b mut [T]> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? alloc::vec::Vec> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&mut self.hue).into_iter(), $($element: (&mut self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (&mut self.alpha).into_iter(), } } } #[cfg(feature = "alloc")] impl<'a, $($phantom_ty,)? T> IntoIterator for &'a mut $self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>> { type Item = $self_ty<$($phantom_ty,)? &'a mut T>; type IntoIter = Iter $(,$phantom_ty)?>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { Iter { hue: (&mut self.hue).into_iter(), $($element: (&mut *self.$element).into_iter(),)+ $($phantom: core::marker::PhantomData)? } } } #[cfg(feature = "alloc")] impl<'a, 'b, $($phantom_ty,)? T> IntoIterator for &'a mut crate::alpha::Alpha<$self_ty<$($phantom_ty,)? alloc::boxed::Box<[T]>>, alloc::boxed::Box<[T]>> { type Item = crate::alpha::Alpha<$self_ty<$($phantom_ty,)? &'a mut T>, &'a mut T>; type IntoIter = crate::alpha::Iter $(,$phantom_ty)?>, core::slice::IterMut<'a, T>>; fn into_iter(self) -> Self::IntoIter { crate::alpha::Iter { color: (&mut self.color).into_iter(), alpha: (&mut *self.alpha).into_iter(), } } } #[doc = concat!("An iterator for [`", stringify!($self_ty), "`] values.")] pub struct Iter { pub(crate) hue: $hue_iter_ty, $(pub(crate) $element: I,)+ $(pub(crate) $phantom: core::marker::PhantomData<$phantom_ty>)? } impl Iterator for Iter where I: Iterator, { type Item = $self_ty<$($phantom_ty,)? I::Item>; #[inline(always)] fn next(&mut self) -> Option { let hue = self.hue.next(); $(let $element = self.$element.next();)+ if let (Some(hue), $(Some($element),)+) = (hue, $($element,)+) { Some($self_ty {hue $(, $element)+ $(, $phantom: core::marker::PhantomData)?}) } else { None } } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let hint = self.hue.size_hint(); $(debug_assert_eq!(self.$element.size_hint(), hint, "the component iterators have different size hints");)+ hint } #[inline(always)] fn count(self) -> usize { let count = self.hue.count(); $(debug_assert_eq!(self.$element.count(), count, "the component iterators have different counts");)+ count } } impl DoubleEndedIterator for Iter where I: DoubleEndedIterator, { #[inline(always)] fn next_back(&mut self) -> Option { let hue = self.hue.next_back(); $(let $element = self.$element.next_back();)+ if let (Some(hue), $(Some($element),)+) = (hue, $($element,)+) { Some($self_ty {hue $(, $element)+ $(, $phantom: core::marker::PhantomData)?}) } else { None } } } impl ExactSizeIterator for Iter where I: ExactSizeIterator, { #[inline(always)] fn len(&self) -> usize { let len = self.hue.len(); $(debug_assert_eq!(self.$element.len(), len, "the component iterators have different lengths");)+ len } } } } macro_rules! impl_struct_of_arrays_methods { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_struct_of_arrays_methods!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($phantom_ty: ident)? > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($phantom_ty,)? C> $self_ty<$($phantom_ty,)? C> { /// Return an iterator over the colors in the wrapped collections. #[inline(always)] pub fn iter<'a>(&'a self) -> <&'a Self as IntoIterator>::IntoIter where &'a Self: IntoIterator { self.into_iter() } /// Return an iterator that allows modifying the colors in the wrapped collections. #[inline(always)] pub fn iter_mut<'a>(&'a mut self) -> <&'a mut Self as IntoIterator>::IntoIter where &'a mut Self: IntoIterator { self.into_iter() } /// Get a color, or slice of colors, with references to the components at `index`. See [`slice::get`] for details. #[inline(always)] pub fn get<'a, I, T>(&'a self, index: I) -> Option<$self_ty<$($phantom_ty,)? &>::Output>> where T: 'a, C: AsRef<[T]>, I: core::slice::SliceIndex<[T]> + Clone, { $(let $element = self.$element.as_ref().get(index.clone());)+ if let ($(Some($element),)+) = ($($element,)+) { Some($self_ty { $($element,)+ $($phantom: core::marker::PhantomData,)? }) } else { None } } /// Get a color, or slice of colors, that allows modifying the components at `index`. See [`slice::get_mut`] for details. #[inline(always)] pub fn get_mut<'a, I, T>(&'a mut self, index: I) -> Option<$self_ty<$($phantom_ty,)? &mut >::Output>> where T: 'a, C: AsMut<[T]>, I: core::slice::SliceIndex<[T]> + Clone, { $(let $element = self.$element.as_mut().get_mut(index.clone());)+ if let ($(Some($element),)+) = ($($element,)+) { Some($self_ty { $($element,)+ $($phantom: core::marker::PhantomData,)? }) } else { None } } } #[cfg(feature = "alloc")] impl<$($phantom_ty,)? T> $self_ty<$($phantom_ty,)? alloc::vec::Vec> { /// Create a struct of vectors with a minimum capacity. See [`Vec::with_capacity`] for details. #[inline(always)] pub fn with_capacity(capacity: usize) -> Self { $(let $element = alloc::vec::Vec::with_capacity(capacity);)+ Self { $($element,)+ $($phantom: core::marker::PhantomData,)? } } /// Push an additional color's components onto the component vectors. See [`Vec::push`] for details. #[inline(always)] pub fn push(&mut self, value: $self_ty<$($phantom_ty,)? T>) { $(self.$element.push(value.$element);)+ } /// Pop a color's components from the component vectors. See [`Vec::pop`] for details. #[inline(always)] pub fn pop(&mut self) -> Option<$self_ty<$($phantom_ty,)? T>> { $(let $element = self.$element.pop();)+ Some($self_ty { $($element: $element?,)+ $($phantom: core::marker::PhantomData,)? }) } /// Clear the component vectors. See [`Vec::clear`] for details. #[inline(always)] pub fn clear(&mut self) { $(self.$element.clear();)+ } /// Return an iterator that moves colors out of the specified range. #[inline(always)] pub fn drain(&mut self, range: R) -> Iter $(, $phantom_ty)?> where R: core::ops::RangeBounds + Clone, { Iter { $($element: self.$element.drain(range.clone()),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($phantom_ty,)? Ct, Ca> crate::Alpha<$self_ty<$($phantom_ty,)? Ct>, Ca> { /// Get a color, or slice of colors, with references to the components at `index`. See [`slice::get`] for details. #[inline(always)] pub fn get<'a, I, T, A>(&'a self, index: I) -> Option>::Output>, &>::Output >> where T: 'a, A: 'a, Ct: AsRef<[T]>, Ca: AsRef<[A]>, I: core::slice::SliceIndex<[T]> + core::slice::SliceIndex<[A]> + Clone { let color = self.color.get(index.clone()); let alpha = self.alpha.as_ref().get(index); if let (Some(color), Some(alpha)) = (color, alpha) { Some(crate::Alpha{color, alpha}) } else { None } } /// Get a color, or slice of colors, that allows modifying the components at `index`. See [`slice::get_mut`] for details. #[inline(always)] pub fn get_mut<'a, I, T, A>(&'a mut self, index: I) -> Option>::Output>, &mut >::Output >> where T: 'a, A: 'a, Ct: AsMut<[T]>, Ca: AsMut<[A]>, I: core::slice::SliceIndex<[T]> + core::slice::SliceIndex<[A]> + Clone { let color = self.color.get_mut(index.clone()); let alpha = self.alpha.as_mut().get_mut(index); if let (Some(color), Some(alpha)) = (color, alpha) { Some(crate::Alpha{color, alpha}) } else { None } } } #[cfg(feature = "alloc")] impl<$($phantom_ty,)? T, A> crate::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { /// Create a struct of vectors with a minimum capacity. See [`Vec::with_capacity`] for details. #[inline(always)] pub fn with_capacity(capacity: usize) -> Self { crate::Alpha { color: $self_ty::with_capacity(capacity), alpha: alloc::vec::Vec::with_capacity(capacity), } } /// Push an additional color's components onto the component vectors. See [`Vec::push`] for details. #[inline(always)] pub fn push(&mut self, value: crate::Alpha<$self_ty<$($phantom_ty,)? T>, A>) { self.color.push(value.color); self.alpha.push(value.alpha); } /// Pop a color's components from the component vectors. See [`Vec::pop`] for details. #[inline(always)] pub fn pop(&mut self) -> Option, A>> { let color = self.color.pop(); let alpha = self.alpha.pop(); Some(crate::Alpha { color: color?, alpha: alpha?, }) } /// Clear the component vectors. See [`Vec::clear`] for details. #[inline(always)] pub fn clear(&mut self) { self.color.clear(); self.alpha.clear(); } /// Return an iterator that moves colors out of the specified range. #[inline(always)] pub fn drain(&mut self, range: R) -> crate::alpha::Iter $(, $phantom_ty)?>, alloc::vec::Drain> where R: core::ops::RangeBounds + Clone, { crate::alpha::Iter { color: self.color.drain(range.clone()), alpha: self.alpha.drain(range), } } } }; } macro_rules! impl_struct_of_arrays_methods_hue { ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { impl_struct_of_arrays_methods_hue!($self_ty<>, [$($element),+] $(, $phantom)?); }; ( $self_ty: ident < $($phantom_ty: ident)? > , [$($element: ident),+] $(, $phantom: ident)?) => { impl<$($phantom_ty,)? C> $self_ty<$($phantom_ty,)? C> { /// Return an iterator over the colors in the wrapped collections. #[inline(always)] pub fn iter<'a>(&'a self) -> <&'a Self as IntoIterator>::IntoIter where &'a Self: IntoIterator { self.into_iter() } /// Return an iterator that allows modifying the colors in the wrapped collections. #[inline(always)] pub fn iter_mut<'a>(&'a mut self) -> <&'a mut Self as IntoIterator>::IntoIter where &'a mut Self: IntoIterator { self.into_iter() } /// Get a color, or slice of colors, with references to the components at `index`. See [`slice::get`] for details. #[inline(always)] pub fn get<'a, I, T>(&'a self, index: I) -> Option<$self_ty<$($phantom_ty,)? &>::Output>> where T: 'a, C: AsRef<[T]>, I: core::slice::SliceIndex<[T]> + Clone, { let hue = self.hue.get(index.clone()); $(let $element = self.$element.as_ref().get(index.clone());)+ if let (Some(hue) $(, Some($element))+) = (hue $(,$element)+) { Some($self_ty {hue $(, $element)+ $(, $phantom: core::marker::PhantomData)?}) } else { None } } /// Get a color, or slice of colors, that allows modifying the components at `index`. See [`slice::get_mut`] for details. #[inline(always)] pub fn get_mut<'a, I, T>(&'a mut self, index: I) -> Option<$self_ty<$($phantom_ty,)? &mut >::Output>> where T: 'a, C: AsMut<[T]>, I: core::slice::SliceIndex<[T]> + Clone, { let hue = self.hue.get_mut(index.clone()); $(let $element = self.$element.as_mut().get_mut(index.clone());)+ if let (Some(hue) $(, Some($element))+) = (hue $(,$element)+) { Some($self_ty {hue $(, $element)+ $(, $phantom: core::marker::PhantomData)?}) } else { None } } } #[cfg(feature = "alloc")] impl<$($phantom_ty,)? T> $self_ty<$($phantom_ty,)? alloc::vec::Vec> { /// Create a struct of vectors with a minimum capacity. See [`Vec::with_capacity`] for details. #[inline(always)] pub fn with_capacity(capacity: usize) -> Self { let hue = alloc::vec::Vec::with_capacity(capacity); $(let $element = alloc::vec::Vec::with_capacity(capacity);)+ Self {hue: hue.into() $(, $element)+ $(, $phantom: core::marker::PhantomData)?} } /// Push an additional color's components onto the component vectors. See [`Vec::push`] for details. #[inline(always)] pub fn push(&mut self, value: $self_ty<$($phantom_ty,)? T>) { self.hue.push(value.hue); $(self.$element.push(value.$element);)+ } /// Pop a color's components from the component vectors. See [`Vec::pop`] for details. #[inline(always)] pub fn pop(&mut self) -> Option<$self_ty<$($phantom_ty,)? T>> { let hue = self.hue.pop(); $(let $element = self.$element.pop();)+ Some($self_ty { hue: hue?, $($element: $element?,)+ $($phantom: core::marker::PhantomData,)? }) } /// Clear the component vectors. See [`Vec::clear`] for details. #[inline(always)] pub fn clear(&mut self) { self.hue.clear(); $(self.$element.clear();)+ } /// Return an iterator that moves colors out of the specified range. #[inline(always)] pub fn drain(&mut self, range: R) -> Iter $(, $phantom_ty)?> where R: core::ops::RangeBounds + Clone, { Iter { hue: self.hue.drain(range.clone()), $($element: self.$element.drain(range.clone()),)+ $($phantom: core::marker::PhantomData,)? } } } impl<$($phantom_ty,)? Ct, Ca> crate::Alpha<$self_ty<$($phantom_ty,)? Ct>, Ca> { /// Get a color, or slice of colors, with references to the components at `index`. See [`slice::get`] for details. #[inline(always)] pub fn get<'a, I, T, A>(&'a self, index: I) -> Option>::Output>, &>::Output >> where T: 'a, A: 'a, Ct: AsRef<[T]>, Ca: AsRef<[A]>, I: core::slice::SliceIndex<[T]> + core::slice::SliceIndex<[A]> + Clone { let color = self.color.get(index.clone()); let alpha = self.alpha.as_ref().get(index); if let (Some(color), Some(alpha)) = (color, alpha) { Some(crate::Alpha{color, alpha}) } else { None } } /// Get a color, or slice of colors, that allows modifying the components at `index`. See [`slice::get_mut`] for details. #[inline(always)] pub fn get_mut<'a, I, T, A>(&'a mut self, index: I) -> Option>::Output>, &mut >::Output >> where T: 'a, A: 'a, Ct: AsMut<[T]>, Ca: AsMut<[A]>, I: core::slice::SliceIndex<[T]> + core::slice::SliceIndex<[A]> + Clone { let color = self.color.get_mut(index.clone()); let alpha = self.alpha.as_mut().get_mut(index); if let (Some(color), Some(alpha)) = (color, alpha) { Some(crate::Alpha{color, alpha}) } else { None } } } #[cfg(feature = "alloc")] impl<$($phantom_ty,)? T, A> crate::Alpha<$self_ty<$($phantom_ty,)? alloc::vec::Vec>, alloc::vec::Vec> { /// Create a struct of vectors with a minimum capacity. See [`Vec::with_capacity`] for details. #[inline(always)] pub fn with_capacity(capacity: usize) -> Self { crate::Alpha { color: $self_ty::with_capacity(capacity), alpha: alloc::vec::Vec::with_capacity(capacity), } } /// Push an additional color's components onto the component vectors. See [`Vec::push`] for details. #[inline(always)] pub fn push(&mut self, value: crate::Alpha<$self_ty<$($phantom_ty,)? T>, A>) { self.color.push(value.color); self.alpha.push(value.alpha); } /// Pop a color's components from the component vectors. See [`Vec::pop`] for details. #[inline(always)] pub fn pop(&mut self) -> Option, A>> { let color = self.color.pop(); let alpha = self.alpha.pop(); Some(crate::Alpha { color: color?, alpha: alpha?, }) } /// Clear the component vectors. See [`Vec::clear`] for details. #[inline(always)] pub fn clear(&mut self) { self.color.clear(); self.alpha.clear(); } /// Return an iterator that moves colors out of the specified range. #[inline(always)] pub fn drain(&mut self, range: R) -> crate::alpha::Iter $(, $phantom_ty)?>, alloc::vec::Drain> where R: core::ops::RangeBounds + Clone, { crate::alpha::Iter { color: self.color.drain(range.clone()), alpha: self.alpha.drain(range), } } } }; } #[cfg(test)] macro_rules! struct_of_arrays_tests { ($color_ty: ident $(<$phantom_ty:ty>)? [$($element: ident),+] $(phantom: $phantom: ident)?, $($values:expr),+) => { #[cfg(feature = "alloc")] #[test] fn collect() { let vec_of_colors = vec![$($values.color),+]; let color_of_vecs: $color_ty<$($phantom_ty,)? Vec<_>> = vec_of_colors.into_iter().collect(); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values.color),+]); } #[cfg(feature = "alloc")] #[test] fn collect_alpha() { let vec_of_colors = vec![$($values),+]; let color_of_vecs: crate::alpha::Alpha<$color_ty<$($phantom_ty,)? Vec<_>>, Vec<_>> = vec_of_colors.into_iter().collect(); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values),+]); } #[cfg(feature = "alloc")] #[test] fn extend() { let vec_of_colors = vec![$($values.color),+]; let mut color_of_vecs = $color_ty::<$($phantom_ty,)? Vec<_>>::with_capacity(vec_of_colors.len()); color_of_vecs.extend(vec_of_colors); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values.color),+]); } #[cfg(feature = "alloc")] #[test] fn extend_alpha() { let vec_of_colors = vec![$($values),+]; let mut color_of_vecs = crate::alpha::Alpha::<$color_ty<$($phantom_ty,)? Vec<_>>, Vec<_>>::with_capacity(vec_of_colors.len()); color_of_vecs.extend(vec_of_colors); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values),+]); } #[cfg(feature = "alloc")] #[test] fn pop_push() { let vec_of_colors = vec![$($values.color),+]; let mut color_of_vecs: $color_ty<$($phantom_ty,)? Vec<_>> = vec_of_colors.into_iter().collect(); let last = color_of_vecs.pop().unwrap(); color_of_vecs.push(last); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values.color),+]); } #[cfg(feature = "alloc")] #[test] fn pop_push_alpha() { let vec_of_colors = vec![$($values),+]; let mut color_of_vecs: crate::alpha::Alpha<$color_ty<$($phantom_ty,)? Vec<_>>, Vec<_>> = vec_of_colors.into_iter().collect(); let last = color_of_vecs.pop().unwrap(); color_of_vecs.push(last); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values),+]); } #[cfg(feature = "alloc")] #[test] fn clear() { let vec_of_colors = vec![$($values.color),+]; let mut color_of_vecs: $color_ty<$($phantom_ty,)? Vec<_>> = vec_of_colors.into_iter().collect(); color_of_vecs.clear(); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![]); } #[cfg(feature = "alloc")] #[test] fn clear_alpha() { let vec_of_colors = vec![$($values),+]; let mut color_of_vecs: crate::alpha::Alpha<$color_ty<$($phantom_ty,)? Vec<_>>, Vec<_>> = vec_of_colors.into_iter().collect(); color_of_vecs.clear(); let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![]); } #[cfg(feature = "alloc")] #[test] fn drain() { let vec_of_colors = vec![$($values.color),+]; let mut color_of_vecs: $color_ty<$($phantom_ty,)? Vec<_>> = vec_of_colors.into_iter().collect(); let vec_of_colors1: Vec<_> = color_of_vecs.drain(..).collect(); let vec_of_colors2: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors1, vec![$($values.color),+]); assert_eq!(vec_of_colors2, vec![]); } #[cfg(feature = "alloc")] #[test] fn drain_alpha() { let vec_of_colors = vec![$($values),+]; let mut color_of_vecs: crate::alpha::Alpha<$color_ty<$($phantom_ty,)? Vec<_>>, Vec<_>> = vec_of_colors.into_iter().collect(); let vec_of_colors1: Vec<_> = color_of_vecs.drain(..).collect(); let vec_of_colors2: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors1, vec![$($values),+]); assert_eq!(vec_of_colors2, vec![]); } #[cfg(feature = "alloc")] #[test] fn modify() { let vec_of_colors = vec![$($values.color),+]; let mut color_of_vecs: $color_ty<$($phantom_ty,)? Vec<_>> = vec_of_colors.into_iter().collect(); for mut color in &mut color_of_vecs { color.set(color.copied() + 2.0); } let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values.color + 2.0),+]); } #[cfg(feature = "alloc")] #[test] fn modify_alpha() { let vec_of_colors = vec![$($values),+]; let mut color_of_vecs: crate::alpha::Alpha<$color_ty<$($phantom_ty,)? Vec<_>>, Vec<_>> = vec_of_colors.into_iter().collect(); for mut color in &mut color_of_vecs { color.set(color.copied() + 2.0); } let vec_of_colors: Vec<_> = color_of_vecs.into_iter().collect(); assert_eq!(vec_of_colors, vec![$($values + 2.0),+]); } #[test] fn into_iterator() { fn expect_move(_: impl Iterator>){} fn expect_ref<'a>(_: impl Iterator>){} fn expect_ref_mut<'a>(_: impl Iterator>){} let arrays = $color_ty::<$($phantom_ty,)? [f32; 0]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; let slices = $color_ty::<$($phantom_ty,)? &[f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; let mut_slices = $color_ty::<$($phantom_ty,)? &mut [f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; expect_move(arrays.into_iter()); expect_ref(slices.into_iter()); expect_ref_mut(mut_slices.into_iter()); } #[test] fn into_iterator_alpha() { use crate::alpha::Alpha; fn expect_move(_: impl Iterator, f32>>){} fn expect_ref<'a>(_: impl Iterator, &'a f32>>){} fn expect_ref_mut<'a>(_: impl Iterator, &'a mut f32>>){} let arrays = Alpha::<_, [f32; 0]>{ color: $color_ty::<$($phantom_ty,)? [f32; 0]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; let slices = Alpha::<_, &[f32]>{ color: $color_ty::<$($phantom_ty,)? &[f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; let mut_slices = Alpha::<_, &mut [f32]>{ color: $color_ty::<$($phantom_ty,)? &mut [f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; expect_move(arrays.into_iter()); expect_ref(slices.into_iter()); expect_ref_mut(mut_slices.into_iter()); } #[test] fn into_iterator_ref() { fn expect_ref<'a>(_: impl Iterator>){} fn expect_ref_mut<'a>(_: impl Iterator>){} let mut arrays = $color_ty::<$($phantom_ty,)? [f32; 0]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; let mut slices = $color_ty::<$($phantom_ty,)? &[f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; let mut mut_slices = $color_ty::<$($phantom_ty,)? &mut [f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; expect_ref((&arrays).into_iter()); expect_ref((&slices).into_iter()); expect_ref((&mut_slices).into_iter()); expect_ref_mut((&mut arrays).into_iter()); expect_ref((&mut slices).into_iter()); expect_ref_mut((&mut mut_slices).into_iter()); } #[test] fn into_iterator_ref_alpha() { use crate::alpha::Alpha; fn expect_ref<'a>(_: impl Iterator, &'a f32>>){} fn expect_ref_mut<'a>(_: impl Iterator, &'a mut f32>>){} let mut arrays = Alpha::<_, [f32; 0]>{ color: $color_ty::<$($phantom_ty,)? [f32; 0]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; let mut slices = Alpha::<_, &[f32]>{ color: $color_ty::<$($phantom_ty,)? &[f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; let mut mut_slices = Alpha::<_, &mut [f32]>{ color: $color_ty::<$($phantom_ty,)? &mut [f32]>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; expect_ref((&arrays).into_iter()); expect_ref((&slices).into_iter()); expect_ref((&mut_slices).into_iter()); expect_ref_mut((&mut arrays).into_iter()); expect_ref((&mut slices).into_iter()); expect_ref_mut((&mut mut_slices).into_iter()); } #[cfg(feature = "alloc")] #[test] fn into_iterator_alloc() { fn expect_move(_: impl Iterator>){} fn expect_ref<'a>(_: impl Iterator>){} let vecs = $color_ty::<$($phantom_ty,)? Vec>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; let boxed_slices = $color_ty::<$($phantom_ty,)? Box<[f32]>>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; expect_move(vecs.into_iter()); expect_ref(boxed_slices.into_iter()); } #[cfg(feature = "alloc")] #[test] fn into_iterator_alloc_alpha() { use crate::alpha::Alpha; fn expect_move(_: impl Iterator, f32>>){} fn expect_ref<'a>(_: impl Iterator, &'a f32>>){} let vecs = Alpha::<_, Vec>{ color: $color_ty::<$($phantom_ty,)? Vec>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; let boxed_slices = Alpha::<_, Box<[f32]>>{ color: $color_ty::<$($phantom_ty,)? Box<[f32]>>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; expect_move(vecs.into_iter()); expect_ref(boxed_slices.into_iter()); } #[cfg(feature = "alloc")] #[test] fn into_iterator_alloc_ref() { fn expect_ref<'a>(_: impl Iterator>){} fn expect_ref_mut<'a>(_: impl Iterator>){} let mut vecs = $color_ty::<$($phantom_ty,)? Vec>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; let mut boxed_slices = $color_ty::<$($phantom_ty,)? Box<[f32]>>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }; expect_ref((&vecs).into_iter()); expect_ref((&boxed_slices).into_iter()); expect_ref_mut((&mut vecs).into_iter()); expect_ref_mut((&mut boxed_slices).into_iter()); } #[cfg(feature = "alloc")] #[test] fn into_iterator_alloc_ref_alpha() { use crate::alpha::Alpha; fn expect_ref<'a>(_: impl Iterator, &'a f32>>){} fn expect_ref_mut<'a>(_: impl Iterator, &'a mut f32>>){} let mut vecs = Alpha::<_, Vec>{ color: $color_ty::<$($phantom_ty,)? Vec>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; let mut boxed_slices = Alpha::<_, Box<[f32]>>{ color: $color_ty::<$($phantom_ty,)? Box<[f32]>>{ $($element: Default::default(),)+ $($phantom: core::marker::PhantomData,)? }, alpha: Default::default(), }; expect_ref((&vecs).into_iter()); expect_ref((&boxed_slices).into_iter()); expect_ref_mut((&mut vecs).into_iter()); expect_ref_mut((&mut boxed_slices).into_iter()); } } } palette-0.7.5/src/macros.rs000064400000000000000000000011501046102023000137110ustar 00000000000000// From https://stackoverflow.com/questions/60187436/rust-macro-repetition-with-plus macro_rules! strip_plus { (+ $($rest: tt)*) => { $($rest)* } } #[macro_use] mod arithmetics; #[macro_use] mod casting; #[macro_use] mod mix; #[macro_use] mod lighten_saturate; #[macro_use] mod equality; #[macro_use] mod blend; #[macro_use] mod lazy_select; #[macro_use] mod simd; #[macro_use] mod clamp; #[macro_use] mod convert; #[macro_use] mod color_difference; #[macro_use] mod struct_of_arrays; #[macro_use] mod reference_component; #[macro_use] mod copy_clone; #[macro_use] mod hue; #[macro_use] mod random; palette-0.7.5/src/matrix.rs000064400000000000000000000223541046102023000137420ustar 00000000000000//! This module provides simple matrix operations on 3x3 matrices to aid in //! chromatic adaptation and conversion calculations. use core::marker::PhantomData; use crate::{ convert::IntoColorUnclamped, encoding::Linear, num::{Arithmetics, FromScalar, IsValidDivisor, Recip}, rgb::{Primaries, Rgb, RgbSpace}, white_point::{Any, WhitePoint}, Xyz, Yxy, }; /// A 9 element array representing a 3x3 matrix. pub type Mat3 = [T; 9]; /// Multiply the 3x3 matrix with an XYZ color. #[inline] pub fn multiply_xyz(c: Mat3, f: Xyz) -> Xyz where T: Arithmetics, { // Input Mat3 is destructured to avoid panic paths let [c0, c1, c2, c3, c4, c5, c6, c7, c8] = c; let x1 = c0 * &f.x; let y1 = c3 * &f.x; let z1 = c6 * f.x; let x2 = c1 * &f.y; let y2 = c4 * &f.y; let z2 = c7 * f.y; let x3 = c2 * &f.z; let y3 = c5 * &f.z; let z3 = c8 * f.z; Xyz { x: x1 + x2 + x3, y: y1 + y2 + y3, z: z1 + z2 + z3, white_point: PhantomData, } } /// Multiply the 3x3 matrix with an XYZ color to return an RGB color. #[inline] pub fn multiply_xyz_to_rgb(c: Mat3, f: Xyz) -> Rgb, V> where S: RgbSpace, V: Arithmetics + FromScalar, { // Input Mat3 is destructured to avoid panic paths. red, green, and blue // can't be extracted like in `multiply_xyz` to get a performance increase let [c0, c1, c2, c3, c4, c5, c6, c7, c8] = c; Rgb { red: (V::from_scalar(c0) * &f.x) + (V::from_scalar(c1) * &f.y) + (V::from_scalar(c2) * &f.z), green: (V::from_scalar(c3) * &f.x) + (V::from_scalar(c4) * &f.y) + (V::from_scalar(c5) * &f.z), blue: (V::from_scalar(c6) * f.x) + (V::from_scalar(c7) * f.y) + (V::from_scalar(c8) * f.z), standard: PhantomData, } } /// Multiply the 3x3 matrix with an RGB color to return an XYZ color. #[inline] pub fn multiply_rgb_to_xyz(c: Mat3, f: Rgb, V>) -> Xyz where S: RgbSpace, V: Arithmetics + FromScalar, { // Input Mat3 is destructured to avoid panic paths. Same problem as // `multiply_xyz_to_rgb` for extracting x, y, z let [c0, c1, c2, c3, c4, c5, c6, c7, c8] = c; Xyz { x: (V::from_scalar(c0) * &f.red) + (V::from_scalar(c1) * &f.green) + (V::from_scalar(c2) * &f.blue), y: (V::from_scalar(c3) * &f.red) + (V::from_scalar(c4) * &f.green) + (V::from_scalar(c5) * &f.blue), z: (V::from_scalar(c6) * f.red) + (V::from_scalar(c7) * f.green) + (V::from_scalar(c8) * f.blue), white_point: PhantomData, } } /// Multiply two 3x3 matrices. #[inline] pub fn multiply_3x3(c: Mat3, f: Mat3) -> Mat3 where T: Arithmetics + Clone, { // Input Mat3 are destructured to avoid panic paths let [c0, c1, c2, c3, c4, c5, c6, c7, c8] = c; let [f0, f1, f2, f3, f4, f5, f6, f7, f8] = f; let o0 = c0.clone() * &f0 + c1.clone() * &f3 + c2.clone() * &f6; let o1 = c0.clone() * &f1 + c1.clone() * &f4 + c2.clone() * &f7; let o2 = c0 * &f2 + c1 * &f5 + c2 * &f8; let o3 = c3.clone() * &f0 + c4.clone() * &f3 + c5.clone() * &f6; let o4 = c3.clone() * &f1 + c4.clone() * &f4 + c5.clone() * &f7; let o5 = c3 * &f2 + c4 * &f5 + c5 * &f8; let o6 = c6.clone() * f0 + c7.clone() * f3 + c8.clone() * f6; let o7 = c6.clone() * f1 + c7.clone() * f4 + c8.clone() * f7; let o8 = c6 * f2 + c7 * f5 + c8 * f8; [o0, o1, o2, o3, o4, o5, o6, o7, o8] } /// Invert a 3x3 matrix and panic if matrix is not invertible. #[inline] pub fn matrix_inverse(a: Mat3) -> Mat3 where T: Recip + IsValidDivisor + Arithmetics + Clone, { // This function runs fastest with assert and no destructuring. The `det`'s // location should not be changed until benched that it's faster elsewhere assert!(a.len() > 8); let d0 = a[4].clone() * &a[8] - a[5].clone() * &a[7]; let d1 = a[3].clone() * &a[8] - a[5].clone() * &a[6]; let d2 = a[3].clone() * &a[7] - a[4].clone() * &a[6]; let mut det = a[0].clone() * &d0 - a[1].clone() * &d1 + a[2].clone() * &d2; let d3 = a[1].clone() * &a[8] - a[2].clone() * &a[7]; let d4 = a[0].clone() * &a[8] - a[2].clone() * &a[6]; let d5 = a[0].clone() * &a[7] - a[1].clone() * &a[6]; let d6 = a[1].clone() * &a[5] - a[2].clone() * &a[4]; let d7 = a[0].clone() * &a[5] - a[2].clone() * &a[3]; let d8 = a[0].clone() * &a[4] - a[1].clone() * &a[3]; if !det.is_valid_divisor() { panic!("The given matrix is not invertible") } det = det.recip(); [ d0 * &det, -d3 * &det, d6 * &det, -d1 * &det, d4 * &det, -d7 * &det, d2 * &det, -d5 * &det, d8 * det, ] } /// Maps a matrix from one item type to another. /// /// This turned out to be easier for the compiler to optimize than `matrix.map(f)`. #[inline(always)] pub fn matrix_map(matrix: Mat3, mut f: impl FnMut(T) -> U) -> Mat3 { let [m1, m2, m3, m4, m5, m6, m7, m8, m9] = matrix; [ f(m1), f(m2), f(m3), f(m4), f(m5), f(m6), f(m7), f(m8), f(m9), ] } /// Generates the Srgb to Xyz transformation matrix for a given white point. #[inline] pub fn rgb_to_xyz_matrix() -> Mat3 where S: RgbSpace, S::Primaries: Primaries, S::WhitePoint: WhitePoint, T: Recip + IsValidDivisor + Arithmetics + Clone + FromScalar, Yxy: IntoColorUnclamped>, { let r = S::Primaries::red().into_color_unclamped(); let g = S::Primaries::green().into_color_unclamped(); let b = S::Primaries::blue().into_color_unclamped(); let matrix = mat3_from_primaries(r, g, b); let s_matrix: Rgb, T> = multiply_xyz_to_rgb( matrix_inverse(matrix.clone()), S::WhitePoint::get_xyz().with_white_point(), ); // Destructuring has some performance benefits, don't change unless measured let [t0, t1, t2, t3, t4, t5, t6, t7, t8] = matrix; [ t0 * &s_matrix.red, t1 * &s_matrix.green, t2 * &s_matrix.blue, t3 * &s_matrix.red, t4 * &s_matrix.green, t5 * &s_matrix.blue, t6 * s_matrix.red, t7 * s_matrix.green, t8 * s_matrix.blue, ] } #[rustfmt::skip] #[inline] fn mat3_from_primaries(r: Xyz, g: Xyz, b: Xyz) -> Mat3 { [ r.x, g.x, b.x, r.y, g.y, b.y, r.z, g.z, b.z, ] } #[cfg(feature = "approx")] #[cfg(test)] mod test { use super::{matrix_inverse, multiply_3x3, multiply_xyz, rgb_to_xyz_matrix}; use crate::chromatic_adaptation::AdaptInto; use crate::encoding::{Linear, Srgb}; use crate::rgb::Rgb; use crate::white_point::D50; use crate::Xyz; #[test] fn matrix_multiply_3x3() { let inp1 = [1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 2.0, 1.0, 3.0]; let inp2 = [4.0, 5.0, 6.0, 6.0, 5.0, 4.0, 4.0, 6.0, 5.0]; let expected = [28.0, 33.0, 29.0, 28.0, 31.0, 31.0, 26.0, 33.0, 31.0]; let computed = multiply_3x3(inp1, inp2); for (t1, t2) in expected.iter().zip(computed.iter()) { assert_relative_eq!(t1, t2); } } #[test] fn matrix_multiply_xyz() { let inp1 = [0.1, 0.2, 0.3, 0.3, 0.2, 0.1, 0.2, 0.1, 0.3]; let inp2 = Xyz::new(0.4, 0.6, 0.8); let expected = Xyz::new(0.4, 0.32, 0.38); let computed = multiply_xyz(inp1, inp2); assert_relative_eq!(expected, computed) } #[test] fn matrix_inverse_check_1() { let input: [f64; 9] = [3.0, 0.0, 2.0, 2.0, 0.0, -2.0, 0.0, 1.0, 1.0]; let expected: [f64; 9] = [0.2, 0.2, 0.0, -0.2, 0.3, 1.0, 0.2, -0.3, 0.0]; let computed = matrix_inverse(input); for (t1, t2) in expected.iter().zip(computed.iter()) { assert_relative_eq!(t1, t2); } } #[test] fn matrix_inverse_check_2() { let input: [f64; 9] = [1.0, 0.0, 1.0, 0.0, 2.0, 1.0, 1.0, 1.0, 1.0]; let expected: [f64; 9] = [-1.0, -1.0, 2.0, -1.0, 0.0, 1.0, 2.0, 1.0, -2.0]; let computed = matrix_inverse(input); for (t1, t2) in expected.iter().zip(computed.iter()) { assert_relative_eq!(t1, t2); } } #[test] #[should_panic] fn matrix_inverse_panic() { let input: [f64; 9] = [1.0, 0.0, 0.0, 2.0, 0.0, 0.0, -4.0, 6.0, 1.0]; matrix_inverse(input); } #[rustfmt::skip] #[test] fn d65_rgb_conversion_matrix() { let expected = [ 0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.0721750, 0.0193339, 0.1191920, 0.9503041 ]; let computed = rgb_to_xyz_matrix::(); for (e, c) in expected.iter().zip(computed.iter()) { assert_relative_eq!(e, c, epsilon = 0.000001) } } #[test] fn d65_to_d50() { let input: Rgb> = Rgb::new(1.0, 1.0, 1.0); let expected: Rgb> = Rgb::new(1.0, 1.0, 1.0); let computed: Rgb> = input.adapt_into(); assert_relative_eq!(expected, computed, epsilon = 0.000001); } } palette-0.7.5/src/named.rs000064400000000000000000000024111046102023000135120ustar 00000000000000//! A collection of named color constants. Can be toggled with the `"named"` and //! `"named_from_str"` Cargo features. //! //! They are taken from the [SVG keyword //! colors](https://www.w3.org/TR/SVG11/types.html#ColorKeywords) (same as in //! CSS3) and they can be used as if they were pixel values: //! //! ``` //! use palette::Srgb; //! use palette::named; //! //! //From constant //! let from_const = Srgb::::from_format(named::OLIVE).into_linear(); #![cfg_attr(feature = "named_from_str", doc = "")] #![cfg_attr(feature = "named_from_str", doc = "//From name string")] #![cfg_attr(feature = "named_from_str", doc = "let olive = named::from_str(\"olive\").expect(\"unknown color\");")] #![cfg_attr(feature = "named_from_str", doc = "let from_str = Srgb::::from_format(olive).into_linear();")] #![cfg_attr(feature = "named_from_str", doc = "")] #![cfg_attr(feature = "named_from_str", doc = "assert_eq!(from_const, from_str);")] //! ``` include!(concat!(env!("OUT_DIR"), "/named.rs")); /// Get a SVG/CSS3 color by name. Can be toggled with the `"named_from_str"` /// Cargo feature. /// /// The names are the same as the constants, but lower case. #[cfg(feature = "named_from_str")] pub fn from_str(name: &str) -> Option> { COLORS.get(name).cloned() } palette-0.7.5/src/num/libm.rs000064400000000000000000000076541046102023000141660ustar 00000000000000use super::*; impl Trigonometry for f32 { #[inline] fn sin(self) -> Self { ::libm::sinf(self) } #[inline] fn cos(self) -> Self { ::libm::cosf(self) } #[inline] fn sin_cos(self) -> (Self, Self) { ::libm::sincosf(self) } #[inline] fn tan(self) -> Self { ::libm::tanf(self) } #[inline] fn asin(self) -> Self { ::libm::asinf(self) } #[inline] fn acos(self) -> Self { ::libm::acosf(self) } #[inline] fn atan(self) -> Self { ::libm::atanf(self) } #[inline] fn atan2(self, other: Self) -> Self { ::libm::atan2f(self, other) } } impl Trigonometry for f64 { #[inline] fn sin(self) -> Self { ::libm::sin(self) } #[inline] fn cos(self) -> Self { ::libm::cos(self) } #[inline] fn sin_cos(self) -> (Self, Self) { ::libm::sincos(self) } #[inline] fn tan(self) -> Self { ::libm::tan(self) } #[inline] fn asin(self) -> Self { ::libm::asin(self) } #[inline] fn acos(self) -> Self { ::libm::acos(self) } #[inline] fn atan(self) -> Self { ::libm::atan(self) } #[inline] fn atan2(self, other: Self) -> Self { ::libm::atan2(self, other) } } impl Abs for f32 { #[inline] fn abs(self) -> Self { ::libm::fabsf(self) } } impl Abs for f64 { #[inline] fn abs(self) -> Self { ::libm::fabs(self) } } impl Sqrt for f32 { #[inline] fn sqrt(self) -> Self { ::libm::sqrtf(self) } } impl Sqrt for f64 { #[inline] fn sqrt(self) -> Self { ::libm::sqrt(self) } } impl Cbrt for f32 { #[inline] fn cbrt(self) -> Self { ::libm::cbrtf(self) } } impl Cbrt for f64 { #[inline] fn cbrt(self) -> Self { ::libm::cbrt(self) } } impl Powf for f32 { #[inline] fn powf(self, exp: Self) -> Self { ::libm::powf(self, exp) } } impl Powf for f64 { #[inline] fn powf(self, exp: Self) -> Self { ::libm::pow(self, exp) } } impl Powi for f32 { #[inline] fn powi(mut self, mut exp: i32) -> Self { if exp < 0 { exp = exp.wrapping_neg(); self = self.recip(); } Powu::powu(self, exp as u32) } } impl Powi for f64 { #[inline] fn powi(mut self, mut exp: i32) -> Self { if exp < 0 { exp = exp.wrapping_neg(); self = self.recip(); } Powu::powu(self, exp as u32) } } impl Recip for f32 { #[inline] fn recip(self) -> Self { 1.0 / self } } impl Recip for f64 { #[inline] fn recip(self) -> Self { 1.0 / self } } impl Exp for f32 { #[inline] fn exp(self) -> Self { ::libm::expf(self) } } impl Exp for f64 { #[inline] fn exp(self) -> Self { ::libm::exp(self) } } impl Hypot for f32 { #[inline] fn hypot(self, other: Self) -> Self { ::libm::hypotf(self, other) } } impl Hypot for f64 { #[inline] fn hypot(self, other: Self) -> Self { ::libm::hypot(self, other) } } impl Round for f32 { #[inline] fn round(self) -> Self { ::libm::roundf(self) } #[inline] fn floor(self) -> Self { ::libm::floorf(self) } #[inline] fn ceil(self) -> Self { ::libm::ceilf(self) } } impl Round for f64 { #[inline] fn round(self) -> Self { ::libm::round(self) } #[inline] fn floor(self) -> Self { ::libm::floor(self) } #[inline] fn ceil(self) -> Self { ::libm::ceil(self) } } impl MulAdd for f32 { #[inline] fn mul_add(self, m: Self, a: Self) -> Self { ::libm::fmaf(self, m, a) } } impl MulAdd for f64 { #[inline] fn mul_add(self, m: Self, a: Self) -> Self { ::libm::fma(self, m, a) } } palette-0.7.5/src/num/wide.rs000064400000000000000000000212341046102023000141610ustar 00000000000000use ::wide::{f32x4, f32x8, f64x2, f64x4, CmpEq, CmpGe, CmpGt, CmpLe, CmpLt, CmpNe}; use super::*; macro_rules! impl_wide_float { ($($ty: ident { array: [$scalar: ident; $n: expr], powf: $pow_self: ident, }),+) => { $( impl Real for $ty { #[inline] fn from_f64(n: f64) -> $ty { $ty::splat(n as $scalar) } } impl FromScalar for $ty { type Scalar = $scalar; #[inline] fn from_scalar(scalar: $scalar) -> Self { $ty::splat(scalar) } } impl FromScalarArray<$n> for $ty { #[inline] fn from_array(scalars: [$scalar; $n]) -> Self { scalars.into() } } impl IntoScalarArray<$n> for $ty { #[inline] fn into_array(self) -> [$scalar; $n] { self.into() } } impl Zero for $ty { #[inline] fn zero() -> Self { $ty::ZERO } } impl One for $ty { #[inline] fn one() -> Self { $ty::ONE } } impl MinMax for $ty { #[inline] fn max(self, other: Self) -> Self { $ty::max(self, other) } #[inline] fn min(self, other: Self) -> Self { $ty::min(self, other) } #[inline] fn min_max(self, other: Self) -> (Self, Self) { ($ty::min(self, other), $ty::max(self, other)) } } impl Powu for $ty { #[inline] fn powu(self, exp: u32) -> Self { pow(self, exp) } } impl IsValidDivisor for $ty { #[inline] fn is_valid_divisor(&self) -> Self { !self.cmp_eq($ty::ZERO) } } impl Trigonometry for $ty { #[inline] fn sin(self) -> Self { $ty::sin(self) } #[inline] fn cos(self) -> Self { $ty::cos(self) } #[inline] fn sin_cos(self) -> (Self, Self) { $ty::sin_cos(self) } #[inline] fn tan(self) -> Self { $ty::tan(self) } #[inline] fn asin(self) -> Self { $ty::asin(self) } #[inline] fn acos(self) -> Self { $ty::acos(self) } #[inline] fn atan(self) -> Self { $ty::atan(self) } #[inline] fn atan2(self, other: Self) -> Self { $ty::atan2(self, other) } } impl Abs for $ty { #[inline] fn abs(self) -> Self { $ty::abs(self) } } impl Sqrt for $ty { #[inline] fn sqrt(self) -> Self { $ty::sqrt(self) } } impl Cbrt for $ty { #[inline] fn cbrt(self) -> Self { let mut array = self.into_array(); for scalar in &mut array { *scalar = scalar.cbrt(); } array.into() } } impl Powf for $ty { #[inline] fn powf(self, exp: Self) -> Self { $ty::$pow_self(self, exp) } } impl Powi for $ty { #[inline] fn powi(mut self, mut exp: i32) -> Self { if exp < 0 { exp = exp.wrapping_neg(); self = self.recip(); } Powu::powu(self, exp as u32) } } // impl Recip for $ty { // #[inline] // fn recip(self) -> Self { // $ty::recip(self) // } // } impl Exp for $ty { #[inline] fn exp(self) -> Self { $ty::exp(self) } } impl Hypot for $ty { #[inline] fn hypot(self, other: Self) -> Self { (self * self + other * other).sqrt() } } impl Round for $ty { #[inline] fn round(self) -> Self { $ty::round(self) } #[inline] fn floor(self) -> Self { let mut array = self.into_array(); for scalar in &mut array { *scalar = scalar.floor(); } array.into() } #[inline] fn ceil(self) -> Self { let mut array = self.into_array(); for scalar in &mut array { *scalar = scalar.ceil(); } array.into() } } impl Clamp for $ty { #[inline] fn clamp(self, min: Self, max: Self) -> Self { self.min(max).max(min) } #[inline] fn clamp_min(self, min: Self) -> Self { $ty::max(self, min) } #[inline] fn clamp_max(self, max: Self) -> Self { $ty::min(self, max) } } impl ClampAssign for $ty { #[inline] fn clamp_assign(&mut self, min: Self, max: Self) { *self = $ty::clamp(*self, min, max); } #[inline] fn clamp_min_assign(&mut self, min: Self) { *self = $ty::max(*self, min); } #[inline] fn clamp_max_assign(&mut self, max: Self) { *self = $ty::min(*self, max); } } impl PartialCmp for $ty { #[inline] fn lt(&self, other: &Self) -> Self::Mask { self.cmp_lt(*other) } #[inline] fn lt_eq(&self, other: &Self) -> Self::Mask { self.cmp_le(*other) } #[inline] fn eq(&self, other: &Self) -> Self::Mask { self.cmp_eq(*other) } #[inline] fn neq(&self, other: &Self) -> Self::Mask { self.cmp_ne(*other) } #[inline] fn gt_eq(&self, other: &Self) -> Self::Mask { self.cmp_ge(*other) } #[inline] fn gt(&self, other: &Self) -> Self::Mask { self.cmp_gt(*other) } } impl MulAdd for $ty { #[inline] fn mul_add(self, m: Self, a: Self) -> Self { $ty::mul_add(self, m, a) } } impl MulSub for $ty { #[inline] fn mul_sub(self, m: Self, s: Self) -> Self { $ty::mul_sub(self, m, s) } } )+ }; } impl_wide_float!( f32x4 { array: [f32; 4], powf: pow_f32x4, }, f32x8 { array: [f32; 8], powf: pow_f32x8, }, f64x2 { array: [f64; 2], powf: pow_f64x2, }, f64x4 { array: [f64; 4], powf: pow_f64x4, } ); impl Recip for f32x4 { #[inline] fn recip(self) -> Self { f32x4::recip(self) } } impl Recip for f32x8 { #[inline] fn recip(self) -> Self { f32x8::recip(self) } } impl Recip for f64x2 { #[inline] fn recip(self) -> Self { f64x2::ONE / self } } impl Recip for f64x4 { #[inline] fn recip(self) -> Self { f64x4::ONE / self } } palette-0.7.5/src/num.rs000064400000000000000000000546471046102023000132470ustar 00000000000000//! Traits for abstracting over numeric types. //! //! These traits describe various numeric properties and operations. They are //! similar in purpose to the immensely helpful traits in //! [`num-traits`](https://crates.io/crates/num-traits/), but the structure is //! different. The philosophy behind this module is to focus on capabilities, //! rather than categories, and to assume as little as possible. Within reason. //! //! Instead of having large traits with a lot of methods and dependencies, each //! operation (or group of operations), are separated into their own traits. //! This allows number types to have partial compatibility by only implementing //! some of the traits, and new methods can be added as new traits without //! affecting old functionality. use core::ops::{Add, Div, Mul, Neg, Sub}; use crate::bool_mask::HasBoolMask; #[cfg(all(not(feature = "std"), feature = "libm"))] mod libm; #[cfg(feature = "wide")] mod wide; /// Numbers that belong to the real number set. It's both a semantic marker and /// provides a constructor for number constants. pub trait Real { /// Create a number from an `f64` value, mainly for converting constants. #[must_use] fn from_f64(n: f64) -> Self; } /// Trait for creating a vectorized value from a scalar value. pub trait FromScalar { /// The scalar type that is stored in each lane of `Self`. Scalar types /// should set this to equal `Self`. type Scalar; /// Create a new vectorized value where each lane is `scalar`. This /// corresponds to `splat` for SIMD types. #[must_use] fn from_scalar(scalar: Self::Scalar) -> Self; } /// Conversion from an array of scalars to a vectorized value. pub trait FromScalarArray: FromScalar { /// Creates a vectorized value from an array of scalars. #[must_use] fn from_array(scalars: [Self::Scalar; N]) -> Self; } /// Conversion from a vectorized value to an array of scalars. pub trait IntoScalarArray: FromScalar { /// Creates an array of scalars from a vectorized value. #[must_use] fn into_array(self) -> [Self::Scalar; N]; } /// Methods for the value `0`. pub trait Zero { /// Create a new `0` value. #[must_use] fn zero() -> Self; } /// Methods for the value `1`. pub trait One { /// Create a new `1` value. #[must_use] fn one() -> Self; } /// A helper trait that collects arithmetic traits under one name. pub trait Arithmetics where Self: Add + Sub + Mul + Div + Neg + Sized, for<'a> Self: Add<&'a Self, Output = Self> + Sub<&'a Self, Output = Self> + Mul<&'a Self, Output = Self> + Div<&'a Self, Output = Self>, { } impl Arithmetics for T where T: Add + Sub + Mul + Div + Neg + Sized, for<'a> Self: Add<&'a Self, Output = Self> + Sub<&'a Self, Output = Self> + Mul<&'a Self, Output = Self> + Div<&'a Self, Output = Self>, { } /// Methods for getting the largest or smallest of two values. pub trait MinMax: Sized { /// Return the smallest of `self` and `other`. #[must_use] fn min(self, other: Self) -> Self; /// Return the largest of `self` and `other`. #[must_use] fn max(self, other: Self) -> Self; /// Return a pair of `self` and `other`, where the smallest is the first /// value and the largest is the second. #[must_use] fn min_max(self, other: Self) -> (Self, Self); } /// Trigonometry methods and their inverses. pub trait Trigonometry: Sized { /// Compute the sine of `self` (in radians). #[must_use] fn sin(self) -> Self; /// Compute the cosine of `self` (in radians). #[must_use] fn cos(self) -> Self; /// Simultaneously compute the sine and cosine of `self` (in radians). /// Returns `(sin(self), cos(self))`. #[must_use] fn sin_cos(self) -> (Self, Self); /// Compute the tangent of `self` (in radians). #[must_use] fn tan(self) -> Self; /// Compute the arcsine in radians of `self`. #[must_use] fn asin(self) -> Self; /// Compute the arccosine in radians of `self`. #[must_use] fn acos(self) -> Self; /// Compute the arctangent in radians of `self`. #[must_use] fn atan(self) -> Self; /// Compute the arctangent in radians of `self` (y) and `other` (x). #[must_use] fn atan2(self, other: Self) -> Self; } /// Method for getting the absolute value of a number. pub trait Abs { /// Returns the absolute value of `self`. #[must_use] fn abs(self) -> Self; } /// Method for getting the square root of a number. pub trait Sqrt { /// Returns the square root of `self`. #[must_use] fn sqrt(self) -> Self; } /// Method for getting the cube root of a number. pub trait Cbrt { /// Returns the cube root of `self`. #[must_use] fn cbrt(self) -> Self; } /// Method for raising a number by a real number exponent. /// /// The name "powf" is kept for familiarity, even though the exponent doesn't /// have to be a floating point number. pub trait Powf { /// Return `self` raised to the power of `exp`. #[must_use] fn powf(self, exp: Self) -> Self; } /// Method for raising a number by a signed integer exponent. pub trait Powi { /// Return `self` raised to the power of `exp`. #[must_use] fn powi(self, exp: i32) -> Self; } /// Method for raising a number by a n unsigned integer exponent. pub trait Powu { /// Return `self` raised to the power of `exp`. #[must_use] fn powu(self, exp: u32) -> Self; } /// Method for calculating `1 / x`. pub trait Recip { /// Return `1 / self`. #[must_use] fn recip(self) -> Self; } /// Methods for calculating `e ^ x`, pub trait Exp { /// Return `e ^ self`. #[must_use] fn exp(self) -> Self; } /// Methods for checking if a number can be used as a divisor. pub trait IsValidDivisor: HasBoolMask { /// Return `true` if `self` can be used as a divisor in `x / self`. /// /// This checks that division by `self` will result in a finite and defined /// value. Integers check for `self != 0`, while floating point types call /// [`is_normal`][std::primitive::f32::is_normal]. #[must_use] fn is_valid_divisor(&self) -> Self::Mask; } /// Methods for calculating the lengths of a hypotenuse. pub trait Hypot { /// Returns the length of the hypotenuse formed by `self` and `other`, i.e. /// `sqrt(self * self + other * other)`. #[must_use] fn hypot(self, other: Self) -> Self; } /// Methods for rounding numbers to integers. pub trait Round { /// Return the nearest integer to `self`. Round half-way cases away from 0.0. #[must_use] fn round(self) -> Self; /// Return the largest integer less than or equal to `self`. #[must_use] fn floor(self) -> Self; /// Return the smallest integer greater than or equal to `self`. #[must_use] fn ceil(self) -> Self; } /// Trait for clamping a value. pub trait Clamp { /// Clamp self to be within the range `[min, max]`. #[must_use] fn clamp(self, min: Self, max: Self) -> Self; /// Clamp self to be within the range `[min, ∞)`. #[must_use] fn clamp_min(self, min: Self) -> Self; /// Clamp self to be within the range `(-∞, max]`. #[must_use] fn clamp_max(self, max: Self) -> Self; } /// Assigning trait for clamping a value. pub trait ClampAssign { /// Clamp self to be within the range `[min, max]`. fn clamp_assign(&mut self, min: Self, max: Self); /// Clamp self to be within the range `[min, ∞)`. fn clamp_min_assign(&mut self, min: Self); /// Clamp self to be within the range `(-∞, max]`. fn clamp_max_assign(&mut self, max: Self); } /// Combined multiplication and addition operation. pub trait MulAdd { /// Multiplies self with `m` and add `a`, as in `(self * m) + a`. #[must_use] fn mul_add(self, m: Self, a: Self) -> Self; } /// Combined multiplication and subtraction operation. pub trait MulSub { /// Multiplies self with `m` and subtract `s`, as in `(self * m) - s`. #[must_use] fn mul_sub(self, m: Self, s: Self) -> Self; } /// Saturating addition operation. pub trait SaturatingAdd { /// The resulting type. type Output; /// Returns the sum of `self` and `other`, but saturates instead of overflowing. #[must_use] fn saturating_add(self, other: Rhs) -> Self::Output; } /// Saturating subtraction operation. pub trait SaturatingSub { /// The resulting type. type Output; /// Returns the difference of `self` and `other`, but saturates instead of overflowing. #[must_use] fn saturating_sub(self, other: Rhs) -> Self::Output; } macro_rules! impl_uint { ($($ty: ident),+) => { $( impl FromScalar for $ty { type Scalar = Self; #[inline] fn from_scalar(scalar: Self) -> Self { scalar } } impl FromScalarArray<1> for $ty { #[inline] fn from_array(scalars: [Self; 1]) -> Self { let [scalar] = scalars; scalar } } impl IntoScalarArray<1> for $ty { #[inline] fn into_array(self) -> [Self; 1] { [self] } } impl Zero for $ty { #[inline] fn zero() -> Self { 0 } } impl One for $ty { #[inline] fn one() -> Self { 1 } } impl MinMax for $ty { #[inline] fn min(self, other: Self) -> Self { core::cmp::Ord::min(self, other) } #[inline] fn max(self, other: Self) -> Self { core::cmp::Ord::max(self, other) } #[inline] fn min_max(self, other: Self) -> (Self, Self) { if self > other { (other, self) } else { (self, other) } } } impl Powu for $ty { #[inline] fn powu(self, exp: u32) -> Self { pow(self, exp) } } impl IsValidDivisor for $ty { #[inline] fn is_valid_divisor(&self) -> bool { *self != 0 } } impl Clamp for $ty { #[inline] fn clamp(self, min: Self, max: Self) -> Self { core::cmp::Ord::clamp(self, min, max) } #[inline] fn clamp_min(self, min: Self) -> Self { core::cmp::Ord::max(self, min) } #[inline] fn clamp_max(self, max: Self) -> Self { core::cmp::Ord::min(self, max) } } impl ClampAssign for $ty { #[inline] fn clamp_assign(&mut self, min: Self, max: Self) { *self = core::cmp::Ord::clamp(*self, min, max); } #[inline] fn clamp_min_assign(&mut self, min: Self) { *self = core::cmp::Ord::max(*self, min); } #[inline] fn clamp_max_assign(&mut self, max: Self) { *self = core::cmp::Ord::min(*self, max); } } impl MulAdd for $ty { #[inline] fn mul_add(self, m: Self, a: Self) -> Self { (self * m) + a } } impl MulSub for $ty { #[inline] fn mul_sub(self, m: Self, s: Self) -> Self { (self * m) - s } } impl SaturatingAdd for $ty { type Output = $ty; #[inline] fn saturating_add(self, other: Self) -> Self{ <$ty>::saturating_add(self, other) } } impl SaturatingSub for $ty { type Output = $ty; #[inline] fn saturating_sub(self, other: Self) -> Self{ <$ty>::saturating_sub(self, other) } } )+ }; } macro_rules! impl_float { ($($ty: ident),+) => { $( impl Real for $ty { #[inline] fn from_f64(n: f64) -> $ty { n as $ty } } impl FromScalar for $ty { type Scalar = Self; #[inline] fn from_scalar(scalar: Self) -> Self { scalar } } impl FromScalarArray<1> for $ty { #[inline] fn from_array(scalars: [Self; 1]) -> Self { let [scalar] = scalars; scalar } } impl IntoScalarArray<1> for $ty { #[inline] fn into_array(self) -> [Self; 1] { [self] } } impl Zero for $ty { #[inline] fn zero() -> Self { 0.0 } } impl One for $ty { #[inline] fn one() -> Self { 1.0 } } impl MinMax for $ty { #[inline] fn max(self, other: Self) -> Self { $ty::max(self, other) } #[inline] fn min(self, other: Self) -> Self { $ty::min(self, other) } #[inline] fn min_max(self, other: Self) -> (Self, Self) { if self > other { (other, self) } else { (self, other) } } } impl Powu for $ty { #[inline] fn powu(self, exp: u32) -> Self { pow(self, exp) } } impl IsValidDivisor for $ty { #[inline] fn is_valid_divisor(&self) -> bool { $ty::is_normal(*self) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Trigonometry for $ty { #[inline] fn sin(self) -> Self { $ty::sin(self) } #[inline] fn cos(self) -> Self { $ty::cos(self) } #[inline] fn sin_cos(self) -> (Self, Self) { $ty::sin_cos(self) } #[inline] fn tan(self) -> Self { $ty::tan(self) } #[inline] fn asin(self) -> Self { $ty::asin(self) } #[inline] fn acos(self) -> Self { $ty::acos(self) } #[inline] fn atan(self) -> Self { $ty::atan(self) } #[inline] fn atan2(self, other: Self) -> Self { $ty::atan2(self, other) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Abs for $ty { #[inline] fn abs(self) -> Self { $ty::abs(self) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Sqrt for $ty { #[inline] fn sqrt(self) -> Self { $ty::sqrt(self) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Cbrt for $ty { #[inline] fn cbrt(self) -> Self { $ty::cbrt(self) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Powf for $ty { #[inline] fn powf(self, exp: Self) -> Self { $ty::powf(self, exp) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Powi for $ty { #[inline] fn powi(self, exp: i32) -> Self { $ty::powi(self, exp) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Recip for $ty { #[inline] fn recip(self) -> Self { $ty::recip(self) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Exp for $ty { #[inline] fn exp(self) -> Self { $ty::exp(self) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Hypot for $ty { #[inline] fn hypot(self, other: Self) -> Self { $ty::hypot(self, other) } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl Round for $ty { #[inline] fn round(self) -> Self { $ty::round(self) } #[inline] fn floor(self) -> Self { $ty::floor(self) } #[inline] fn ceil(self) -> Self { $ty::ceil(self) } } impl Clamp for $ty { #[inline] fn clamp(self, min: Self, max: Self) -> Self { $ty::clamp(self, min, max) } #[inline] fn clamp_min(self, min: Self) -> Self { $ty::max(self, min) } #[inline] fn clamp_max(self, max: Self) -> Self { $ty::min(self, max) } } impl ClampAssign for $ty { #[inline] fn clamp_assign(&mut self, min: Self, max: Self) { *self = $ty::clamp(*self, min, max); } #[inline] fn clamp_min_assign(&mut self, min: Self) { *self = $ty::max(*self, min); } #[inline] fn clamp_max_assign(&mut self, max: Self) { *self = $ty::min(*self, max); } } #[cfg(any(feature = "std", all(test, not(feature = "libm"))))] impl MulAdd for $ty { #[inline] fn mul_add(self, m: Self, a: Self) -> Self { $ty::mul_add(self, m, a) } } impl MulSub for $ty { #[inline] fn mul_sub(self, m: Self, s: Self) -> Self { (self * m) - s } } )+ }; } impl_uint!(u8, u16, u32, u64, u128); impl_float!(f32, f64); /// "borrowed" from num_traits /// /// Raises a value to the power of exp, using exponentiation by squaring. /// /// Note that `0⁰` (`pow(0, 0)`) returns `1`. Mathematically this is undefined. // // # Example // // ```rust // use num_traits::pow; // // assert_eq!(pow(2i8, 4), 16); // assert_eq!(pow(6u8, 3), 216); // assert_eq!(pow(0u8, 0), 1); // Be aware if this case affects you // ``` #[inline] fn pow>(mut base: T, mut exp: u32) -> T { if exp == 0 { return T::one(); } while exp & 1 == 0 { base = base.clone() * base; exp >>= 1; } if exp == 1 { return base; } let mut acc = base.clone(); while exp > 1 { exp >>= 1; base = base.clone() * base; if exp & 1 == 1 { acc = acc * base.clone(); } } acc } /// Trait for lanewise comparison of two values. /// /// This is similar to `PartialEq` and `PartialOrd`, except that it returns a /// Boolean mask instead of `bool` or [`Ordering`][core::cmp::Ordering]. pub trait PartialCmp: HasBoolMask { /// Compares `self < other`. #[must_use] fn lt(&self, other: &Self) -> Self::Mask; /// Compares `self <= other`. #[must_use] fn lt_eq(&self, other: &Self) -> Self::Mask; /// Compares `self == other`. #[must_use] fn eq(&self, other: &Self) -> Self::Mask; /// Compares `self != other`. #[must_use] fn neq(&self, other: &Self) -> Self::Mask; /// Compares `self >= other`. #[must_use] fn gt_eq(&self, other: &Self) -> Self::Mask; /// Compares `self > other`. #[must_use] fn gt(&self, other: &Self) -> Self::Mask; } macro_rules! impl_partial_cmp { ($($ty:ident),+) => { $( impl PartialCmp for $ty { #[inline] fn lt(&self, other: &Self) -> Self::Mask { self < other } #[inline] fn lt_eq(&self, other: &Self) -> Self::Mask { self <= other } #[inline] fn eq(&self, other: &Self) -> Self::Mask { self == other } #[inline] fn neq(&self, other: &Self) -> Self::Mask { self != other } #[inline] fn gt_eq(&self, other: &Self) -> Self::Mask { self >= other } #[inline] fn gt(&self, other: &Self) -> Self::Mask { self > other } } )+ }; } impl_partial_cmp!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); palette-0.7.5/src/ok_utils.rs000064400000000000000000000544011046102023000142650ustar 00000000000000//! Traits and functions used in Ok* color spaces #[cfg(feature = "approx")] #[cfg(test)] use crate::{angle::RealAngle, num::Trigonometry, OklabHue}; use crate::{ convert::IntoColorUnclamped, num::{Arithmetics, Cbrt, MinMax, One, Powi, Real, Sqrt, Zero}, HasBoolMask, LinSrgb, Oklab, }; /// Finds intersection of the line defined by /// /// L = l0 * (1 - t) + t * l1; /// /// C = t * c1; /// /// a and b must be normalized so a² + b² == 1 fn find_gamut_intersection(a: T, b: T, l1: T, c1: T, l0: T, cusp: LC) -> T where T: Real + One + Zero + Arithmetics + MinMax + HasBoolMask + PartialOrd + Clone, { // Find the intersection for upper and lower half separately if ((l1.clone() - &l0) * &cusp.chroma - (cusp.lightness.clone() - &l0) * &c1) <= T::zero() { // Lower half cusp.chroma.clone() * &l0 / (c1 * cusp.lightness + cusp.chroma * (l0 - l1)) } else { // Upper half // First intersect with triangle let t = cusp.chroma.clone() * (l0.clone() - T::one()) / (c1.clone() * (cusp.lightness - T::one()) + cusp.chroma * (l0.clone() - &l1)); // Then one step Halley's method { let dl = l1.clone() - &l0; let dc = c1.clone(); let k_l = T::from_f64(0.3963377774) * &a + T::from_f64(0.2158037573) * &b; let k_m = -T::from_f64(0.1055613458) * &a - T::from_f64(0.0638541728) * &b; let k_s = -T::from_f64(0.0894841775) * a - T::from_f64(1.2914855480) * b; let l_dt = dl.clone() + dc.clone() * &k_l; let m_dt = dl.clone() + dc.clone() * &k_m; let s_dt = dl + dc * &k_s; // If higher accuracy is required, 2 or 3 iterations of the following block can be used: { let lightness = l0 * (T::one() - &t) + t.clone() * l1; let chroma = t.clone() * c1; let l_ = lightness.clone() + chroma.clone() * k_l; let m_ = lightness.clone() + chroma.clone() * k_m; let s_ = lightness + chroma * k_s; let l = l_.clone() * &l_ * &l_; let m = m_.clone() * &m_ * &m_; let s = s_.clone() * &s_ * &s_; let ldt = T::from_f64(3.0) * &l_dt * &l_ * &l_; let mdt = T::from_f64(3.0) * &m_dt * &m_ * &m_; let sdt = T::from_f64(3.0) * &s_dt * &s_ * &s_; let ldt2 = T::from_f64(6.0) * &l_dt * l_dt * l_; let mdt2 = T::from_f64(6.0) * &m_dt * m_dt * m_; let sdt2 = T::from_f64(6.0) * &s_dt * s_dt * s_; let r = T::from_f64(4.0767416621) * &l - T::from_f64(3.3077115913) * &m + T::from_f64(0.2309699292) * &s - T::one(); let r1 = T::from_f64(4.0767416621) * &ldt - T::from_f64(3.3077115913) * &mdt + T::from_f64(0.2309699292) * &sdt; let r2 = T::from_f64(4.0767416621) * &ldt2 - T::from_f64(3.3077115913) * &mdt2 + T::from_f64(0.2309699292) * &sdt2; let u_r = r1.clone() / (r1.clone() * r1 - T::from_f64(0.5) * &r * r2); let mut t_r = -r * &u_r; let g = -T::from_f64(1.2684380046) * &l + T::from_f64(2.6097574011) * &m - T::from_f64(0.3413193965) * &s - T::one(); let g1 = -T::from_f64(1.2684380046) * &ldt + T::from_f64(2.6097574011) * &mdt - T::from_f64(0.3413193965) * &sdt; let g2 = -T::from_f64(1.2684380046) * &ldt2 + T::from_f64(2.6097574011) * &mdt2 - T::from_f64(0.3413193965) * &sdt2; let u_g = g1.clone() / (g1.clone() * g1 - T::from_f64(0.5) * &g * g2); let mut t_g = -g * &u_g; let b = -T::from_f64(0.0041960863) * l - T::from_f64(0.7034186147) * m + T::from_f64(1.7076147010) * s - T::one(); let b1 = -T::from_f64(0.0041960863) * ldt - T::from_f64(0.7034186147) * mdt + T::from_f64(1.7076147010) * sdt; let b2 = -T::from_f64(0.0041960863) * ldt2 - T::from_f64(0.7034186147) * mdt2 + T::from_f64(1.7076147010) * sdt2; let u_b = b1.clone() / (b1.clone() * b1 - T::from_f64(0.5) * &b * b2); let mut t_b = -b * &u_b; // flt_max really is a constant, but cannot be defined as one due to the T::from_f64 function let flt_max = T::from_f64(10e5); t_r = if u_r >= T::zero() { t_r } else { flt_max.clone() }; t_g = if u_g >= T::zero() { t_g } else { flt_max.clone() }; t_b = if u_b >= T::zero() { t_b } else { flt_max }; t + T::min(t_r, T::min(t_g, t_b)) } } } } pub struct ChromaValues { pub zero: T, pub mid: T, pub max: T, } impl ChromaValues where T: Real + One + Zero + Arithmetics + MinMax + Cbrt + Sqrt + Powi + Clone + HasBoolMask + PartialOrd, Oklab: IntoColorUnclamped>, { // Corresponds to `get_Cs` in the reference implementation. Assumes that // `lightness != 1.0` and `lightness != 0.0`. pub fn from_normalized(lightness: T, a_: T, b_: T) -> Self { let cusp = LC::find_cusp(a_.clone(), b_.clone()); let max_chroma = find_gamut_intersection( a_.clone(), b_.clone(), lightness.clone(), T::one(), lightness.clone(), cusp.clone(), ); let st_max = ST::from(cusp); // Scale factor to compensate for the curved part of gamut shape: let k = max_chroma.clone() / T::min( lightness.clone() * st_max.s, (T::one() - &lightness) * st_max.t, ); let c_mid = { let st_mid = ST::mid(a_, b_); // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma. let c_a = lightness.clone() * st_mid.s; let c_b = (T::one() - &lightness) * st_mid.t; T::from_f64(0.9) * k * T::sqrt(T::sqrt( T::one() / (T::one() / (c_a.clone() * &c_a * &c_a * &c_a) + T::one() / (c_b.clone() * &c_b * &c_b * &c_b)), )) }; let c_0 = { // for C_0, the shape is independent of hue, so ST are constant. // Values picked to roughly be the average values of ST. let c_a = lightness.clone() * T::from_f64(0.4); let c_b = (T::one() - lightness) * T::from_f64(0.8); // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma. T::sqrt(T::one() / (T::one() / (c_a.clone() * c_a) + T::one() / (c_b.clone() * c_b))) }; Self { zero: c_0, mid: c_mid, max: max_chroma, } } } /// A `lightness`-`chroma` representation of a point in the `sRGB` gamut for a fixed hue. /// /// Gamut is the range of representable colors of a color space. In this case the /// `sRGB` color space. /// /// Only together are `lightness` and `chroma` guaranteed to be inside the `sRGB` gamut. /// While a color with lower `chroma` will always stay in the gamut, a color of raised /// *and lowered* lightness might move the point outside the gamut. /// ///# See /// [LC diagram samples](https://bottosson.github.io/posts/gamutclipping/#gamut-clipping) #[derive(Debug, Copy, Clone)] pub(crate) struct LC { /// The lightness of the color. 0 corresponds to black. 1 corresponds to white pub lightness: T, /// The chroma of the color. 0 corresponds to totally desaturated (white, grey or black). /// Larger values correspond to colorful values. /// ///Note: the maximum representable value depends on the lightness and the hue. pub chroma: T, } /// The number of iterations used for optimizing the result of [`LC::max_saturation`]. /// /// Must match [`MAX_SRGB_SATURATION_INACCURACY`] pub(crate) const MAX_SRGB_SATURATION_SEARCH_MAX_ITER: usize = 1; /// The expected inaccuracy of the result of [`LC::max_saturation`], optimized with /// [`MAX_SRGB_SATURATION_SEARCH_MAX_ITER`] iterations pub(crate) const MAX_SRGB_SATURATION_INACCURACY: f64 = 1e-6; impl LC where T: Real + One + Arithmetics + Powi + HasBoolMask + PartialOrd + Clone, { /// Returns the cusp of the geometrical shape of representable `sRGB` colors for /// normalized `a` and `b` values of an `OKlabHue`, where "normalized" means, `a² + b² == 1`. /// /// The cusp solely depends on the maximum saturation of the hue, but is expressed as a /// combination of lightness and chroma. pub fn find_cusp(a: T, b: T) -> Self where T: MinMax + Cbrt, Oklab: IntoColorUnclamped>, { // First, find the maximum saturation (saturation S = C/L) let max_saturation = Self::max_saturation(a.clone(), b.clone()); // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1: let rgb_at_max: LinSrgb = Oklab::new( T::one(), max_saturation.clone() * a, max_saturation.clone() * b, ) .into_color_unclamped(); let max_lightness = T::cbrt(T::one() / T::max(T::max(rgb_at_max.red, rgb_at_max.green), rgb_at_max.blue)); Self { lightness: max_lightness.clone(), chroma: max_lightness * max_saturation, } } /// Returns the maximum `sRGB`-saturation (chroma / lightness) for the hue (`a` and `b`). /// /// # Arguments /// * `a` - the green/redness of the hue /// * `b` - the blue/yellowness of the hue /// /// `a` and `b` must be normalized to a chroma (`a²+b²`) of `1`. /// # See /// [Original C-Version](https://bottosson.github.io/posts/gamutclipping/#intersection-with-srgb-gamut) fn max_saturation(a: T, b: T) -> T { // Max saturation will be reached, when one of r, g or b goes below zero. // Select different coefficients depending on which component goes below zero first // wl, wm and ws are coefficients for https://en.wikipedia.org/wiki/LMS_color_space // -- the color space modelling human perception. let (k0, k1, k2, k3, k4, wl, wm, ws) = if T::from_f64(-1.88170328) * &a - T::from_f64(0.80936493) * &b > T::one() { // red component at zero first ( T::from_f64(1.19086277), T::from_f64(1.76576728), T::from_f64(0.59662641), T::from_f64(0.75515197), T::from_f64(0.56771245), T::from_f64(4.0767416621), T::from_f64(-3.3077115913), T::from_f64(0.2309699292), ) } else if T::from_f64(1.81444104) * &a - T::from_f64(1.19445276) * &b > T::one() { //green component at zero first ( T::from_f64(0.73956515), T::from_f64(-0.45954404), T::from_f64(0.08285427), T::from_f64(0.12541070), T::from_f64(0.14503204), T::from_f64(-1.2684380046), T::from_f64(2.6097574011), T::from_f64(-0.3413193965), ) } else { //blue component at zero first ( T::from_f64(1.35733652), T::from_f64(-0.00915799), T::from_f64(-1.15130210), T::from_f64(-0.50559606), T::from_f64(0.00692167), T::from_f64(-0.0041960863), T::from_f64(-0.7034186147), T::from_f64(1.7076147010), ) }; // Approximate max saturation using a polynomial let mut approx_max_saturation = k0 + k1 * &a + k2 * &b + k3 * a.clone().powi(2) + k4 * &a * &b; // Get closer with Halley's method let k_l = T::from_f64(0.3963377774) * &a + T::from_f64(0.2158037573) * &b; let k_m = T::from_f64(-0.1055613458) * &a - T::from_f64(0.0638541728) * &b; let k_s = T::from_f64(-0.0894841775) * a - T::from_f64(1.2914855480) * b; for _i in 0..MAX_SRGB_SATURATION_SEARCH_MAX_ITER { let l_ = T::one() + approx_max_saturation.clone() * &k_l; let m_ = T::one() + approx_max_saturation.clone() * &k_m; let s_ = T::one() + approx_max_saturation.clone() * &k_s; let l = l_.clone().powi(3); let m = m_.clone().powi(3); let s = s_.clone().powi(3); // first derivative components let l_ds = T::from_f64(3.0) * &k_l * l_.clone().powi(2); let m_ds = T::from_f64(3.0) * &k_m * m_.clone().powi(2); let s_ds = T::from_f64(3.0) * &k_s * s_.clone().powi(2); // second derivative components let l_ds2 = T::from_f64(6.0) * k_l.clone().powi(2) * l_; let m_ds2 = T::from_f64(6.0) * k_m.clone().powi(2) * m_; let s_ds2 = T::from_f64(6.0) * k_s.clone().powi(2) * s_; // let x be the approximate maximum saturation and // i the current iteration // f = f(x_i), f1 = f'(x_i), f2 = f''(x_i) for let f = wl.clone() * l + wm.clone() * m + ws.clone() * s; let f1 = wl.clone() * l_ds + wm.clone() * m_ds + ws.clone() * s_ds; let f2 = wl.clone() * l_ds2 + wm.clone() * m_ds2 + ws.clone() * s_ds2; approx_max_saturation = approx_max_saturation - f.clone() * &f1 / (f1.powi(2) - T::from_f64(0.5) * f * f2); } approx_max_saturation } } #[cfg(feature = "approx")] #[cfg(test)] impl OklabHue where T: RealAngle + One + Arithmetics + Trigonometry + MinMax + Cbrt + Powi + HasBoolMask + PartialOrd + Clone, Oklab: IntoColorUnclamped>, { pub(crate) fn srgb_limits(self) -> (LC, T, T) { let normalized_hue_vector = self.into_cartesian(); let lc = LC::find_cusp( normalized_hue_vector.0.clone(), normalized_hue_vector.1.clone(), ); let a = lc.chroma.clone() * normalized_hue_vector.0; let b = lc.chroma.clone() * normalized_hue_vector.1; (lc, a, b) } } /// A representation of [`LC`], that allows computing the maximum chroma `C` /// for a given lightness `L` in the gamut triangle of a hue as /// ```text /// C /// = min(S*L, T*(1-L)) /// = min(lc.chroma / lc.lightness * L, lc.chroma / (T::one() - lc.lightness) * (1-L)) /// ``` #[derive(Debug, Copy, Clone)] pub(crate) struct ST { /// `lc.chroma / lc.lightness` pub s: T, /// `lc.chroma / (T::one() - lc.lightness)` pub t: T, } impl From> for ST where T: Arithmetics + One + Clone, { fn from(lc: LC) -> Self { ST { s: lc.chroma.clone() / &lc.lightness, t: lc.chroma / (T::one() - lc.lightness), } } } impl ST where T: Real + Arithmetics + One + Clone, { /// Returns a smooth approximation of the location of the cusp. /// /// This polynomial was created by an optimization process. /// It has been designed so that /// /// `S_mid < S_max` and /// /// `T_mid < T_max` #[rustfmt::skip] fn mid(a_: T, b_: T) -> ST { let s = T::from_f64(0.11516993) + T::one() / ( T::from_f64(7.44778970) + T::from_f64(4.15901240) * &b_ + a_.clone() * (T::from_f64(-2.19557347)+ T::from_f64(1.75198401) * &b_ + a_.clone() * (T::from_f64(-2.13704948) - T::from_f64(10.02301043) * &b_ + a_.clone() * (T::from_f64(-4.24894561) + T::from_f64(5.38770819) * &b_+ T::from_f64(4.69891013) * &a_ ))) ); let t = T::from_f64(0.11239642)+ T::one()/ ( T::from_f64(1.61320320) - T::from_f64(0.68124379) * &b_ + a_.clone() * (T::from_f64(0.40370612) + T::from_f64(0.90148123) * &b_ + a_.clone() * (T::from_f64(-0.27087943) + T::from_f64(0.61223990) * &b_ + a_.clone() * (T::from_f64(0.00299215) - T::from_f64(0.45399568) * b_ - T::from_f64(0.14661872) * a_ ))) ); ST { s, t } } } /// Maps an `oklab_lightness` to an *sRGB* reference-white based lightness `L_r`. /// /// The `Oklab` lightness is relative, i.e. `0` is black, `1` is pure white, but /// `Oklab` is scale independent -- i.e. the luminosity of `luminance == 1.0` is undefined. /// Lightness values may mean different things in different contexts (maximum display /// luminosity, background brightness and other viewing conditions). /// /// *sRGB* however has a well defined dynamic range and a /// [D65](https://en.wikipedia.org/wiki/Illuminant_D65) reference white luminance. /// Mapping `1` to that luminance is just a matter of definition. But is say `0.8` `Oklab` /// lightness equal to `0.5` or `0.9` `sRGB` luminance? /// /// The shape and weights of `L_r` are chosen to closely matches the lightness estimate of /// the `CIELab` color space and be nearly equal at `0.5`. /// /// Inverse of [`toe_inv`] /// /// # See /// https://bottosson.github.io/posts/colorpicker/#intermission---a-new-lightness-estimate-for-oklab pub(crate) fn toe(oklab_lightness: T) -> T where T: Real + Powi + Sqrt + Arithmetics + One + Clone, { let k_1 = T::from_f64(0.206); let k_2 = T::from_f64(0.03); let k_3 = (T::one() + &k_1) / (T::one() + &k_2); T::from_f64(0.5) * (k_3.clone() * &oklab_lightness - &k_1 + T::sqrt( (k_3.clone() * &oklab_lightness - k_1).powi(2) + T::from_f64(4.0) * k_2 * k_3 * oklab_lightness, )) } /// Maps a *sRGB* reference-white based lightness to `Oklab`s scale-independent luminance. /// /// Inverse of [`toe`] pub(crate) fn toe_inv(l_r: T) -> T where T: Real + Powi + Arithmetics + One + Clone, { let k_1 = T::from_f64(0.206); let k_2 = T::from_f64(0.03); let k_3 = (T::one() + &k_1) / (T::one() + &k_2); (l_r.clone().powi(2) + k_1 * &l_r) / (k_3 * (l_r + k_2)) } #[cfg(feature = "approx")] #[cfg(test)] mod tests { use super::*; use crate::convert::FromColorUnclamped; use crate::rgb::Rgb; use crate::{encoding, Oklab, OklabHue, Srgb}; use core::str::FromStr; #[cfg_attr(miri, ignore)] #[test] fn test_roundtrip_toe_is_original() { let n = 500; for i in 0..n { let x = i as f64 / n as f64; assert_ulps_eq!(toe_inv(toe(x)), x); } let x = 1000.0; assert_ulps_eq!(toe_inv(toe(x)), x); } #[test] fn test_toe() { assert_eq!(toe(0.0), 0.0); assert_eq!(toe(1.0), 1.0); let grey50srgb: Srgb = Rgb::::from_str("#777777") .unwrap() .into_format(); let grey50oklab = Oklab::from_color_unclamped(grey50srgb); println!("grey 50% oklab lightness: {}", grey50oklab.l); assert_relative_eq!(toe(grey50oklab.l), 0.5, epsilon = 1e-3); } #[cfg_attr(miri, ignore)] #[test] fn print_min_max_srgb_chroma_of_all_hues() { struct HueLc { hue: OklabHue, lc: LC, } let mut min_chroma: HueLc = HueLc { hue: OklabHue::new(f64::NAN), lc: LC { lightness: 0.0, chroma: f64::INFINITY, }, }; let mut max_chroma: HueLc = HueLc { hue: OklabHue::new(f64::NAN), lc: LC { lightness: 0.0, chroma: 0.0, }, }; let mut min_a = f64::INFINITY; let mut min_b = f64::INFINITY; let mut max_a = -f64::INFINITY; let mut max_b = -f64::INFINITY; // use 300000 for actually computing values (takes < 10 seconds) const SAMPLE_RESOLUTION: usize = 3; for i in 0..SAMPLE_RESOLUTION * 360 { let hue: OklabHue = OklabHue::new(i as f64 / (SAMPLE_RESOLUTION as f64)); let (lc, a, b) = hue.srgb_limits(); if lc.chroma < min_chroma.lc.chroma { min_chroma = HueLc { hue, lc }; } if lc.chroma > max_chroma.lc.chroma { max_chroma = HueLc { hue, lc }; } max_a = f64::max(max_a, a); min_a = f64::min(min_a, a); max_b = f64::max(max_b, b); min_b = f64::min(min_b, b); } let (normalized_a, normalized_b) = max_chroma.hue.into_cartesian(); let (max_chroma_a, max_chroma_b) = ( normalized_a * max_chroma.lc.chroma, normalized_b * max_chroma.lc.chroma, ); println!( "Min chroma {} at hue {:?}°.", min_chroma.lc.chroma, min_chroma.hue, ); println!( "Max chroma {} at hue {:?}° (Oklab a and b {}, {}).", max_chroma.lc.chroma, max_chroma.hue, max_chroma_a, max_chroma_b ); println!("{} <= a <= {}", min_a, max_a); println!("{} <= b <= {}", min_b, max_b); } #[test] fn max_saturation_f64_eq_f32() { let lin_srgb = LinSrgb::new(0.0, 0.0, 1.0); let oklab_64 = Oklab::::from_color_unclamped(lin_srgb); let (normalized_a, normalized_b) = ( oklab_64.a / oklab_64.get_chroma(), oklab_64.b / oklab_64.get_chroma(), ); let saturation_64 = LC::max_saturation(normalized_a, normalized_b); let saturation_32 = LC::max_saturation(normalized_a as f32, normalized_b as f32); // EPSILON should be 1e-6. See issue https://github.com/Ogeon/palette/issues/296 const EPSILON: f32 = 3e-1; assert_relative_eq!( saturation_32, saturation_64 as f32, epsilon = EPSILON, max_relative = EPSILON ); } } palette-0.7.5/src/okhsl/alpha.rs000064400000000000000000000037111046102023000146370ustar 00000000000000use crate::hues::OklabHue; use crate::{angle::FromAngle, stimulus::FromStimulus, Alpha}; use super::Okhsl; /// Okhsl with an alpha component. pub type Okhsla = Alpha, T>; ///[`Okhsla`](crate::Okhsla) implementations. impl Alpha, A> { /// Create an `Okhsl` color with transparency. pub fn new>>(hue: H, saturation: T, lightness: T, alpha: A) -> Self { Alpha { color: Okhsl::new(hue, saturation, lightness), alpha, } } /// Create an `Okhsla` color. This is the same as `Okhsla::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: OklabHue, saturation: T, lightness: T, alpha: A) -> Self { Alpha { color: Okhsl::new_const(hue, saturation, lightness), alpha, } } /// Convert into another component type. pub fn into_format(self) -> Alpha, B> where U: FromStimulus + FromAngle, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert from another component type. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus + FromAngle, A: FromStimulus, { color.into_format() } /// Convert to a `(hue, saturation, lightness, alpha)` tuple. pub fn into_components(self) -> (OklabHue, T, T, A) { ( self.color.hue, self.color.saturation, self.color.lightness, self.alpha, ) } /// Convert from a `(hue, saturation, lightness, alpha)` tuple. pub fn from_components>>( (hue, saturation, lightness, alpha): (H, T, T, A), ) -> Self { Self::new(hue, saturation, lightness, alpha) } } palette-0.7.5/src/okhsl/properties.rs000064400000000000000000000033311046102023000157440ustar 00000000000000use crate::{hues::OklabHueIter, white_point::D65}; use crate::{ bool_mask::LazySelect, num::{Arithmetics, PartialCmp, Real}, stimulus::Stimulus, FromColor, OklabHue, Xyz, }; use super::Okhsl; impl_is_within_bounds! { Okhsl { saturation => [Self::min_saturation(), Self::max_saturation()], lightness => [Self::min_lightness(), Self::max_lightness()] } where T: Stimulus } impl_clamp! { Okhsl { saturation => [Self::min_saturation(), Self::max_saturation()], lightness => [Self::min_lightness(), Self::max_lightness()] } other {hue} where T: Stimulus } impl_mix_hue!(Okhsl { saturation, lightness }); impl_lighten!(Okhsl increase {lightness => [Self::min_lightness(), Self::max_lightness()]} other {hue, saturation} where T: Stimulus); impl_saturate!(Okhsl increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, lightness} where T: Stimulus); impl_hue_ops!(Okhsl, OklabHue); impl_color_add!(Okhsl, [hue, saturation, lightness]); impl_color_sub!(Okhsl, [hue, saturation, lightness]); impl_array_casts!(Okhsl, [T; 3]); impl_simd_array_conversion_hue!(Okhsl, [saturation, lightness]); impl_struct_of_array_traits_hue!(Okhsl, OklabHueIter, [saturation, lightness]); impl_eq_hue!(Okhsl, OklabHue, [hue, saturation, lightness]); #[allow(deprecated)] impl crate::RelativeContrast for Okhsl where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } palette-0.7.5/src/okhsl/random.rs000064400000000000000000000003041046102023000150250ustar 00000000000000use crate::{Okhsl, OklabHue}; impl_rand_traits_hsl_bicone!( UniformOkhsl, Okhsl { hue: UniformOklabHue => OklabHue, height: lightness, radius: saturation } ); palette-0.7.5/src/okhsl/visual_eq.rs000064400000000000000000000047431046102023000155500ustar 00000000000000use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; use crate::num::{One, Zero}; use crate::visual::{VisualColor, VisuallyEqual}; use crate::{HasBoolMask, Okhsl, OklabHue}; use approx::AbsDiffEq; use std::borrow::Borrow; use std::ops::{Mul, Neg, Sub}; impl VisualColor for Okhsl where T: PartialOrd + HasBoolMask + AbsDiffEq + One + Zero + Neg, T::Epsilon: Clone, OklabHue: AbsDiffEq, { /// Returns true, if `saturation == 0` fn is_grey(&self, epsilon: T::Epsilon) -> bool { debug_assert!(self.saturation >= -epsilon.clone()); self.saturation.abs_diff_eq(&T::zero(), epsilon) } /// Returns true, if `Self::is_grey` && `lightness >= 1`, /// i.e. the color's hue is irrelevant **and** it is at or beyond the /// `sRGB` maximum luminance. A color at or beyond maximum brightness isn't /// necessarily white. It may also be a bright shining hue. fn is_white(&self, epsilon: T::Epsilon) -> bool { self.is_grey(epsilon.clone()) && self.lightness > T::one() || self.lightness.abs_diff_eq(&T::one(), epsilon) } /// Returns true if `lightness == 0` fn is_black(&self, epsilon: T::Epsilon) -> bool { debug_assert!(self.lightness >= -epsilon.clone()); self.lightness <= epsilon } } impl VisuallyEqual for Okhsl where T: PartialOrd + HasBoolMask + RealAngle + SignedAngle + Zero + One + AngleEq + Sub + AbsDiffEq + Neg + Clone, T::Epsilon: Clone + HalfRotation + Mul, S: Borrow + Copy, O: Borrow + Copy, { fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) || VisuallyEqual::both_greyscale(s, o, epsilon.clone()) && s.borrow() .lightness .abs_diff_eq(&o.borrow().lightness, epsilon.clone()) || s.borrow().hue.abs_diff_eq(&o.borrow().hue, epsilon.clone()) && s.borrow() .saturation .abs_diff_eq(&o.borrow().saturation, epsilon.clone()) && s.borrow() .lightness .abs_diff_eq(&o.borrow().lightness, epsilon) } } palette-0.7.5/src/okhsl.rs000064400000000000000000000330431046102023000135530ustar 00000000000000//! Types for the Okhsl color space. pub use alpha::Okhsla; use crate::{ angle::FromAngle, convert::{FromColorUnclamped, IntoColorUnclamped}, num::{Arithmetics, Cbrt, Hypot, IsValidDivisor, MinMax, One, Powi, Real, Sqrt, Zero}, ok_utils::{toe, ChromaValues}, stimulus::{FromStimulus, Stimulus}, white_point::D65, GetHue, HasBoolMask, LinSrgb, Oklab, OklabHue, }; pub use self::properties::Iter; #[cfg(feature = "random")] pub use self::random::UniformOkhsl; mod alpha; mod properties; #[cfg(feature = "random")] mod random; #[cfg(test)] #[cfg(feature = "approx")] mod visual_eq; /// A Hue/Saturation/Lightness representation of [`Oklab`] in the `sRGB` color space. /// /// Allows /// * changing hue/chroma/saturation, while keeping perceived lightness constant (like HSLuv) /// * changing lightness/chroma/saturation, while keeping perceived hue constant /// * changing the perceived saturation (more or less) proportionally with the numerical /// amount of change (unlike HSLuv) #[derive(Debug, Copy, Clone, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "D65", component = "T", skip_derives(Oklab) )] #[repr(C)] pub struct Okhsl { /// The hue of the color, in degrees of a circle. /// /// For fully saturated, bright colors /// * 0° corresponds to a kind of magenta-pink (RBG #ff0188), /// * 90° to a kind of yellow (RBG RGB #ffcb00) /// * 180° to a kind of cyan (RBG #00ffe1) and /// * 240° to a kind of blue (RBG #00aefe). /// /// For s == 0 or v == 0, the hue is irrelevant. #[palette(unsafe_same_layout_as = "T")] pub hue: OklabHue, /// The saturation (freedom of black or white) of the color. /// /// * `0.0` corresponds to pure mixture of black and white without any color. /// The black to white relation depends on v. /// * `1.0` to a fully saturated color without any white. /// /// For v == 0 the saturation is irrelevant. pub saturation: T, /// The relative luminance of the color, where /// * `0.0` corresponds to pure black /// * `1.0` corresponds to white /// /// This luminance is visually similar to [Cielab](crate::Lab)'s luminance for a /// `D65` reference white point. /// /// `Okhsv`'s `value` component goes from black to non-black /// -- a maximally bright color in the `sRGB` gamut. /// /// `Okhsl`'s `lightness` component goes from black to white in the `sRGB` color space. pub lightness: T, } impl Okhsl { /// Create an Okhsl color. pub fn new>>(hue: H, saturation: T, lightness: T) -> Self { Self { hue: hue.into(), saturation, lightness, } } /// Create an `Okhsl` color. This is the same as `Okhsl::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: OklabHue, saturation: T, lightness: T) -> Self { Self { hue, saturation, lightness, } } /// Convert into another component type. pub fn into_format(self) -> Okhsl where U: FromStimulus + FromAngle, { Okhsl { hue: self.hue.into_format(), saturation: U::from_stimulus(self.saturation), lightness: U::from_stimulus(self.lightness), } } /// Convert from another component type. pub fn from_format(color: Okhsl) -> Self where T: FromStimulus + FromAngle, { color.into_format() } /// Convert to a `(h, s, l)` tuple. pub fn into_components(self) -> (OklabHue, T, T) { (self.hue, self.saturation, self.lightness) } /// Convert from a `(h, s, l)` tuple. pub fn from_components>>((hue, saturation, lightness): (H, T, T)) -> Self { Self::new(hue, saturation, lightness) } } impl Okhsl where T: Stimulus, { /// Return the `saturation` value minimum. pub fn min_saturation() -> T { T::zero() } /// Return the `saturation` value maximum. pub fn max_saturation() -> T { T::max_intensity() } /// Return the `lightness` value minimum. pub fn min_lightness() -> T { T::zero() } /// Return the `lightness` value maximum. pub fn max_lightness() -> T { T::max_intensity() } } impl_reference_component_methods_hue!(Okhsl, [saturation, lightness]); impl_struct_of_arrays_methods_hue!(Okhsl, [saturation, lightness]); /// # See /// See [`srgb_to_okhsl`](https://bottosson.github.io/posts/colorpicker/#hsl-2) impl FromColorUnclamped> for Okhsl where T: Real + One + Zero + Arithmetics + Powi + Sqrt + Hypot + MinMax + Cbrt + IsValidDivisor + HasBoolMask + PartialOrd + Clone, Oklab: GetHue> + IntoColorUnclamped>, { fn from_color_unclamped(lab: Oklab) -> Self { // refer to the SRGB reference-white-based lightness L_r as l for consistency with HSL let l = toe(lab.l.clone()); let chroma = lab.get_chroma(); // Not part of the reference implementation. Added to prevent // https://github.com/Ogeon/palette/issues/368 and other cases of NaN. if !chroma.is_valid_divisor() || lab.l == T::one() || !lab.l.is_valid_divisor() { return Self::new(T::zero(), T::zero(), l); } let hue = lab.get_hue(); let cs = ChromaValues::from_normalized(lab.l, lab.a / &chroma, lab.b / &chroma); // Inverse of the interpolation in okhsl_to_srgb: let mid = T::from_f64(0.8); let mid_inv = T::from_f64(1.25); let s = if chroma < cs.mid { let k_1 = mid.clone() * cs.zero; let k_2 = T::one() - k_1.clone() / cs.mid; let t = chroma.clone() / (k_1 + k_2 * chroma); t * mid } else { let k_0 = cs.mid.clone(); let k_1 = (T::one() - &mid) * (cs.mid.clone() * mid_inv).powi(2) / cs.zero; let k_2 = T::one() - k_1.clone() / (cs.max - cs.mid); let t = (chroma.clone() - &k_0) / (k_1 + k_2 * (chroma - k_0)); mid.clone() + (T::one() - mid) * t }; Self::new(hue, s, l) } } impl HasBoolMask for Okhsl where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Okhsl where T: Stimulus, OklabHue: Default, { fn default() -> Okhsl { Okhsl::new( OklabHue::default(), Self::min_saturation(), Self::min_lightness(), ) } } #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Okhsl where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Okhsl where T: bytemuck::Pod {} #[cfg(test)] mod tests { use crate::{ convert::{FromColorUnclamped, IntoColorUnclamped}, encoding, rgb::Rgb, Okhsl, Oklab, Srgb, }; test_convert_into_from_xyz!(Okhsl); #[cfg(feature = "approx")] mod conversion { use core::str::FromStr; use crate::{ convert::FromColorUnclamped, visual::{VisualColor, VisuallyEqual}, LinSrgb, Okhsl, Oklab, Srgb, }; #[cfg_attr(miri, ignore)] #[test] fn test_roundtrip_okhsl_oklab_is_original() { let colors = [ ( "red", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 0.0)), ), ( "green", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 0.0)), ), ( "cyan", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 1.0)), ), ( "magenta", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 1.0)), ), ( "black", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 0.0)), ), ( "grey", Oklab::from_color_unclamped(LinSrgb::new(0.5, 0.5, 0.5)), ), ( "yellow", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 0.0)), ), ( "blue", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 1.0)), ), ( "white", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 1.0)), ), ]; // unlike in okhwb we are using f64 here, which actually works. // So we can afford a small tolerance. // For some reason the roundtrip of Okhsl seems to produce a greater // divergence than the round trip of Okhsv (1e-8 vs 1e-10) const EPSILON: f64 = 1e-8; for (name, color) in colors { let rgb: Srgb = Srgb::::from_color_unclamped(color).into_format(); println!( "\n\ roundtrip of {} (#{:x} / {:?})\n\ =================================================", name, rgb, color ); println!("Color is white: {}", color.is_white(EPSILON)); let okhsl = Okhsl::from_color_unclamped(color); println!("Okhsl: {:?}", okhsl); let roundtrip_color = Oklab::from_color_unclamped(okhsl); assert!( Oklab::visually_eq(roundtrip_color, color, EPSILON), "'{}' failed.\n{:?}\n!=\n{:?}", name, roundtrip_color, color ); } } #[test] fn test_blue() { let lab = Oklab::new( 0.45201371519623734_f64, -0.03245697990291002, -0.3115281336419824, ); let okhsl = Okhsl::::from_color_unclamped(lab); assert!( abs_diff_eq!( okhsl.hue.into_raw_degrees(), 360.0 * 0.7334778365225699, epsilon = 1e-10 ), "{}\n!=\n{}", okhsl.hue.into_raw_degrees(), 360.0 * 0.7334778365225699 ); assert!( abs_diff_eq!(okhsl.saturation, 0.9999999897262261, epsilon = 1e-8), "{}\n!=\n{}", okhsl.saturation, 0.9999999897262261 ); assert!( abs_diff_eq!(okhsl.lightness, 0.366565335813274, epsilon = 1e-10), "{}\n!=\n{}", okhsl.lightness, 0.366565335813274 ); } #[test] fn test_srgb_to_okhsl() { let red_hex = "#834941"; let rgb: Srgb = Srgb::from_str(red_hex).unwrap().into_format(); let lin_rgb = LinSrgb::::from_color_unclamped(rgb); let oklab = Oklab::from_color_unclamped(lin_rgb); println!( "RGB: {:?}\n\ LinRgb: {:?}\n\ Oklab: {:?}", rgb, lin_rgb, oklab ); let okhsl = Okhsl::from_color_unclamped(oklab); // test data from Ok Color picker assert_relative_eq!( okhsl.hue.into_raw_degrees(), 360.0 * 0.07992730371382328, epsilon = 1e-10, max_relative = 1e-13 ); assert_relative_eq!(okhsl.saturation, 0.4629217183454986, epsilon = 1e-10); assert_relative_eq!(okhsl.lightness, 0.3900998146147427, epsilon = 1e-10); } } #[test] fn test_okhsl_to_srgb() { let okhsl = Okhsl::new(0.0_f32, 0.5, 0.5); let rgb = Srgb::from_color_unclamped(okhsl); let rgb8: Rgb = rgb.into_format(); let hex_str = format!("{:x}", rgb8); assert_eq!(hex_str, "aa5a74"); } #[test] fn test_okhsl_to_srgb_saturated_black() { let okhsl = Okhsl::new(0.0_f32, 1.0, 0.0); let rgb = Srgb::from_color_unclamped(okhsl); assert_eq!(rgb, Srgb::new(0.0, 0.0, 0.0)); } #[test] fn test_oklab_to_okhsl_saturated_white() { // Minimized check for the case in // https://github.com/Ogeon/palette/issues/368. It ended up resulting in // an Oklab value where a or b was larger than 0, which bypassed the // chroma check. let oklab = Oklab::new(1.0, 1.0, 0.0); let okhsl: Okhsl = oklab.into_color_unclamped(); assert_eq!(okhsl, Okhsl::new(0.0, 0.0, 1.0)); } #[test] fn test_oklab_to_okhsl_saturated_black() { // Minimized check for the case in // https://github.com/Ogeon/palette/issues/368. This wasn't the reported // case, but another variant of it. let oklab = Oklab::new(0.0, 1.0, 0.0); let okhsl: Okhsl = oklab.into_color_unclamped(); assert_eq!(okhsl, Okhsl::new(0.0, 0.0, 0.0)); } struct_of_arrays_tests!( Okhsl[hue, saturation, lightness], super::Okhsla::new(0.1f32, 0.2, 0.3, 0.4), super::Okhsla::new(0.2, 0.3, 0.4, 0.5), super::Okhsla::new(0.3, 0.4, 0.5, 0.6) ); } palette-0.7.5/src/okhsv/alpha.rs000064400000000000000000000040721046102023000146520ustar 00000000000000use super::Okhsv; use crate::angle::FromAngle; use crate::hues::OklabHue; use crate::num::{MinMax, Zero}; use crate::stimulus::FromStimulus; use crate::Alpha; /// Okhsv with an alpha component. See the [`Okhsva` implementation in /// `Alpha`](crate::Alpha#Okhsva). pub type Okhsva = Alpha, T>; ///[`Hsva`](crate::Hsva) implementations. impl Alpha, A> { /// Create an `Okhsv` color with transparency. pub fn new>>(hue: H, saturation: T, value: T, alpha: A) -> Self { Alpha { color: Okhsv::new(hue.into(), saturation, value), alpha, } } /// Create an `Okhsva` color. This is the same as `Okhsva::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: OklabHue, saturation: T, value: T, alpha: A) -> Self { Alpha { color: Okhsv::new_const(hue, saturation, value), alpha, } } /// Convert into another component type. pub fn into_format(self) -> Alpha, B> where U: FromStimulus + FromAngle, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert from another component type. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus + FromAngle, A: FromStimulus, U: Zero + MinMax, { color.into_format() } /// Convert to a `(hue, saturation, value, alpha)` tuple. pub fn into_components(self) -> (OklabHue, T, T, A) { ( self.color.hue, self.color.saturation, self.color.value, self.alpha, ) } /// Convert from a `(hue, saturation, value, alpha)` tuple. pub fn from_components>>( (hue, saturation, value, alpha): (H, T, T, A), ) -> Self { Self::new(hue, saturation, value, alpha) } } palette-0.7.5/src/okhsv/properties.rs000064400000000000000000000027131046102023000157610ustar 00000000000000use crate::hues::OklabHueIter; use crate::num::{Arithmetics, Real}; use crate::stimulus::Stimulus; use crate::{ok_utils, OklabHue}; use super::Okhsv; impl_is_within_bounds! { Okhsv { saturation => [Self::min_saturation(), Self::max_saturation()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)], value => [Self::min_value(), Self::max_value()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)] } where T: Real+Arithmetics+Stimulus } impl_clamp! { Okhsv { saturation => [Self::min_saturation(), Self::max_saturation()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)], value => [Self::min_value(), Self::max_value()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)] } other {hue} where T: Real+Arithmetics+Stimulus } impl_mix_hue!(Okhsv { saturation, value }); impl_lighten!(Okhsv increase {value => [Self::min_value(), Self::max_value()]} other {hue, saturation} where T: Real+Stimulus); impl_saturate!(Okhsv increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, value} where T:Real+ Stimulus); impl_hue_ops!(Okhsv, OklabHue); impl_color_add!(Okhsv, [hue, saturation, value]); impl_color_sub!(Okhsv, [hue, saturation, value]); impl_array_casts!(Okhsv, [T; 3]); impl_simd_array_conversion_hue!(Okhsv, [saturation, value]); impl_struct_of_array_traits_hue!(Okhsv, OklabHueIter, [saturation, value]); impl_eq_hue!(Okhsv, OklabHue, [hue, saturation, value]); palette-0.7.5/src/okhsv/random.rs000064400000000000000000000006141046102023000150430ustar 00000000000000use crate::{Okhsv, OklabHue}; impl_rand_traits_hsv_cone!( UniformOkhsv, Okhsv { hue: UniformOklabHue => OklabHue, height: value, radius: saturation } ); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Okhsv where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Okhsv where T: bytemuck::Pod {} palette-0.7.5/src/okhsv/visual_eq.rs000064400000000000000000000045551046102023000155630ustar 00000000000000use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; use crate::num::{One, Zero}; use crate::visual::{VisualColor, VisuallyEqual}; use crate::{HasBoolMask, Okhsv, OklabHue}; use approx::AbsDiffEq; use std::borrow::Borrow; use std::ops::{Mul, Neg, Sub}; impl VisualColor for Okhsv where T: PartialOrd + HasBoolMask + AbsDiffEq + One + Zero + Neg, T::Epsilon: Clone, OklabHue: AbsDiffEq, { /// Returns true, if `saturation == 0` fn is_grey(&self, epsilon: T::Epsilon) -> bool { self.saturation.abs_diff_eq(&T::zero(), epsilon) } /// Returns true, if `Self::is_grey` && `value >= 1`, /// i.e. the color's hue is irrelevant **and** it is at or beyond the /// `sRGB` maximum brightness. A color at or beyond maximum brightness isn't /// necessarily white. It can also be a bright shining hue. fn is_white(&self, epsilon: T::Epsilon) -> bool { self.is_grey(epsilon.clone()) && self.value >= T::one() || self.value.abs_diff_eq(&T::one(), epsilon) } /// Returns true if `value == 0` fn is_black(&self, epsilon: T::Epsilon) -> bool { debug_assert!(self.value >= -epsilon.clone()); self.value.abs_diff_eq(&T::zero(), epsilon) } } impl VisuallyEqual for Okhsv where T: PartialOrd + HasBoolMask + RealAngle + SignedAngle + Zero + One + AngleEq + Sub + AbsDiffEq + Neg + Clone, T::Epsilon: Clone + HalfRotation + Mul, S: Borrow + Copy, O: Borrow + Copy, { fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) || VisuallyEqual::both_greyscale(s, o, epsilon.clone()) && s.borrow() .value .abs_diff_eq(&o.borrow().value, epsilon.clone()) || s.borrow().hue.abs_diff_eq(&o.borrow().hue, epsilon.clone()) && s.borrow() .saturation .abs_diff_eq(&o.borrow().saturation, epsilon.clone()) && s.borrow().value.abs_diff_eq(&o.borrow().value, epsilon) } } palette-0.7.5/src/okhsv.rs000064400000000000000000000446551046102023000136000ustar 00000000000000//! Types for the Okhsv color space. use core::fmt::Debug; pub use alpha::Okhsva; #[cfg(feature = "random")] pub use random::UniformOkhsv; use crate::{ angle::FromAngle, bool_mask::LazySelect, convert::{FromColorUnclamped, IntoColorUnclamped}, num::{ Arithmetics, Cbrt, Hypot, IsValidDivisor, MinMax, One, Powi, Real, Sqrt, Trigonometry, Zero, }, ok_utils::{self, LC, ST}, stimulus::{FromStimulus, Stimulus}, white_point::D65, GetHue, HasBoolMask, LinSrgb, Okhwb, Oklab, OklabHue, }; pub use self::properties::Iter; mod alpha; mod properties; #[cfg(feature = "random")] mod random; #[cfg(test)] #[cfg(feature = "approx")] mod visual_eq; /// A Hue/Saturation/Value representation of [`Oklab`] in the `sRGB` color space. /// /// Allows /// * changing lightness/chroma/saturation while keeping perceived Hue constant /// (like HSV promises but delivers only partially) /// * finding the strongest color (maximum chroma) at s == 1 (like HSV) #[derive(Debug, Copy, Clone, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "D65", component = "T", skip_derives(Oklab, Okhwb) )] #[repr(C)] pub struct Okhsv { /// The hue of the color, in degrees of a circle. /// /// For fully saturated, bright colors /// * 0° corresponds to a kind of magenta-pink (RBG #ff0188), /// * 90° to a kind of yellow (RBG RGB #ffcb00) /// * 180° to a kind of cyan (RBG #00ffe1) and /// * 240° to a kind of blue (RBG #00aefe). /// /// For s == 0 or v == 0, the hue is irrelevant. #[palette(unsafe_same_layout_as = "T")] pub hue: OklabHue, /// The saturation (freedom of whitishness) of the color. /// /// * `0.0` corresponds to pure mixture of black and white without any color. /// The black to white relation depends on v. /// * `1.0` to a fully saturated color without any white. /// /// For v == 0 the saturation is irrelevant. pub saturation: T, /// The monochromatic brightness of the color. /// * `0.0` corresponds to pure black /// * `1.0` corresponds to a maximally bright colour -- be it very colorful or very white /// /// `Okhsl`'s `lightness` component goes from black to white. /// `Okhsv`'s `value` component goes from black to non-black -- a maximally bright color.. pub value: T, } impl_tuple_conversion_hue!(Okhsv as (H, T, T), OklabHue); impl HasBoolMask for Okhsv where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Okhsv where T: Stimulus, OklabHue: Default, { fn default() -> Okhsv { Okhsv::new( OklabHue::default(), Self::min_saturation(), Self::min_value(), ) } } impl Okhsv where T: Stimulus, { /// Return the `saturation` value minimum. pub fn min_saturation() -> T { T::zero() } /// Return the `saturation` value maximum. pub fn max_saturation() -> T { T::max_intensity() } /// Return the `value` value minimum. pub fn min_value() -> T { T::zero() } /// Return the `value` value maximum. pub fn max_value() -> T { T::max_intensity() } } impl_reference_component_methods_hue!(Okhsv, [saturation, value]); impl_struct_of_arrays_methods_hue!(Okhsv, [saturation, value]); impl Okhsv { /// Create an `Okhsv` color. pub fn new>>(hue: H, saturation: T, value: T) -> Self { Self { hue: hue.into(), saturation, value, } } /// Create an `Okhsv` color. This is the same as `Okhsv::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: OklabHue, saturation: T, value: T) -> Self { Self { hue, saturation, value, } } /// Convert into another component type. pub fn into_format(self) -> Okhsv where U: FromStimulus + FromAngle, { Okhsv { hue: self.hue.into_format(), saturation: U::from_stimulus(self.saturation), value: U::from_stimulus(self.value), } } /// Convert to a `(h, s, v)` tuple. pub fn into_components(self) -> (OklabHue, T, T) { (self.hue, self.saturation, self.value) } /// Convert from a `(h, s, v)` tuple. pub fn from_components>>((hue, saturation, value): (H, T, T)) -> Self { Self::new(hue, saturation, value) } } /// Converts `lab` to `Okhsv` in the bounds of sRGB. /// /// # See /// See [`srgb_to_okhsv`](https://bottosson.github.io/posts/colorpicker/#hsv-2). /// This implementation differs from srgb_to_okhsv in that it starts with the `lab` /// value and produces hues in degrees, whereas `srgb_to_okhsv` produces degree/360. impl FromColorUnclamped> for Okhsv where T: Real + MinMax + Clone + Powi + Sqrt + Cbrt + Arithmetics + Trigonometry + Zero + Hypot + One + IsValidDivisor + HasBoolMask + PartialOrd, Oklab: GetHue> + IntoColorUnclamped>, { fn from_color_unclamped(lab: Oklab) -> Self { if lab.l == T::zero() { // the color is pure black return Self::new(T::zero(), T::zero(), T::zero()); } let chroma = lab.get_chroma(); let hue = lab.get_hue(); if chroma.is_valid_divisor() { let (a_, b_) = (lab.a / &chroma, lab.b / &chroma); // For each hue the sRGB gamut can be drawn on a 2-dimensional space. // Let L_r, the lightness in relation to the possible luminance of sRGB, be spread // along the y-axis (bottom is black, top is bright) and Chroma along the x-axis // (left is desaturated, right is colorful). The gamut then takes a triangular shape, // with a concave top side and a cusp to the right. // To use saturation and brightness values, the gamut must be mapped to a square. // The lower point of the triangle is expanded to the lower side of the square. // The left side remains unchanged and the cusp of the triangle moves to the upper right. let cusp = LC::find_cusp(a_.clone(), b_.clone()); let st_max = ST::::from(cusp); let s_0 = T::from_f64(0.5); let k = T::one() - s_0.clone() / st_max.s; // first we find L_v, C_v, L_vt and C_vt let t = st_max.t.clone() / (chroma.clone() + lab.l.clone() * &st_max.t); let l_v = t.clone() * &lab.l; let c_v = t * chroma; let l_vt = ok_utils::toe_inv(l_v.clone()); let c_vt = c_v.clone() * &l_vt / &l_v; // we can then use these to invert the step that compensates for the toe and the curved top part of the triangle: let rgb_scale: LinSrgb = Oklab::new(l_vt, a_ * &c_vt, b_ * c_vt).into_color_unclamped(); let lightness_scale_factor = T::cbrt( T::one() / T::max( T::max(rgb_scale.red, rgb_scale.green), T::max(rgb_scale.blue, T::zero()), ), ); //chroma = chroma / lightness_scale_factor; // use L_r instead of L and also scale C by L_r/L let l_r = ok_utils::toe(lab.l / lightness_scale_factor); //chroma = chroma * l_r / (lab.l / lightness_scale_factor); // we can now compute v and s: let v = l_r / l_v; let s = (s_0.clone() + &st_max.t) * &c_v / ((st_max.t.clone() * s_0) + st_max.t * k * c_v); Self::new(hue, s, v) } else { // the color is totally desaturated. let v = ok_utils::toe(lab.l); Self::new(T::zero(), T::zero(), v) } } } impl FromColorUnclamped> for Okhsv where T: One + Zero + IsValidDivisor + Arithmetics, T::Mask: LazySelect, { fn from_color_unclamped(hwb: Okhwb) -> Self { let Okhwb { hue, whiteness, blackness, } = hwb; let value = T::one() - blackness; // avoid divide by zero let saturation = lazy_select! { if value.is_valid_divisor() => T::one() - (whiteness / &value), else => T::zero(), }; Self { hue, saturation, value, } } } #[cfg(test)] mod tests { use crate::{convert::FromColorUnclamped, Clamp, IsWithinBounds, LinSrgb, Okhsv, Oklab}; test_convert_into_from_xyz!(Okhsv); #[cfg(feature = "approx")] mod conversion { use core::str::FromStr; use crate::{ convert::FromColorUnclamped, encoding, rgb::Rgb, visual::VisuallyEqual, LinSrgb, Okhsv, Oklab, OklabHue, Srgb, }; #[cfg_attr(miri, ignore)] #[test] fn test_roundtrip_okhsv_oklab_is_original() { let colors = [ ( "red", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 0.0)), ), ( "green", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 0.0)), ), ( "cyan", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 1.0)), ), ( "magenta", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 1.0)), ), ( "white", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 1.0)), ), ( "black", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 0.0)), ), ( "grey", Oklab::from_color_unclamped(LinSrgb::new(0.5, 0.5, 0.5)), ), ( "yellow", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 0.0)), ), ( "blue", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 1.0)), ), ]; // unlike in okhwb we are using f64 here, which actually works. // So we can afford a small tolerance const EPSILON: f64 = 1e-10; for (name, color) in colors { let rgb: Rgb = crate::Srgb::::from_color_unclamped(color).into_format(); println!( "\n\ roundtrip of {} (#{:x} / {:?})\n\ =================================================", name, rgb, color ); let okhsv = Okhsv::from_color_unclamped(color); println!("Okhsv: {:?}", okhsv); let roundtrip_color = Oklab::from_color_unclamped(okhsv); assert!( Oklab::visually_eq(roundtrip_color, color, EPSILON), "'{}' failed.\n{:?}\n!=\n{:?}", name, roundtrip_color, color ); } } /// Compares results to results for a run of /// https://github.com/bottosson/bottosson.github.io/blob/3d3f17644d7f346e1ce1ca08eb8b01782eea97af/misc/ok_color.h /// Not to the ideal values, which should be /// hue: as is /// saturation: 1.0 /// value: 1.0 #[test] fn blue() { let lin_srgb_blue = LinSrgb::new(0.0, 0.0, 1.0); let oklab_blue_64 = Oklab::::from_color_unclamped(lin_srgb_blue); let okhsv_blue_64 = Okhsv::from_color_unclamped(oklab_blue_64); println!("Okhsv f64: {:?}\n", okhsv_blue_64); // HSV values of the reference implementation (in C) // 1 iteration : 264.0520206380550121, 0.9999910912349018, 0.9999999646150918 // 2 iterations: 264.0520206380550121, 0.9999999869716002, 0.9999999646150844 // 3 iterations: 264.0520206380550121, 0.9999999869716024, 0.9999999646150842 #[allow(clippy::excessive_precision)] let expected_hue = OklabHue::new(264.0520206380550121); let expected_saturation = 0.9999910912349018; let expected_value = 0.9999999646150918; // compare to the reference implementation values assert_abs_diff_eq!(okhsv_blue_64.hue, expected_hue, epsilon = 1e-12); assert_abs_diff_eq!( okhsv_blue_64.saturation, expected_saturation, epsilon = 1e-12 ); assert_abs_diff_eq!(okhsv_blue_64.value, expected_value, epsilon = 1e-12); } #[test] fn test_srgb_to_okhsv() { let red_hex = "#ff0004"; let rgb: Srgb = Rgb::::from_str(red_hex) .unwrap() .into_format(); let okhsv = Okhsv::from_color_unclamped(rgb); assert_relative_eq!(okhsv.saturation, 1.0, epsilon = 1e-3); assert_relative_eq!(okhsv.value, 1.0, epsilon = 1e-3); assert_relative_eq!( okhsv.hue.into_raw_degrees(), 29.0, epsilon = 1e-3, max_relative = 1e-3 ); } #[test] fn test_okhsv_to_srgb() { let okhsv = Okhsv::new(0.0_f32, 0.5, 0.5); let rgb = Srgb::from_color_unclamped(okhsv); let rgb8: Rgb = rgb.into_format(); let hex_str = format!("{:x}", rgb8); assert_eq!(hex_str, "7a4355"); } #[test] fn test_okhsv_to_srgb_saturated_black() { let okhsv = Okhsv::new(0.0_f32, 1.0, 0.0); let rgb = Srgb::from_color_unclamped(okhsv); assert_relative_eq!(rgb, Srgb::new(0.0, 0.0, 0.0)); } #[test] fn black_eq_different_black() { assert!(Okhsv::visually_eq( Okhsv::from_color_unclamped(Oklab::new(0.0, 1.0, 0.0)), Okhsv::from_color_unclamped(Oklab::new(0.0, 0.0, 1.0)), 1e-12 )); } } #[cfg(feature = "approx")] mod visual_eq { use crate::{visual::VisuallyEqual, Okhsv}; #[test] fn white_eq_different_white() { assert!(Okhsv::visually_eq( Okhsv::new(240.0, 0.0, 1.0), Okhsv::new(24.0, 0.0, 1.0), 1e-12 )); } #[test] fn white_ne_grey_or_black() { assert!(!Okhsv::visually_eq( Okhsv::new(0.0, 0.0, 0.0), Okhsv::new(0.0, 0.0, 1.0), 1e-12 )); assert!(!Okhsv::visually_eq( Okhsv::new(0.0, 0.0, 0.3), Okhsv::new(0.0, 0.0, 1.0), 1e-12 )); } #[test] fn color_neq_different_color() { assert!(!Okhsv::visually_eq( Okhsv::new(10.0, 0.01, 0.5), Okhsv::new(11.0, 0.01, 0.5), 1e-12 )); assert!(!Okhsv::visually_eq( Okhsv::new(10.0, 0.01, 0.5), Okhsv::new(10.0, 0.02, 0.5), 1e-12 )); assert!(!Okhsv::visually_eq( Okhsv::new(10.0, 0.01, 0.5), Okhsv::new(10.0, 0.01, 0.6), 1e-12 )); } #[test] fn grey_vs_grey() { // greys of different lightness are not equal assert!(!Okhsv::visually_eq( Okhsv::new(0.0, 0.0, 0.3), Okhsv::new(0.0, 0.0, 0.4), 1e-12 )); // greys of same lightness but different hue are equal assert!(Okhsv::visually_eq( Okhsv::new(0.0, 0.0, 0.3), Okhsv::new(12.0, 0.0, 0.3), 1e-12 )); } } #[test] fn srgb_gamut_containment() { { println!("sRGB Red"); let oklab = Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 0.0)); println!("{:?}", oklab); let okhsv: Okhsv = Okhsv::from_color_unclamped(oklab); println!("{:?}", okhsv); assert!(okhsv.is_within_bounds()); } { println!("Double sRGB Red"); let oklab = Oklab::from_color_unclamped(LinSrgb::new(2.0, 0.0, 0.0)); println!("{:?}", oklab); let okhsv: Okhsv = Okhsv::from_color_unclamped(oklab); println!("{:?}", okhsv); assert!(!okhsv.is_within_bounds()); let clamped_okhsv = okhsv.clamp(); println!("Clamped: {:?}", clamped_okhsv); assert!(clamped_okhsv.is_within_bounds()); let linsrgb = LinSrgb::from_color_unclamped(clamped_okhsv); println!("Clamped as unclamped Linear sRGB: {:?}", linsrgb); } { println!("P3 Yellow"); // display P3 yellow according to https://colorjs.io/apps/convert/?color=color(display-p3%201%201%200)&precision=17 let oklab = Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, -0.098273600140966)); println!("{:?}", oklab); let okhsv: Okhsv = Okhsv::from_color_unclamped(oklab); println!("{:?}", okhsv); assert!(!okhsv.is_within_bounds()); let clamped_okhsv = okhsv.clamp(); println!("Clamped: {:?}", clamped_okhsv); assert!(clamped_okhsv.is_within_bounds()); let linsrgb = LinSrgb::from_color_unclamped(clamped_okhsv); println!( "Clamped as unclamped Linear sRGB: {:?}\n\ May be different, but should be visually indistinguishable from\n\ color.js' gamut mapping red: 1 green: 0.9876530763223166 blue: 0", linsrgb ); } } struct_of_arrays_tests!( Okhsv[hue, saturation, value], super::Okhsva::new(0.1f32, 0.2, 0.3, 0.4), super::Okhsva::new(0.2, 0.3, 0.4, 0.5), super::Okhsva::new(0.3, 0.4, 0.5, 0.6) ); } palette-0.7.5/src/okhwb/alpha.rs000064400000000000000000000040311046102023000146250ustar 00000000000000use crate::angle::FromAngle; use crate::okhwb::Okhwb; use crate::stimulus::FromStimulus; use crate::{Alpha, OklabHue}; /// Okhwb with an alpha component. See the [`Okhwba` implementation in /// `Alpha`](crate::Alpha#Okhwba). pub type Okhwba = Alpha, T>; ///[`Okhwba`](crate::Okhwba) implementations. impl Alpha, A> { /// Create an `Okhwb` color with transparency. pub fn new>>(hue: H, whiteness: T, blackness: T, alpha: A) -> Self { Alpha { color: Okhwb::new(hue.into(), whiteness, blackness), alpha, } } /// Create an `Okhwba` color. This is the same as `Okhwba::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: OklabHue, whiteness: T, blackness: T, alpha: A) -> Self { Alpha { color: Okhwb::new_const(hue, whiteness, blackness), alpha, } } /// Convert into another component type. pub fn into_format(self) -> Alpha, B> where U: FromStimulus + FromAngle, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert from another component type. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus + FromAngle, A: FromStimulus, { color.into_format() } /// Convert to a `(hue, whiteness, blackness, alpha)` tuple. pub fn into_components(self) -> (OklabHue, T, T, A) { ( self.color.hue, self.color.whiteness, self.color.blackness, self.alpha, ) } /// Convert from a `(hue, whiteness, blackness, alpha)` tuple. pub fn from_components>>( (hue, whiteness, blackness, alpha): (H, T, T, A), ) -> Self { Self::new(hue, whiteness, blackness, alpha) } } palette-0.7.5/src/okhwb/properties.rs000064400000000000000000000023101046102023000157320ustar 00000000000000use crate::hues::OklabHueIter; use crate::num::{Arithmetics, PartialCmp, Real}; use crate::stimulus::Stimulus; use crate::white_point::D65; use crate::{ bool_mask::{LazySelect, Select}, FromColor, OklabHue, Xyz, }; use super::Okhwb; impl_is_within_bounds_hwb!(Okhwb where T: Stimulus); impl_clamp_hwb!(Okhwb where T: Stimulus); impl_mix_hue!(Okhwb { whiteness, blackness }); impl_lighten_hwb!(Okhwb where T: Stimulus); impl_hue_ops!(Okhwb, OklabHue); impl_color_add!(Okhwb, [hue, whiteness, blackness]); impl_color_sub!(Okhwb, [hue, whiteness, blackness]); impl_array_casts!(Okhwb, [T; 3]); impl_simd_array_conversion_hue!(Okhwb, [whiteness, blackness]); impl_struct_of_array_traits_hue!(Okhwb, OklabHueIter, [whiteness, blackness]); #[allow(deprecated)] impl crate::RelativeContrast for Okhwb where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl_eq_hue!(Okhwb, OklabHue, [hue, whiteness, blackness]); palette-0.7.5/src/okhwb/random.rs000064400000000000000000000003201046102023000150150ustar 00000000000000use crate::{ okhsv::{Okhsv, UniformOkhsv}, Okhwb, }; impl_rand_traits_hwb_cone!( UniformOkhwb, Okhwb, UniformOkhsv, Okhsv { height: value, radius: saturation } ); palette-0.7.5/src/okhwb/visual_eq.rs000064400000000000000000000047261046102023000155430ustar 00000000000000use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; use crate::num::{Arithmetics, One, Zero}; use crate::visual::{VisualColor, VisuallyEqual}; use crate::{HasBoolMask, Okhwb, OklabHue}; use approx::AbsDiffEq; use std::borrow::Borrow; impl VisualColor for Okhwb where T: PartialOrd + HasBoolMask + AbsDiffEq + One + Zero + Arithmetics, T::Epsilon: Clone, OklabHue: AbsDiffEq, { /// Returns `true`, if `self.blackness + self.whiteness >= 1`, /// assuming (but not asserting) that neither /// `blackness` nor `whiteness` can be negative. fn is_grey(&self, epsilon: T::Epsilon) -> bool { let wb_sum = self.blackness.clone() + self.whiteness.clone(); wb_sum > T::one() || wb_sum.abs_diff_eq(&T::one(), epsilon) } /// Returns `true`, if `Self::is_grey && blackness == 0`, /// i.e. the color's hue is irrelevant **and** the color contains /// no black component it must be white. fn is_white(&self, epsilon: T::Epsilon) -> bool { self.is_grey(epsilon.clone()) && self.blackness < epsilon } /// Returns `true` if `Self::is_grey && whiteness == 0` fn is_black(&self, epsilon: T::Epsilon) -> bool { self.is_grey(epsilon.clone()) && self.whiteness < epsilon } } impl VisuallyEqual for Okhwb where T: PartialOrd + HasBoolMask + RealAngle + SignedAngle + Zero + One + AngleEq + Arithmetics + AbsDiffEq + Clone, T::Epsilon: Clone + HalfRotation, S: Borrow + Copy, O: Borrow + Copy, { fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) || VisuallyEqual::both_greyscale(s, o, epsilon.clone()) && s.borrow() .whiteness .abs_diff_eq(&o.borrow().whiteness, epsilon.clone()) && s.borrow() .blackness .abs_diff_eq(&o.borrow().blackness, epsilon.clone()) || s.borrow().hue.abs_diff_eq(&o.borrow().hue, epsilon.clone()) && s.borrow() .blackness .abs_diff_eq(&o.borrow().blackness, epsilon.clone()) && s.borrow() .whiteness .abs_diff_eq(&o.borrow().whiteness, epsilon) } } palette-0.7.5/src/okhwb.rs000064400000000000000000000211571046102023000135500ustar 00000000000000//! Types for the Okhwb color space. use core::fmt::Debug; pub use alpha::Okhwba; use crate::{ angle::FromAngle, convert::FromColorUnclamped, num::{Arithmetics, One}, stimulus::{FromStimulus, Stimulus}, white_point::D65, HasBoolMask, Okhsv, OklabHue, }; pub use self::properties::Iter; #[cfg(feature = "random")] pub use self::random::UniformOkhwb; mod alpha; mod properties; #[cfg(feature = "random")] mod random; #[cfg(test)] #[cfg(feature = "approx")] mod visual_eq; /// A Hue/Whiteness/Blackness representation of [`Oklab`][crate::Oklab] in the /// `sRGB` color space, similar to [`Hwb`][crate::Okhwb]. #[derive(Debug, Copy, Clone, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "D65", component = "T", skip_derives(Okhwb, Okhsv) )] #[repr(C)] pub struct Okhwb { /// The hue of the color, in degrees of a circle. /// /// For fully saturated, bright colors /// * 0° corresponds to a kind of magenta-pink (RBG #ff0188), /// * 90° to a kind of yellow (RBG RGB #ffcb00) /// * 180° to a kind of cyan (RBG #00ffe1) and /// * 240° to a kind of blue (RBG #00aefe). /// /// For s == 0 or v == 0, the hue is irrelevant. #[palette(unsafe_same_layout_as = "T")] pub hue: OklabHue, /// The amount of white, mixed in the pure hue, ranging from `0.0` to `1.0`. /// `0.0` produces pure, possibly black color. `1.0` a white or grey. pub whiteness: T, /// The amount of black, mixed in the pure hue, ranging from `0.0` to `1.0`. /// `0.0` produces a pure bright or whitened color. `1.0` a black or grey. pub blackness: T, } impl Okhwb { /// Create an `Okhwb` color. pub fn new>>(hue: H, whiteness: T, blackness: T) -> Self { let hue = hue.into(); Self { hue, whiteness, blackness, } } /// Create an `Okhwb` color. This is the same as `Okhwb::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(hue: OklabHue, whiteness: T, blackness: T) -> Self { Self { hue, whiteness, blackness, } } /// Convert into another component type. pub fn into_format(self) -> Okhwb where U: FromStimulus + FromAngle, { Okhwb { hue: self.hue.into_format(), whiteness: U::from_stimulus(self.whiteness), blackness: U::from_stimulus(self.blackness), } } /// Convert to a `(h, w, b)` tuple. pub fn into_components(self) -> (OklabHue, T, T) { (self.hue, self.whiteness, self.blackness) } /// Convert from a `(h, w, b)` tuple. pub fn from_components>>((hue, whiteness, blackness): (H, T, T)) -> Self { Self::new(hue, whiteness, blackness) } } impl Okhwb where T: Stimulus, { /// Return the `whiteness` value minimum. pub fn min_whiteness() -> T { T::zero() } /// Return the `whiteness` value maximum. pub fn max_whiteness() -> T { T::max_intensity() } /// Return the `blackness` value minimum. pub fn min_blackness() -> T { T::zero() } /// Return the `blackness` value maximum. pub fn max_blackness() -> T { T::max_intensity() } } impl_reference_component_methods_hue!(Okhwb, [whiteness, blackness]); impl_struct_of_arrays_methods_hue!(Okhwb, [whiteness, blackness]); impl FromColorUnclamped> for Okhwb where T: One + Arithmetics, { /// Converts `lab` to `Okhwb` in the bounds of sRGB. fn from_color_unclamped(hsv: Okhsv) -> Self { // See . Self { hue: hsv.hue, whiteness: (T::one() - hsv.saturation) * &hsv.value, blackness: T::one() - hsv.value, } } } impl HasBoolMask for Okhwb where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Okhwb where T: Stimulus, OklabHue: Default, { fn default() -> Okhwb { Okhwb::new( OklabHue::default(), Self::min_whiteness(), Self::max_blackness(), ) } } #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Okhwb where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Okhwb where T: bytemuck::Pod {} #[cfg(test)] mod tests { use crate::Okhwb; test_convert_into_from_xyz!(Okhwb); #[cfg(feature = "approx")] mod conversion { use crate::{ convert::FromColorUnclamped, encoding, rgb::Rgb, visual::VisuallyEqual, LinSrgb, Okhsv, Okhwb, Oklab, }; #[cfg_attr(miri, ignore)] #[test] fn test_roundtrip_okhwb_oklab_is_original() { let colors = [ ( "red", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 0.0)), ), ( "green", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 0.0)), ), ( "cyan", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 1.0)), ), ( "magenta", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 1.0)), ), ( "white", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 1.0)), ), ( "black", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 0.0)), ), ( "grey", Oklab::from_color_unclamped(LinSrgb::new(0.5, 0.5, 0.5)), ), ( "yellow", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 0.0)), ), ( "blue", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 1.0)), ), ]; const EPSILON: f64 = 1e-14; for (name, color) in colors { let rgb: Rgb = crate::Srgb::::from_color_unclamped(color).into_format(); println!( "\n\ roundtrip of {} (#{:x} / {:?})\n\ =================================================", name, rgb, color ); let okhsv = Okhsv::from_color_unclamped(color); println!("Okhsv: {:?}", okhsv); let okhwb_from_okhsv = Okhwb::from_color_unclamped(okhsv); let okhwb = Okhwb::from_color_unclamped(color); println!("Okhwb: {:?}", okhwb); assert!( Okhwb::visually_eq(okhwb, okhwb_from_okhsv, EPSILON), "Okhwb \n{:?} is not visually equal to Okhwb from Okhsv \n{:?}\nwithin EPSILON {}", okhwb, okhwb_from_okhsv, EPSILON ); let okhsv_from_okhwb = Okhsv::from_color_unclamped(okhwb); assert!( Okhsv::visually_eq(okhsv, okhsv_from_okhwb, EPSILON), "Okhsv \n{:?} is not visually equal to Okhsv from Okhsv from Okhwb \n{:?}\nwithin EPSILON {}", okhsv, okhsv_from_okhwb, EPSILON ); let roundtrip_color = Oklab::from_color_unclamped(okhwb); let oklab_from_okhsv = Oklab::from_color_unclamped(okhsv); assert!( Oklab::visually_eq(roundtrip_color, oklab_from_okhsv, EPSILON), "roundtrip color \n{:?} does not match \n{:?}\nwithin EPSILON {}", roundtrip_color, oklab_from_okhsv, EPSILON ); assert!( Oklab::visually_eq(roundtrip_color, color, EPSILON), "'{}' failed.\n\ {:?}\n\ !=\n\ \n{:?}\n", name, roundtrip_color, color ); } } } struct_of_arrays_tests!( Okhwb[hue, whiteness, blackness], super::Okhwba::new(0.1f32, 0.2, 0.3, 0.4), super::Okhwba::new(0.2, 0.3, 0.4, 0.5), super::Okhwba::new(0.3, 0.4, 0.5, 0.6) ); } palette-0.7.5/src/oklab/alpha.rs000064400000000000000000000013751046102023000146130ustar 00000000000000use crate::alpha::Alpha; use crate::oklab::Oklab; /// Oklab with an alpha component. pub type Oklaba = Alpha, T>; ///[`Oklaba`](crate::Oklaba) implementations. impl Alpha, A> { /// Create an Oklab color with transparency. pub const fn new(l: T, a: T, b: T, alpha: A) -> Self { Alpha { color: Oklab::new(l, a, b), alpha, } } /// Convert to a `(L, a, b, alpha)` tuple. pub fn into_components(self) -> (T, T, T, A) { (self.color.l, self.color.a, self.color.b, self.alpha) } /// Convert from a `(L, a, b, alpha)` tuple. pub fn from_components((l, a, b, alpha): (T, T, T, A)) -> Self { Self::new(l, a, b, alpha) } } palette-0.7.5/src/oklab/properties.rs000064400000000000000000000032541046102023000157200ustar 00000000000000use core::ops::{Add, Neg}; use crate::{ angle::RealAngle, bool_mask::LazySelect, num::{Arithmetics, One, PartialCmp, Real, Trigonometry, Zero}, white_point::D65, FromColor, GetHue, OklabHue, Xyz, }; use super::Oklab; impl_is_within_bounds! { Oklab { l => [Self::min_l(), Self::max_l()] } where T: Zero + One } impl_clamp! { Oklab { l => [Self::min_l(), Self::max_l()] } other {a, b} where T: Zero + One } impl_mix!(Oklab); impl_lighten!(Oklab increase {l => [Self::min_l(), Self::max_l()]} other {a, b} where T: One); impl_premultiply!(Oklab { l, a, b }); impl_euclidean_distance!(Oklab { l, a, b }); impl_hyab!(Oklab { lightness: l, chroma1: a, chroma2: b }); impl GetHue for Oklab where T: RealAngle + Trigonometry + Add + Neg + Clone, { type Hue = OklabHue; fn get_hue(&self) -> OklabHue { OklabHue::from_cartesian(self.a.clone(), self.b.clone()) } } impl_color_add!(Oklab, [l, a, b]); impl_color_sub!(Oklab, [l, a, b]); impl_color_mul!(Oklab, [l, a, b]); impl_color_div!(Oklab, [l, a, b]); impl_array_casts!(Oklab, [T; 3]); impl_simd_array_conversion!(Oklab, [l, a, b]); impl_struct_of_array_traits!(Oklab, [l, a, b]); impl_eq!(Oklab, [l, a, b]); #[allow(deprecated)] impl crate::RelativeContrast for Oklab where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } palette-0.7.5/src/oklab/random.rs000064400000000000000000000005111046102023000147750ustar 00000000000000use core::ops::{Add, Mul, Sub}; use crate::{num::One, Oklab}; impl_rand_traits_cartesian!( UniformOklab, Oklab { l, a => [|x| x * (T::one() + T::one()) - T::one()], b => [|x| x * (T::one() + T::one()) - T::one()] } where T: Mul + Add + Sub + One ); palette-0.7.5/src/oklab/visual_eq.rs000064400000000000000000000040441046102023000155120ustar 00000000000000use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; use crate::num::{One, Zero}; use crate::visual::{VisualColor, VisuallyEqual}; use crate::{HasBoolMask, Oklab, OklabHue}; use approx::AbsDiffEq; use std::borrow::Borrow; use std::ops::{Mul, Neg, Sub}; impl VisualColor for Oklab where T: PartialOrd + HasBoolMask + AbsDiffEq + One + Zero + Neg, T::Epsilon: Clone, OklabHue: AbsDiffEq, { /// Returns true, if `chroma == 0` #[allow(dead_code)] fn is_grey(&self, epsilon: T::Epsilon) -> bool { self.a.abs_diff_eq(&T::zero(), epsilon.clone()) && self.b.abs_diff_eq(&T::zero(), epsilon) } /// Returns true, if `lightness >= 1` /// /// **Note:** `sRGB` to `Oklab` conversion uses `f32` constants. /// A tolerance `epsilon >= 1e-8` is required to reliably detect white. /// Conversion of `sRGB` via XYZ requires `epsilon >= 1e-5` fn is_white(&self, epsilon: T::Epsilon) -> bool { self.l >= T::one() || self.l.abs_diff_eq(&T::one(), epsilon) } /// Returns true, if `lightness == 0` fn is_black(&self, epsilon: T::Epsilon) -> bool { self.l.abs_diff_eq(&T::zero(), epsilon) } } impl VisuallyEqual for Oklab where T: PartialOrd + HasBoolMask + RealAngle + SignedAngle + Zero + One + AngleEq + Sub + AbsDiffEq + Neg + Clone, T::Epsilon: Clone + HalfRotation + Mul, S: Borrow + Copy, O: Borrow + Copy, { fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) || s.borrow().l.abs_diff_eq(&o.borrow().l, epsilon.clone()) && s.borrow().a.abs_diff_eq(&o.borrow().a, epsilon.clone()) && s.borrow().b.abs_diff_eq(&o.borrow().b, epsilon) } } palette-0.7.5/src/oklab.rs000064400000000000000000000574761046102023000135430ustar 00000000000000//! Types for the Oklab color space. use core::{any::TypeId, fmt::Debug, ops::Mul}; pub use alpha::Oklaba; use crate::{ angle::RealAngle, bool_mask::HasBoolMask, convert::{FromColorUnclamped, IntoColorUnclamped}, encoding::{IntoLinear, Srgb}, matrix::multiply_xyz, num::{Arithmetics, Cbrt, Hypot, MinMax, One, Powi, Real, Sqrt, Trigonometry, Zero}, ok_utils::{toe_inv, ChromaValues, LC, ST}, rgb::{Rgb, RgbSpace, RgbStandard}, white_point::D65, LinSrgb, Mat3, Okhsl, Okhsv, Oklch, Xyz, }; pub use self::properties::Iter; #[cfg(feature = "random")] pub use self::random::UniformOklab; mod alpha; mod properties; #[cfg(feature = "random")] mod random; #[cfg(test)] #[cfg(feature = "approx")] mod visual_eq; // Using recalculated matrix values from // https://github.com/LeaVerou/color.js/blob/master/src/spaces/oklab.js // // see https://github.com/w3c/csswg-drafts/issues/6642#issuecomment-943521484 // and the following https://github.com/w3c/csswg-drafts/issues/6642#issuecomment-945714988 /// XYZ to LSM transformation matrix #[rustfmt::skip] fn m1() -> Mat3 { [ T::from_f64(0.8190224432164319), T::from_f64(0.3619062562801221), T::from_f64(-0.12887378261216414), T::from_f64(0.0329836671980271), T::from_f64(0.9292868468965546), T::from_f64(0.03614466816999844), T::from_f64(0.048177199566046255), T::from_f64(0.26423952494422764), T::from_f64(0.6335478258136937), ] } /// LMS to XYZ transformation matrix #[rustfmt::skip] pub(crate) fn m1_inv() -> Mat3 { [ T::from_f64(1.2268798733741557), T::from_f64(-0.5578149965554813), T::from_f64(0.28139105017721583), T::from_f64(-0.04057576262431372), T::from_f64(1.1122868293970594), T::from_f64(-0.07171106666151701), T::from_f64(-0.07637294974672142), T::from_f64(-0.4214933239627914), T::from_f64(1.5869240244272418), ] } /// LMS to Oklab transformation matrix #[rustfmt::skip] fn m2() -> Mat3 { [ T::from_f64(0.2104542553), T::from_f64(0.7936177850), T::from_f64(-0.0040720468), T::from_f64(1.9779984951), T::from_f64(-2.4285922050), T::from_f64(0.4505937099), T::from_f64(0.0259040371), T::from_f64(0.7827717662), T::from_f64(-0.8086757660), ] } /// Oklab to LMS transformation matrix #[rustfmt::skip] #[allow(clippy::excessive_precision)] pub(crate) fn m2_inv() -> Mat3 { [ T::from_f64(0.99999999845051981432), T::from_f64(0.39633779217376785678), T::from_f64(0.21580375806075880339), T::from_f64(1.0000000088817607767), T::from_f64(-0.1055613423236563494), T::from_f64(-0.063854174771705903402), T::from_f64(1.0000000546724109177), T::from_f64(-0.089484182094965759684), T::from_f64(-1.2914855378640917399), ] } /// The [Oklab color space](https://bottosson.github.io/posts/oklab/). /// /// # Characteristics /// `Oklab` is a *perceptual* color space. It does not relate to an output /// device (a monitor or printer) but instead relates to the [CIE standard /// observer](https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_standard_observer) /// -- an averaging of the results of color matching experiments under /// laboratory conditions. /// /// `Oklab` is a uniform color space ([Compare to the HSV color /// space](https://bottosson.github.io/posts/oklab/#comparing-oklab-to-hsv)). It /// is useful for things like: /// * Turning an image grayscale, while keeping the perceived lightness the same /// * Increasing the saturation of colors, while maintaining perceived hue and /// lightness /// * Creating smooth and uniform looking transitions between colors /// /// `Oklab`'s structure is similar to [L\*a\*b\*](crate::Lab). It is based on /// the [opponent color model of human /// vision](https://en.wikipedia.org/wiki/Opponent_process), where red and green /// form an opponent pair, and blue and yellow form an opponent pair. /// /// `Oklab` uses [D65](https://en.wikipedia.org/wiki/Illuminant_D65)'s /// whitepoint -- daylight illumination, which is also used by sRGB, rec2020 and /// Display P3 color spaces -- and assumes normal well-lit viewing conditions, /// to which the eye is adapted. Thus `Oklab`s lightness `l` technically is a /// measure of relative brightness -- a subjective measure -- not relative /// luminance. The lightness is scale/exposure-independend, i.e. independent of /// the actual luminance of the color, as displayed by some medium, and even for /// blindingly bright colors or very bright or dark viewing conditions assumes, /// that the eye is adapted to the color's luminance and the hue and chroma are /// perceived linearly. /// /// /// `Oklab`'s chroma is unlimited. Thus it can represent colors of any color /// space (including HDR). `l` is in the range `0.0 .. 1.0` and `a` and `b` are /// unbounded. /// /// # Conversions /// [`Oklch`] is a cylindrical form of `Oklab`. /// /// `Oklab` colors converted from valid (i.e. clamped) `sRGB` will be in the /// `sRGB` gamut. /// /// [`Okhsv`], [`Okhwb`][crate::Okhsv] and [`Okhsl`] reference the `sRGB` gamut. /// The transformation from `Oklab` to one of them is based on the assumption, /// that the transformed `Oklab` value is within `sRGB`. /// /// `Okhsv`, `Okhwb` and `Okhsl` are not applicable to HDR, which also come with /// color spaces with wider gamuts. They require [additional /// research](https://bottosson.github.io/posts/colorpicker/#ideas-for-future-work). /// /// When a `Oklab` color is converted from [`Srgb`](crate::rgb::Srgb) or a /// equivalent color space, e.g. [`Hsv`][crate::Hsv], [`Okhsv`], /// [`Hsl`][crate::Hsl], [`Okhsl`], [`Hwb`][crate::Hwb], /// [`Okhwb`][crate::Okhwb], it's lightness will be relative to the (user /// controlled) maximum contrast and luminance of the display device, to which /// the eye is assumed to be adapted. /// /// # Clamping /// [`Clamp`][crate::Clamp]ing will only clamp `l`. Clamping does not guarantee /// the color to be inside the perceptible or any display-dependent color space /// (like *sRGB*). /// /// To ensure a color is within the *sRGB* gamut, it can first be converted to /// `Okhsv`, clamped there and converted it back to `Oklab`. /// /// ``` /// # use approx::assert_abs_diff_eq; /// # use palette::{convert::FromColorUnclamped,IsWithinBounds, LinSrgb, Okhsv, Oklab}; /// # use palette::Clamp; /// // Display P3 yellow according to https://colorjs.io/apps/convert/?color=color(display-p3%201%201%200)&precision=17 /// let oklab = Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, -0.098273600140966)); /// let okhsv: Okhsv = Okhsv::from_color_unclamped(oklab); /// assert!(!okhsv.is_within_bounds()); /// let clamped_okhsv = okhsv.clamp(); /// assert!(clamped_okhsv.is_within_bounds()); /// let linsrgb = LinSrgb::from_color_unclamped(clamped_okhsv); /// let expected = LinSrgb::new(1.0, 0.9876530763223166, 0.0); /// assert_abs_diff_eq!(expected, linsrgb, epsilon = 0.02); /// ``` /// Since the conversion contains a gamut mapping, it will map the color to one /// of the perceptually closest locations in the `sRGB` gamut. Gamut mapping -- /// unlike clamping -- is an expensive operation. To get computationally cheaper /// (and perceptually much worse) results, convert directly to [`Srgb`] and /// clamp there. /// /// # Lightening / Darkening /// [`Lighten`](crate::Lighten)ing and [`Darken`](crate::Darken)ing will change /// `l`, as expected. However, either operation may leave an implicit color /// space (the percetible or a display dependent color space like *sRGB*). /// /// To ensure a color is within the *sRGB* gamut, first convert it to `Okhsl`, /// lighten/darken it there and convert it back to `Oklab`. #[derive(Debug, Copy, Clone, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "D65", component = "T", skip_derives(Oklab, Oklch, Okhsv, Okhsl, Xyz, Rgb) )] #[repr(C)] pub struct Oklab { /// `l` is the lightness of the color. `0` gives absolute black and `1` gives the /// full white point luminance of the display medium. /// /// [`D65` (normalized with Y=1, i.e. white according to the adaption of the /// eye) transforms to /// L=1,a=0,b=0](https://bottosson.github.io/posts/oklab/#how-oklab-was-derived). /// However intermediate values differ from those of CIELab non-linearly. pub l: T, /// `a` changes the hue from reddish to greenish, when moving from positive /// to negative values and becomes more intense with larger absolute values. /// /// The exact orientation is determined by `b` pub a: T, /// `b` changes the hue from yellowish to blueish, when moving from positive /// to negative values and becomes more intense with larger absolute values. /// /// [Positive b is oriented to the same yellow color as /// CAM16](https://bottosson.github.io/posts/oklab/#how-oklab-was-derived) pub b: T, } impl Oklab { /// Create an Oklab color. pub const fn new(l: T, a: T, b: T) -> Self { Self { l, a, b } } /// Convert to a `(L, a, b)` tuple. pub fn into_components(self) -> (T, T, T) { (self.l, self.a, self.b) } /// Convert from a `(L, a, b)` tuple. pub fn from_components((l, a, b): (T, T, T)) -> Self { Self::new(l, a, b) } } // component bounds // For `Oklab` in general a and b are unbounded. // In the sRGB gamut `Oklab`s chroma (and thus a and b) are bounded. impl Oklab where T: Zero + One, { /// Return the `l` value minimum. pub fn min_l() -> T { T::zero() } /// Return the `l` value maximum. pub fn max_l() -> T { T::one() } } impl_reference_component_methods!(Oklab, [l, a, b]); impl_struct_of_arrays_methods!(Oklab, [l, a, b]); impl Oklab where T: Hypot + Clone, { /// Returns the chroma. pub(crate) fn get_chroma(&self) -> T { T::hypot(self.a.clone(), self.b.clone()) } } impl FromColorUnclamped> for Oklab { fn from_color_unclamped(color: Self) -> Self { color } } impl FromColorUnclamped> for Oklab where T: Real + Cbrt + Arithmetics, { fn from_color_unclamped(color: Xyz) -> Self { let m1 = m1(); let m2 = m2(); let Xyz { x: l, y: m, z: s, .. } = multiply_xyz(m1, color.with_white_point()); let l_m_s_ = Xyz::new(l.cbrt(), m.cbrt(), s.cbrt()); let Xyz { x: l, y: a, z: b, .. } = multiply_xyz(m2, l_m_s_); Self::new(l, a, b) } } fn linear_srgb_to_oklab(c: LinSrgb) -> Oklab where T: Real + Arithmetics + Cbrt + Copy, { let l = T::from_f64(0.4122214708) * c.red + T::from_f64(0.5363325363) * c.green + T::from_f64(0.0514459929) * c.blue; let m = T::from_f64(0.2119034982) * c.red + T::from_f64(0.6806995451) * c.green + T::from_f64(0.1073969566) * c.blue; let s = T::from_f64(0.0883024619) * c.red + T::from_f64(0.2817188376) * c.green + T::from_f64(0.6299787005) * c.blue; let l_ = l.cbrt(); let m_ = m.cbrt(); let s_ = s.cbrt(); Oklab::new( T::from_f64(0.2104542553) * l_ + T::from_f64(0.7936177850) * m_ - T::from_f64(0.0040720468) * s_, T::from_f64(1.9779984951) * l_ - T::from_f64(2.4285922050) * m_ + T::from_f64(0.4505937099) * s_, T::from_f64(0.0259040371) * l_ + T::from_f64(0.7827717662) * m_ - T::from_f64(0.8086757660) * s_, ) } pub(crate) fn oklab_to_linear_srgb(c: Oklab) -> LinSrgb where T: Real + Arithmetics + Copy, { let l_ = c.l + T::from_f64(0.3963377774) * c.a + T::from_f64(0.2158037573) * c.b; let m_ = c.l - T::from_f64(0.1055613458) * c.a - T::from_f64(0.0638541728) * c.b; let s_ = c.l - T::from_f64(0.0894841775) * c.a - T::from_f64(1.2914855480) * c.b; let l = l_ * l_ * l_; let m = m_ * m_ * m_; let s = s_ * s_ * s_; LinSrgb::new( T::from_f64(4.0767416621) * l - T::from_f64(3.3077115913) * m + T::from_f64(0.2309699292) * s, T::from_f64(-1.2684380046) * l + T::from_f64(2.6097574011) * m - T::from_f64(0.3413193965) * s, T::from_f64(-0.0041960863) * l - T::from_f64(0.7034186147) * m + T::from_f64(1.7076147010) * s, ) } impl FromColorUnclamped> for Oklab where T: Real + Cbrt + Arithmetics + Copy, S: RgbStandard, S::TransferFn: IntoLinear, S::Space: RgbSpace + 'static, Xyz: FromColorUnclamped>, { fn from_color_unclamped(rgb: Rgb) -> Self { if TypeId::of::<::Space>() == TypeId::of::() { // Use direct sRGB to Oklab conversion // Rounding errors are likely a contributing factor to differences. // Also the conversion via XYZ doesn't use pre-defined matrices (yet) linear_srgb_to_oklab(rgb.into_linear().reinterpret_as()) } else { // Convert via XYZ Xyz::from_color_unclamped(rgb).into_color_unclamped() } } } impl FromColorUnclamped> for Oklab where T: RealAngle + Zero + MinMax + Trigonometry + Mul + Clone, { fn from_color_unclamped(color: Oklch) -> Self { let (a, b) = color.hue.into_cartesian(); let chroma = color.chroma.max(T::zero()); Oklab { l: color.l, a: a * chroma.clone(), b: b * chroma, } } } /// # See /// See [`okhsl_to_srgb`](https://bottosson.github.io/posts/colorpicker/#hsl-2) impl FromColorUnclamped> for Oklab where T: RealAngle + One + Zero + Arithmetics + Sqrt + MinMax + PartialOrd + HasBoolMask + Powi + Cbrt + Trigonometry + Clone, Oklab: IntoColorUnclamped>, { fn from_color_unclamped(hsl: Okhsl) -> Self { let h = hsl.hue; let s = hsl.saturation; let l = hsl.lightness; if l == T::one() { return Oklab::new(T::one(), T::zero(), T::zero()); } else if l == T::zero() { return Oklab::new(T::zero(), T::zero(), T::zero()); } let (a_, b_) = h.into_cartesian(); let oklab_lightness = toe_inv(l); let cs = ChromaValues::from_normalized(oklab_lightness.clone(), a_.clone(), b_.clone()); // Interpolate the three values for C so that: // At s=0: dC/ds = cs.zero, C = 0 // At s=0.8: C = cs.mid // At s=1.0: C = cs.max let mid = T::from_f64(0.8); let mid_inv = T::from_f64(1.25); let chroma = if s < mid { let t = mid_inv * s; let k_1 = mid * cs.zero; let k_2 = T::one() - k_1.clone() / cs.mid; t.clone() * k_1 / (T::one() - k_2 * t) } else { let t = (s - &mid) / (T::one() - &mid); let k_0 = cs.mid.clone(); let k_1 = (T::one() - mid) * &cs.mid * &cs.mid * &mid_inv * mid_inv / cs.zero; let k_2 = T::one() - k_1.clone() / (cs.max - cs.mid); k_0 + t.clone() * k_1 / (T::one() - k_2 * t) }; Oklab::new(oklab_lightness, chroma.clone() * a_, chroma * b_) } } impl FromColorUnclamped> for Oklab where T: RealAngle + PartialOrd + HasBoolMask + MinMax + Powi + Arithmetics + Clone + One + Zero + Cbrt + Trigonometry, Oklab: IntoColorUnclamped>, { fn from_color_unclamped(hsv: Okhsv) -> Self { if hsv.value == T::zero() { // pure black return Self { l: T::zero(), a: T::zero(), b: T::zero(), }; } if hsv.saturation == T::zero() { // totally desaturated color -- the triangle is just the 0-chroma-line let l = toe_inv(hsv.value); return Self { l, a: T::zero(), b: T::zero(), }; } let h_radians = hsv.hue.into_raw_radians(); let a_ = T::cos(h_radians.clone()); let b_ = T::sin(h_radians); let cusp = LC::find_cusp(a_.clone(), b_.clone()); let cusp: ST = cusp.into(); let s_0 = T::from_f64(0.5); let k = T::one() - s_0.clone() / cusp.s; // first we compute L and V as if the gamut is a perfect triangle // L, C, when v == 1: let l_v = T::one() - hsv.saturation.clone() * s_0.clone() / (s_0.clone() + &cusp.t - cusp.t.clone() * &k * &hsv.saturation); let c_v = hsv.saturation.clone() * &cusp.t * &s_0 / (s_0 + &cusp.t - cusp.t * k * hsv.saturation); // then we compensate for both toe and the curved top part of the triangle: let l_vt = toe_inv(l_v.clone()); let c_vt = c_v.clone() * &l_vt / &l_v; let mut lightness = hsv.value.clone() * l_v; let mut chroma = hsv.value * c_v; let lightness_new = toe_inv(lightness.clone()); chroma = chroma * &lightness_new / lightness; // the values may be outside the normal range let rgb_scale: LinSrgb = Oklab::new(l_vt, a_.clone() * &c_vt, b_.clone() * c_vt).into_color_unclamped(); let lightness_scale_factor = T::cbrt( T::one() / T::max( T::max(rgb_scale.red, rgb_scale.green), T::max(rgb_scale.blue, T::zero()), ), ); lightness = lightness_new * &lightness_scale_factor; chroma = chroma * lightness_scale_factor; Oklab::new(lightness, chroma.clone() * a_, chroma * b_) } } impl_tuple_conversion!(Oklab as (T, T, T)); impl HasBoolMask for Oklab where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Oklab where T: Zero, { fn default() -> Self { Self::new(T::zero(), T::zero(), T::zero()) } } #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Oklab where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Oklab where T: bytemuck::Pod {} #[cfg(test)] mod test { use crate::Oklab; test_convert_into_from_xyz!(Oklab); #[cfg(feature = "approx")] mod conversion { use core::str::FromStr; use crate::{ convert::FromColorUnclamped, rgb::Rgb, visual::VisuallyEqual, white_point::D65, FromColor, Lab, LinSrgb, Oklab, Srgb, }; /// Asserts that, for any color space, the lightness of pure white is converted to `l == 1.0` #[test] fn lightness_of_white_is_one() { let rgb: Srgb = Rgb::from_str("#ffffff").unwrap().into_format(); let lin_rgb = LinSrgb::from_color_unclamped(rgb); let oklab = Oklab::from_color_unclamped(lin_rgb); println!("white {rgb:?} == {oklab:?}"); assert_abs_diff_eq!(oklab.l, 1.0, epsilon = 1e-7); assert_abs_diff_eq!(oklab.a, 0.0, epsilon = 1e-7); assert_abs_diff_eq!(oklab.b, 0.0, epsilon = 1e-7); let lab: Lab = Lab::from_components((100.0, 0.0, 0.0)); let rgb: Srgb = Srgb::from_color_unclamped(lab); let oklab = Oklab::from_color_unclamped(lab); println!("white {lab:?} == {rgb:?} == {oklab:?}"); assert_abs_diff_eq!(oklab.l, 1.0, epsilon = 1e-4); assert_abs_diff_eq!(oklab.a, 0.0, epsilon = 1e-4); assert_abs_diff_eq!(oklab.b, 0.0, epsilon = 1e-4); } #[test] fn blue_srgb() { // use f64 to be comparable to javascript let rgb: Srgb = Rgb::from_str("#0000ff").unwrap().into_format(); let lin_rgb = LinSrgb::from_color_unclamped(rgb); let oklab = Oklab::from_color_unclamped(lin_rgb); // values from Ok Color Picker, which seems to use Björn Ottosson's original // algorithm (from the direct srgb2oklab conversion, not via the XYZ color space) assert_abs_diff_eq!(oklab.l, 0.4520137183853429, epsilon = 1e-9); assert_abs_diff_eq!(oklab.a, -0.03245698416876397, epsilon = 1e-9); assert_abs_diff_eq!(oklab.b, -0.3115281476783751, epsilon = 1e-9); } #[test] fn red() { let a = Oklab::from_color(LinSrgb::new(1.0, 0.0, 0.0)); // from https://github.com/bottosson/bottosson.github.io/blob/master/misc/ok_color.h let b = Oklab::new(0.6279553606145516, 0.22486306106597395, 0.1258462985307351); assert!(Oklab::visually_eq(a, b, 1e-8)); } #[test] fn green() { let a = Oklab::from_color(LinSrgb::new(0.0, 1.0, 0.0)); // from https://github.com/bottosson/bottosson.github.io/blob/master/misc/ok_color.h let b = Oklab::new( 0.8664396115356694, -0.23388757418790812, 0.17949847989672985, ); assert!(Oklab::visually_eq(a, b, 1e-8)); } #[test] fn blue() { let a = Oklab::from_color(LinSrgb::new(0.0, 0.0, 1.0)); println!("Oklab blue: {:?}", a); // from https://github.com/bottosson/bottosson.github.io/blob/master/misc/ok_color.h let b = Oklab::new(0.4520137183853429, -0.0324569841687640, -0.3115281476783751); assert!(Oklab::visually_eq(a, b, 1e-8)); } } #[cfg(feature = "approx")] mod visually_eq { use crate::{visual::VisuallyEqual, Oklab}; #[test] fn black_eq_different_black() { assert!(Oklab::visually_eq( Oklab::new(0.0, 1.0, 0.0), Oklab::new(0.0, 0.0, 1.0), 1e-8 )); } #[test] fn white_eq_different_white() { assert!(Oklab::visually_eq( Oklab::new(1.0, 1.0, 0.0), Oklab::new(1.0, 0.0, 1.0), 1e-8 )); } #[test] fn white_ne_black() { assert!(!Oklab::visually_eq( Oklab::new(1.0, 1.0, 0.0), Oklab::new(0.0, 0.0, 1.0), 1e-8 )); assert!(!Oklab::visually_eq( Oklab::new(1.0, 1.0, 0.0), Oklab::new(0.0, 1.0, 0.0), 1e-8 )); } #[test] fn non_bw_neq_different_non_bw() { assert!(!Oklab::visually_eq( Oklab::new(0.3, 1.0, 0.0), Oklab::new(0.3, 0.0, 1.0), 1e-8 )); } } #[test] fn ranges() { assert_ranges! { Oklab; clamped { l: 0.0 => 1.0 // a and b are unbounded --> not part of test } clamped_min {} unclamped {} }; } #[test] fn check_min_max_components() { assert_eq!(Oklab::::min_l(), 0.0); assert_eq!(Oklab::::max_l(), 1.0); } struct_of_arrays_tests!( Oklab[l, a, b], super::Oklaba::new(0.1f32, 0.2, 0.3, 0.4), super::Oklaba::new(0.2, 0.3, 0.4, 0.5), super::Oklaba::new(0.3, 0.4, 0.5, 0.6) ); #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Oklab::new(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"l":0.3,"a":0.8,"b":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Oklab = ::serde_json::from_str(r#"{"l":0.3,"a":0.8,"b":0.1}"#).unwrap(); assert_eq!(deserialized, Oklab::new(0.3, 0.8, 0.1)); } test_uniform_distribution! { Oklab { l: (0.0, 1.0), a: (-1.0, 1.0), b: (-1.0, 1.0) }, min: Oklab::new(0.0, -1.0, -1.0), max: Oklab::new(1.0, 1.0, 1.0) } } palette-0.7.5/src/oklch/alpha.rs000064400000000000000000000023541046102023000146210ustar 00000000000000use crate::{Alpha, OklabHue}; use super::Oklch; /// Oklch with an alpha component. See the [`Oklcha` implementation in /// `Alpha`](crate::Alpha#Oklcha). pub type Oklcha = Alpha, T>; ///[`Oklcha`](crate::Oklcha) implementations. impl Alpha, A> { /// Create an Oklch color with transparency. pub fn new>>(l: T, chroma: T, hue: H, alpha: A) -> Self { Alpha { color: Oklch::new(l, chroma, hue), alpha, } } /// Create an `Oklcha` color. This is the same as `Oklcha::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(l: T, chroma: T, hue: OklabHue, alpha: A) -> Self { Alpha { color: Oklch::new_const(l, chroma, hue), alpha, } } /// Convert to a `(L, C, h, alpha)` tuple. pub fn into_components(self) -> (T, T, OklabHue, A) { (self.color.l, self.color.chroma, self.color.hue, self.alpha) } /// Convert from a `(L, C, h, alpha)` tuple. pub fn from_components>>((l, chroma, hue, alpha): (T, T, H, A)) -> Self { Self::new(l, chroma, hue, alpha) } } palette-0.7.5/src/oklch/properties.rs000064400000000000000000000025521046102023000157300ustar 00000000000000use crate::{ bool_mask::LazySelect, hues::OklabHueIter, num::{Arithmetics, One, PartialCmp, Real, Zero}, white_point::D65, FromColor, OklabHue, Xyz, }; use super::Oklch; impl_is_within_bounds! { Oklch { l => [Self::min_l(), Self::max_l()], chroma => [Self::min_chroma(), None] } where T: Zero + One } impl_clamp! { Oklch { l => [Self::min_l(), Self::max_l()], chroma => [Self::min_chroma()] } other {hue} where T: Zero + One } impl_mix_hue!(Oklch { l, chroma }); impl_lighten!(Oklch increase {l => [Self::min_l(), Self::max_l()]} other {hue, chroma} where T: Zero + One); impl_hue_ops!(Oklch, OklabHue); impl_color_add!(Oklch, [l, chroma, hue]); impl_color_sub!(Oklch, [l, chroma, hue]); impl_array_casts!(Oklch, [T; 3]); impl_simd_array_conversion_hue!(Oklch, [l, chroma]); impl_struct_of_array_traits_hue!(Oklch, OklabHueIter, [l, chroma]); impl_eq_hue!(Oklch, OklabHue, [l, chroma, hue]); #[allow(deprecated)] impl crate::RelativeContrast for Oklch where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, Xyz: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } palette-0.7.5/src/oklch/random.rs000064400000000000000000000004011046102023000150030ustar 00000000000000use super::Oklch; use crate::OklabHue; impl_rand_traits_cylinder!( UniformOklch, Oklch { hue: UniformOklabHue => OklabHue, height: l, radius: chroma // FIXME: Same as with Oklab: The limit of chroma has no meaning } ); palette-0.7.5/src/oklch.rs000064400000000000000000000175261046102023000135430ustar 00000000000000//! Types for the Oklch color space. pub use alpha::Oklcha; use crate::{ bool_mask::HasBoolMask, convert::FromColorUnclamped, num::{Hypot, One, Zero}, white_point::D65, GetHue, Oklab, OklabHue, }; pub use self::properties::Iter; #[cfg(feature = "random")] pub use self::random::UniformOklch; mod alpha; mod properties; #[cfg(feature = "random")] mod random; /// Oklch, a polar version of [Oklab](crate::Oklab). /// /// It is Oklab’s equivalent of [CIE L\*C\*h°](crate::Lch). /// /// It's a cylindrical color space, like [HSL](crate::Hsl) and /// [HSV](crate::Hsv). This gives it the same ability to directly change /// the hue and colorfulness of a color, while preserving other visual aspects. /// /// It assumes a D65 whitepoint and normal well-lit viewing conditions, /// like Oklab. #[derive(Debug, Copy, Clone, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, white_point = "D65", component = "T", skip_derives(Oklab, Oklch) )] #[repr(C)] pub struct Oklch { /// L is the lightness of the color. 0 gives absolute black and 1 gives the brightest white. pub l: T, /// `chroma` is the colorfulness of the color. /// A color with `chroma == 0` is a shade of grey. /// In a transformation from `Oklab` it is computed as `chroma = √(a²+b²)`. /// `chroma` is unbounded pub chroma: T, /// h is the hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. #[palette(unsafe_same_layout_as = "T")] pub hue: OklabHue, } impl Oklch { /// Create an `Oklch` color. pub fn new>>(l: T, chroma: T, hue: H) -> Self { Oklch { l, chroma, hue: hue.into(), } } /// Create an `Oklch` color. This is the same as `Oklch::new` without the /// generic hue type. It's temporary until `const fn` supports traits. pub const fn new_const(l: T, chroma: T, hue: OklabHue) -> Self { Oklch { l, chroma, hue } } /// Convert to a `(L, C, h)` tuple. pub fn into_components(self) -> (T, T, OklabHue) { (self.l, self.chroma, self.hue) } /// Convert from a `(L, C, h)` tuple. pub fn from_components>>((l, chroma, hue): (T, T, H)) -> Self { Self::new(l, chroma, hue) } } impl Oklch where T: Zero + One, { /// Return the `l` value minimum. pub fn min_l() -> T { T::zero() } /// Return the `l` value maximum. pub fn max_l() -> T { T::one() } /// Return the `chroma` value minimum. pub fn min_chroma() -> T { T::zero() } } impl_reference_component_methods_hue!(Oklch, [l, chroma]); impl_struct_of_arrays_methods_hue!(Oklch, [l, chroma]); impl FromColorUnclamped> for Oklch { fn from_color_unclamped(color: Oklch) -> Self { color } } impl FromColorUnclamped> for Oklch where T: Hypot + Clone, Oklab: GetHue>, { fn from_color_unclamped(color: Oklab) -> Self { let hue = color.get_hue(); let chroma = color.get_chroma(); Oklch::new(color.l, chroma, hue) } } impl_tuple_conversion_hue!(Oklch as (T, T, H), OklabHue); impl HasBoolMask for Oklch where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Oklch where T: Zero + One, OklabHue: Default, { fn default() -> Oklch { Oklch::new(Self::min_l(), Self::min_chroma(), OklabHue::default()) } } #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Oklch where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Oklch where T: bytemuck::Pod {} #[cfg(test)] mod test { use crate::Oklch; test_convert_into_from_xyz!(Oklch); #[cfg(feature = "approx")] mod conversion { use crate::{ convert::FromColorUnclamped, visual::{VisualColor, VisuallyEqual}, LinSrgb, Oklab, Oklch, Srgb, }; #[cfg_attr(miri, ignore)] #[test] fn test_roundtrip_oklch_oklab_is_original() { let colors = [ ( "red", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 0.0)), ), ( "green", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 0.0)), ), ( "cyan", Oklab::from_color_unclamped(LinSrgb::new(0.0, 1.0, 1.0)), ), ( "magenta", Oklab::from_color_unclamped(LinSrgb::new(1.0, 0.0, 1.0)), ), ( "black", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 0.0)), ), ( "grey", Oklab::from_color_unclamped(LinSrgb::new(0.5, 0.5, 0.5)), ), ( "yellow", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 0.0)), ), ( "blue", Oklab::from_color_unclamped(LinSrgb::new(0.0, 0.0, 1.0)), ), ( "white", Oklab::from_color_unclamped(LinSrgb::new(1.0, 1.0, 1.0)), ), ]; const EPSILON: f64 = 1e-14; for (name, color) in colors { let rgb: Srgb = Srgb::::from_color_unclamped(color).into_format(); println!( "\n\ roundtrip of {} (#{:x} / {:?})\n\ =================================================", name, rgb, color ); println!("Color is white: {}", color.is_white(EPSILON)); let oklch = Oklch::from_color_unclamped(color); println!("Oklch: {:?}", oklch); let roundtrip_color = Oklab::from_color_unclamped(oklch); assert!( Oklab::visually_eq(roundtrip_color, color, EPSILON), "'{}' failed.\n{:?}\n!=\n{:?}", name, roundtrip_color, color ); } } } #[test] fn ranges() { // chroma: 0.0 => infinity assert_ranges! { Oklch< f64>; clamped { l: 0.0 => 1.0 } clamped_min {} unclamped { hue: 0.0 => 360.0 } } } #[test] fn check_min_max_components() { assert_eq!(Oklch::::min_l(), 0.0); assert_eq!(Oklch::::max_l(), 1.0); assert_eq!(Oklch::::min_chroma(), 0.0); } #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Oklch::new(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"l":0.3,"chroma":0.8,"hue":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Oklch = ::serde_json::from_str(r#"{"l":0.3,"chroma":0.8,"hue":0.1}"#).unwrap(); assert_eq!(deserialized, Oklch::new(0.3, 0.8, 0.1)); } struct_of_arrays_tests!( Oklch[l, chroma, hue], super::Oklcha::new(0.1f32, 0.2, 0.3, 0.4), super::Oklcha::new(0.2, 0.3, 0.4, 0.5), super::Oklcha::new(0.3, 0.4, 0.5, 0.6) ); test_uniform_distribution! { Oklch as crate::Oklab { l: (0.0, 1.0), a: (-0.7, 0.7), b: (-0.7, 0.7), }, min: Oklch::new(0.0f32, 0.0, 0.0), max: Oklch::new(1.0, 1.0, 360.0) } } palette-0.7.5/src/random_sampling/cone.rs000064400000000000000000000123631046102023000165330ustar 00000000000000use crate::{ bool_mask::LazySelect, num::{Arithmetics, Cbrt, One, PartialCmp, Powi, Real, Sqrt}, }; // Based on https://stackoverflow.com/q/4778147 and https://math.stackexchange.com/q/18686, // picking A = (0, 0), B = (0, 1), C = (1, 1) gives us: // // ( sqrt(r1) * r2 , sqrt(r1) * (1 - r2) + sqrt(r1) * r2 ) = // ( sqrt(r1) * r2 , sqrt(r1) - sqrt(r1) * r2 + sqrt(r1) * r2 ) = // ( sqrt(r1) * r2 , sqrt(r1) ) // // `sqrt(r1)` gives us the scale of the triangle, `r2` the radius. // Substituting, we get `x = scale * radius, y = scale` and thus for cone // sampling: `scale = powf(r1, 1.0/3.0)` and `radius = sqrt(r2)`. #[derive(Debug, PartialEq)] pub(crate) struct HsvSample { pub(crate) value: T, pub(crate) saturation: T, } #[inline] pub(crate) fn sample_hsv(r1: T, r2: T) -> HsvSample where T: Cbrt + Sqrt, { HsvSample { value: r1.cbrt(), saturation: r2.sqrt(), } } #[inline] pub(crate) fn invert_hsv_sample(sample: HsvSample) -> (T, T) where T: Powi, { (sample.value.powi(3), sample.saturation.powi(2)) } #[derive(Debug, PartialEq)] pub(crate) struct HslSample { pub(crate) saturation: T, pub(crate) lightness: T, } #[inline] pub(crate) fn sample_hsl(r1: T, r2: T) -> HslSample where T: Real + One + Cbrt + Sqrt + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect + Clone, { HslSample { saturation: r2.sqrt(), lightness: sample_bicone_height(r1), } } #[inline] fn sample_bicone_height(r1: T) -> T where T: Real + One + Cbrt + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect + Clone, { let mask = r1.lt_eq(&T::from_f64(0.5)); // Scale it up to [0, 1] or [0, 1) let r1 = lazy_select! { if mask.clone() => r1.clone(), else => T::one() - &r1, } * T::from_f64(2.0); let height = r1.cbrt(); // Turn the height back to [0, 0.5] or (0.5, 1.0] let height = height * T::from_f64(0.5); lazy_select! { if mask => height.clone(), else => T::one() - &height, } } #[inline] pub(crate) fn invert_hsl_sample(sample: HslSample) -> (T, T) where T: Real + Powi + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { let HslSample { saturation, lightness, } = sample; let r1 = invert_bicone_height_sample(lightness); // saturation is first multiplied, then divided by h before squaring. // h can be completely eliminated, leaving only the saturation. let r2 = saturation.powi(2); (r1, r2) } fn invert_bicone_height_sample(height: T) -> T where T: Real + Powi + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { lazy_select! { if height.lt_eq(&T::from_f64(0.5)) => { // ((x * 2)^3) / 2 = x^3 * 4. // height is multiplied by 2 to scale it up to [0, 1], becoming h. // h is cubed to make it r1. r1 is divided by 2 to take it back to [0, 0.5]. height.clone().powi(3) * T::from_f64(4.0) }, else => { let x = height.clone() - T::from_f64(1.0); x.powi(3) * T::from_f64(4.0) + T::from_f64(1.0) }, } } #[cfg(test)] mod test { use super::{sample_hsl, sample_hsv, HslSample, HsvSample}; #[cfg(feature = "random")] #[test] fn sample_max_min() { let a = sample_hsv(0.0, 0.0); let b = sample_hsv(1.0, 1.0); assert_eq!( HsvSample { saturation: 0.0, value: 0.0 }, a ); assert_eq!( HsvSample { saturation: 1.0, value: 1.0 }, b ); let a = sample_hsl(0.0, 0.0); let b = sample_hsl(1.0, 1.0); assert_eq!( HslSample { saturation: 0.0, lightness: 0.0 }, a ); assert_eq!( HslSample { saturation: 1.0, lightness: 1.0 }, b ); } #[cfg(all(feature = "random", feature = "approx"))] #[allow(clippy::excessive_precision)] #[test] fn hsl_sampling() { use super::invert_hsl_sample; // Sanity check that sampling and inverting from sample are equivalent macro_rules! test_hsl { ( $x:expr, $y:expr ) => {{ let hsl = sample_hsl($x, $y); let a = invert_hsl_sample(hsl); assert_relative_eq!(a.0, $x); assert_relative_eq!(a.1, $y); }}; } test_hsl!(0.8464721407, 0.8271899200); test_hsl!(0.8797234442, 0.4924621591); test_hsl!(0.9179406120, 0.8771350605); test_hsl!(0.5458023108, 0.1154283005); test_hsl!(0.2691241774, 0.7881780600); test_hsl!(0.2085030453, 0.9975406626); test_hsl!(0.8483632811, 0.4955013942); test_hsl!(0.0857919040, 0.0652214785); test_hsl!(0.7152662838, 0.2788421565); test_hsl!(0.2973598808, 0.5585230243); test_hsl!(0.0936619602, 0.7289450731); test_hsl!(0.4364395449, 0.9362269009); test_hsl!(0.9802381158, 0.9742974964); test_hsl!(0.1666129293, 0.4396910574); test_hsl!(0.6190216210, 0.7175675180); } } palette-0.7.5/src/random_sampling.rs000064400000000000000000000043071046102023000156060ustar 00000000000000mod cone; pub(crate) use self::cone::*; #[cfg(test)] pub(crate) mod test_utils { pub(crate) const BINS: usize = 10; pub(crate) const SAMPLES: usize = 20_000; /// Perform a Chi-squared goodness-of-fit test to check if the bins are /// uniformly distributed. Returns the p-value. pub(crate) fn uniform_distribution_test(bins: &[usize]) -> f64 { let sum = bins.iter().sum::() as f64; let expected = sum / bins.len() as f64; let critical_value = bins .iter() .map(|&bin| { let difference = bin as f64 - expected; difference * difference / expected }) .sum::(); chi_square(bins.len() - 1, critical_value) } // Shamelessly taken from https://www.codeproject.com/Articles/432194/How-to-Calculate-the-Chi-Squared-P-Value fn chi_square(dof: usize, critical_value: f64) -> f64 { if critical_value < 0.0 || dof < 1 { return 0.0; } let k = dof as f64 * 0.5; let x = critical_value * 0.5; if dof == 2 { return (-x).exp(); } let mut p_value = incomplete_gamma_function(k, x); if p_value.is_nan() || p_value.is_infinite() || p_value <= 1e-8 { return 1e-14; } p_value /= approximate_gamma(k); 1.0 - p_value } fn incomplete_gamma_function(mut s: f64, z: f64) -> f64 { if z < 0.0 { return 0.0; } let mut sc = 1.0 / s; sc *= z.powf(s); sc *= (-z).exp(); let mut sum = 1.0; let mut nom = 1.0; let mut denom = 1.0; for _ in 0..200 { nom *= z; s += 1.0; denom *= s; sum += nom / denom; } sum * sc } fn approximate_gamma(z: f64) -> f64 { #[allow(clippy::excessive_precision)] const RECIP_E: f64 = 0.36787944117144232159552377016147; // RECIP_E = (E^-1) = (1.0 / E) const TWOPI: f64 = core::f64::consts::TAU; let mut d = 1.0 / (10.0 * z); d = 1.0 / ((12.0 * z) - d); d = (d + z) * RECIP_E; d = d.powf(z); d *= (TWOPI / z).sqrt(); d } } palette-0.7.5/src/relative_contrast.rs000064400000000000000000000153371046102023000161710ustar 00000000000000use crate::{ bool_mask::{HasBoolMask, LazySelect}, num::{Arithmetics, PartialCmp, Real}, }; /// A trait for calculating relative contrast between two colors. /// /// W3C's Web Content Accessibility Guidelines (WCAG) 2.1 suggest a method /// to calculate accessible contrast ratios of text and background colors for /// those with low vision or color deficiencies, and for contrast of colors used /// in user interface graphics objects. /// /// These criteria are recommendations, not hard and fast rules. Most /// importantly, look at the colors in action and make sure they're clear and /// comfortable to read. A pair of colors may pass contrast guidelines but still /// be uncomfortable to look at. Favor readability over only satisfying the /// contrast ratio metric. It is recommended to verify the contrast ratio /// in the output format of the colors and not to assume the contrast ratio /// remains exactly the same across color formats. The following example checks /// the contrast ratio of two colors in RGB format. /// /// ```rust /// use std::str::FromStr; /// use palette::{Srgb, RelativeContrast}; /// # fn main() -> Result<(), palette::rgb::FromHexError> { /// /// // the rustdoc "DARK" theme background and text colors /// let background: Srgb = Srgb::from(0x353535).into_format(); /// let foreground = Srgb::from_str("#ddd")?.into_format(); /// /// assert!(background.has_enhanced_contrast_text(foreground)); /// # Ok(()) /// # } /// ``` /// /// The possible range of contrast ratios is from 1:1 to 21:1. There is a /// Success Criterion for Contrast (Minimum) and a Success Criterion for /// Contrast (Enhanced), SC 1.4.3 and SC 1.4.6 respectively, which are concerned /// with text and images of text. SC 1.4.11 is a Success Criterion for "non-text /// contrast" such as user interface components and other graphics. The relative /// contrast is calculated by `(L1 + 0.05) / (L2 + 0.05)`, where `L1` is the /// luminance of the brighter color and `L2` is the luminance of the darker /// color both in sRGB linear space. A higher contrast ratio is generally /// desirable. /// /// For more details, visit the following links: /// /// [Success Criterion 1.4.3 Contrast (Minimum) (Level AA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum) /// /// [Success Criterion 1.4.6 Contrast (Enhanced) (Level AAA)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced) /// /// [Success Criterion 1.4.11 Non-text Contrast (Level AA)](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html) #[doc(alias = "wcag")] #[deprecated( since = "0.7.2", note = "replaced by `palette::color_difference::Wcag21RelativeContrast`" )] pub trait RelativeContrast: Sized { /// The type of the contrast ratio. type Scalar: Real + PartialCmp; /// Calculate the contrast ratio between two colors. #[must_use] fn get_contrast_ratio(self, other: Self) -> Self::Scalar; /// Verify the contrast between two colors satisfies SC 1.4.3. Contrast /// is at least 4.5:1 (Level AA). #[must_use] #[inline] fn has_min_contrast_text(self, other: Self) -> ::Mask { self.get_contrast_ratio(other) .gt_eq(&Self::Scalar::from_f64(4.5)) } /// Verify the contrast between two colors satisfies SC 1.4.3 for large /// text. Contrast is at least 3:1 (Level AA). #[must_use] #[inline] fn has_min_contrast_large_text(self, other: Self) -> ::Mask { self.get_contrast_ratio(other) .gt_eq(&Self::Scalar::from_f64(3.0)) } /// Verify the contrast between two colors satisfies SC 1.4.6. Contrast /// is at least 7:1 (Level AAA). #[must_use] #[inline] fn has_enhanced_contrast_text(self, other: Self) -> ::Mask { self.get_contrast_ratio(other) .gt_eq(&Self::Scalar::from_f64(7.0)) } /// Verify the contrast between two colors satisfies SC 1.4.6 for large /// text. Contrast is at least 4.5:1 (Level AAA). #[must_use] #[inline] fn has_enhanced_contrast_large_text(self, other: Self) -> ::Mask { self.has_min_contrast_text(other) } /// Verify the contrast between two colors satisfies SC 1.4.11 for graphical /// objects. Contrast is at least 3:1 (Level AA). #[must_use] #[inline] fn has_min_contrast_graphics(self, other: Self) -> ::Mask { self.has_min_contrast_large_text(other) } } /// Calculate the ratio between two `luma` values. #[inline] #[deprecated( since = "0.7.2", note = "replaced by `LinLuma::relative_contrast`, via `Wcag21RelativeContrast`" )] pub fn contrast_ratio(luma1: T, luma2: T) -> T where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, { lazy_select! { if luma1.gt(&luma2) => (T::from_f64(0.05) + &luma1) / (T::from_f64(0.05) + &luma2), else => (T::from_f64(0.05) + &luma2) / (T::from_f64(0.05) + &luma1) } } #[cfg(feature = "approx")] #[cfg(test)] #[allow(deprecated)] mod test { use core::str::FromStr; use crate::RelativeContrast; use crate::Srgb; #[test] fn relative_contrast() { let white = Srgb::new(1.0f32, 1.0, 1.0); let black = Srgb::new(0.0, 0.0, 0.0); assert_relative_eq!(white.get_contrast_ratio(white), 1.0); assert_relative_eq!(white.get_contrast_ratio(black), 21.0); assert_relative_eq!( white.get_contrast_ratio(black), black.get_contrast_ratio(white) ); let c1 = Srgb::from_str("#600").unwrap().into_format(); assert_relative_eq!(c1.get_contrast_ratio(white), 13.41, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(black), 1.56, epsilon = 0.01); assert!(c1.has_min_contrast_text(white)); assert!(c1.has_min_contrast_large_text(white)); assert!(c1.has_enhanced_contrast_text(white)); assert!(c1.has_enhanced_contrast_large_text(white)); assert!(c1.has_min_contrast_graphics(white)); assert!(!c1.has_min_contrast_text(black)); assert!(!c1.has_min_contrast_large_text(black)); assert!(!c1.has_enhanced_contrast_text(black)); assert!(!c1.has_enhanced_contrast_large_text(black)); assert!(!c1.has_min_contrast_graphics(black)); let c1 = Srgb::from_str("#066").unwrap().into_format(); assert_relative_eq!(c1.get_contrast_ratio(white), 6.79, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(black), 3.09, epsilon = 0.01); let c1 = Srgb::from_str("#9f9").unwrap().into_format(); assert_relative_eq!(c1.get_contrast_ratio(white), 1.22, epsilon = 0.01); assert_relative_eq!(c1.get_contrast_ratio(black), 17.11, epsilon = 0.01); } } palette-0.7.5/src/rgb/channels.rs000064400000000000000000000213611046102023000150000ustar 00000000000000//! Channel orders for packed RGBA types. use crate::{cast::ComponentOrder, rgb}; /// RGBA color packed in ABGR order. /// /// See [Packed](crate::cast::Packed) for more details. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Abgr; impl ComponentOrder, [T; 4]> for Abgr { #[inline] fn pack(color: rgb::Rgba) -> [T; 4] { let [red, green, blue, alpha]: [T; 4] = color.into(); [alpha, blue, green, red] } #[inline] fn unpack(packed: [T; 4]) -> rgb::Rgba { let [alpha, blue, green, red] = packed; rgb::Rgba::new(red, green, blue, alpha) } } /// RGBA color packed in ARGB order. /// /// See [Packed](crate::cast::Packed) for more details. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Argb; impl ComponentOrder, [T; 4]> for Argb { #[inline] fn pack(color: rgb::Rgba) -> [T; 4] { let [red, green, blue, alpha]: [T; 4] = color.into(); [alpha, red, green, blue] } #[inline] fn unpack(packed: [T; 4]) -> rgb::Rgba { let [alpha, red, green, blue] = packed; rgb::Rgba::new(red, green, blue, alpha) } } /// RGBA color packed in BGRA order. /// /// See [Packed](crate::cast::Packed) for more details. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Bgra; impl ComponentOrder, [T; 4]> for Bgra { #[inline] fn pack(color: rgb::Rgba) -> [T; 4] { let [red, green, blue, alpha]: [T; 4] = color.into(); [blue, green, red, alpha] } #[inline] fn unpack(packed: [T; 4]) -> rgb::Rgba { let [blue, green, red, alpha] = packed; rgb::Rgba::new(red, green, blue, alpha) } } /// RGBA color packed in RGBA order. /// /// See [Packed](crate::cast::Packed) for more details. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Rgba; impl ComponentOrder, [T; 4]> for Rgba { #[inline] fn pack(color: rgb::Rgba) -> [T; 4] { let [red, green, blue, alpha]: [T; 4] = color.into(); [red, green, blue, alpha] } #[inline] fn unpack(packed: [T; 4]) -> rgb::Rgba { let [red, green, blue, alpha] = packed; rgb::Rgba::new(red, green, blue, alpha) } } #[cfg(feature = "approx")] #[cfg(test)] mod test { use super::{Abgr, Argb, Bgra, Rgba}; use crate::{cast::Packed, Srgb, Srgba}; #[test] fn rgba() { let a1: Packed = Srgb::new(0.5, 0.0, 0.0).into_format().into(); let a2: Packed = Srgb::new(0.0, 1.0, 0.0).into_format().into(); let a3: Packed = Srgb::new(0.0, 0.0, 0.5).into_format().into(); let x1: u32 = 0x8000_00FF; let x2: u32 = 0x00FF_00FF; let x3: u32 = 0x0000_80FF; assert_eq!(a1.color, x1); assert_eq!(a2.color, x2); assert_eq!(a3.color, x3); let unpacked: Srgb = Packed::::from(0x80FF_80FF).into(); assert_relative_eq!( Srgb::new(0.5, 1.0, 0.5), unpacked.into_format(), epsilon = 0.01 ); let b1: Packed = Srgba::new(0.5, 0.0, 0.0, 0.0).into_format().into(); let b2: Packed = Srgba::new(0.0, 1.0, 0.0, 0.0).into_format().into(); let b3: Packed = Srgba::new(0.0, 0.0, 0.5, 0.0).into_format().into(); let b4: Packed = Srgba::new(0.0, 0.0, 0.0, 1.0).into_format().into(); let y1: u32 = 0x8000_0000; let y2: u32 = 0x00FF_0000; let y3: u32 = 0x0000_8000; let y4: u32 = 0x0000_00FF; assert_eq!(b1.color, y1); assert_eq!(b2.color, y2); assert_eq!(b3.color, y3); assert_eq!(b4.color, y4); let unpacked: Srgba = Packed::::from(0x80FF_80FF).into(); assert_relative_eq!( Srgba::new(0.5, 1.0, 0.5, 1.0), unpacked.into_format(), epsilon = 0.01 ); } #[test] fn argb() { let a1: Packed = Srgb::new(0.5, 0.0, 0.0).into_format().into(); let a2: Packed = Srgb::new(0.0, 1.0, 0.0).into_format().into(); let a3: Packed = Srgb::new(0.0, 0.0, 0.5).into_format().into(); let x1: u32 = 0xFF80_0000; let x2: u32 = 0xFF00_FF00; let x3: u32 = 0xFF00_0080; assert_eq!(a1.color, x1); assert_eq!(a2.color, x2); assert_eq!(a3.color, x3); let unpacked: Srgb = Packed::::from(0x80FF_80FF).into(); assert_relative_eq!( Srgb::new(1.0, 0.5, 1.0), unpacked.into_format(), epsilon = 0.01 ); let b1: Packed = Srgba::new(0.5, 0.0, 0.0, 0.0).into_format().into(); let b2: Packed = Srgba::new(0.0, 1.0, 0.0, 0.0).into_format().into(); let b3: Packed = Srgba::new(0.0, 0.0, 0.5, 0.0).into_format().into(); let b4: Packed = Srgba::new(0.0, 0.0, 0.0, 1.0).into_format().into(); let y1: u32 = 0x0080_0000; let y2: u32 = 0x0000_FF00; let y3: u32 = 0x0000_0080; let y4: u32 = 0xFF00_0000; assert_eq!(b1.color, y1); assert_eq!(b2.color, y2); assert_eq!(b3.color, y3); assert_eq!(b4.color, y4); let unpacked: Srgba = Packed::::from(0x80FF_80FF).into(); assert_relative_eq!( Srgba::new(1.0, 0.5, 1.0, 0.5), unpacked.into_format(), epsilon = 0.01 ); } #[test] fn bgra() { let a1: Packed = Srgb::new(0.5, 0.0, 0.0).into_format().into(); let a2: Packed = Srgb::new(0.0, 1.0, 0.0).into_format().into(); let a3: Packed = Srgb::new(0.0, 0.0, 0.5).into_format().into(); let x1: u32 = 0x0000_80FF; let x2: u32 = 0x00FF_00FF; let x3: u32 = 0x8000_00FF; assert_eq!(a1.color, x1); assert_eq!(a2.color, x2); assert_eq!(a3.color, x3); let unpacked: Srgb = Packed::::from(0x80FF_FF80).into(); assert_relative_eq!( Srgb::new(1.0, 1.0, 0.5), unpacked.into_format(), epsilon = 0.01 ); let b1: Packed = Srgba::new(0.5, 0.0, 0.0, 0.0).into_format().into(); let b2: Packed = Srgba::new(0.0, 1.0, 0.0, 0.0).into_format().into(); let b3: Packed = Srgba::new(0.0, 0.0, 0.5, 0.0).into_format().into(); let b4: Packed = Srgba::new(0.0, 0.0, 0.0, 1.0).into_format().into(); let y1: u32 = 0x0000_8000; let y2: u32 = 0x00FF_0000; let y3: u32 = 0x8000_0000; let y4: u32 = 0x0000_00FF; assert_eq!(b1.color, y1); assert_eq!(b2.color, y2); assert_eq!(b3.color, y3); assert_eq!(b4.color, y4); let unpacked: Srgba = Packed::::from(0x80FF_FF80).into(); assert_relative_eq!( Srgba::new(1.0, 1.0, 0.5, 0.5), unpacked.into_format(), epsilon = 0.01 ); } #[test] fn abgr() { let a1: Packed = Srgb::new(0.5, 0.0, 0.0).into_format().into(); let a2: Packed = Srgb::new(0.0, 1.0, 0.0).into_format().into(); let a3: Packed = Srgb::new(0.0, 0.0, 0.5).into_format().into(); let x1: u32 = 0xFF00_0080; let x2: u32 = 0xFF00_FF00; let x3: u32 = 0xFF80_0000; assert_eq!(a1.color, x1); assert_eq!(a2.color, x2); assert_eq!(a3.color, x3); let unpacked: Srgb = Packed::::from(0x80FF_FF80).into(); assert_relative_eq!( Srgb::new(0.5, 1.0, 1.0), unpacked.into_format(), epsilon = 0.01 ); let b1: Packed = Srgba::new(0.5, 0.0, 0.0, 0.0).into_format().into(); let b2: Packed = Srgba::new(0.0, 1.0, 0.0, 0.0).into_format().into(); let b3: Packed = Srgba::new(0.0, 0.0, 0.5, 0.0).into_format().into(); let b4: Packed = Srgba::new(0.0, 0.0, 0.0, 1.0).into_format().into(); let y1: u32 = 0x0000_0080; let y2: u32 = 0x0000_FF00; let y3: u32 = 0x0080_0000; let y4: u32 = 0xFF00_0000; assert_eq!(b1.color, y1); assert_eq!(b2.color, y2); assert_eq!(b3.color, y3); assert_eq!(b4.color, y4); let unpacked: Srgba = Packed::::from(0x80FF_FF80).into(); assert_relative_eq!( Srgba::new(0.5, 1.0, 1.0, 0.5), unpacked.into_format(), epsilon = 0.01 ); } #[test] fn u32_to_color() { assert_eq!(0xFFFF_FF80, u32::from(Srgb::new(255u8, 255, 128))); assert_eq!(0x7FFF_FF80, u32::from(Srgba::new(127u8, 255u8, 255, 128))); } } palette-0.7.5/src/rgb/rgb.rs000064400000000000000000001371621046102023000137660ustar 00000000000000use core::{ any::TypeId, fmt, fmt::Debug, marker::PhantomData, num::ParseIntError, ops::{Add, Div}, str::FromStr, }; use crate::{ alpha::Alpha, angle::{RealAngle, UnsignedAngle}, bool_mask::{BitOps, HasBoolMask, LazySelect}, cast::{ComponentOrder, Packed}, color_difference::Wcag21RelativeContrast, convert::{FromColorUnclamped, IntoColorUnclamped}, encoding::{FromLinear, IntoLinear, Linear, Srgb}, luma::LumaStandard, matrix::{matrix_inverse, matrix_map, multiply_xyz_to_rgb, rgb_to_xyz_matrix}, num::{ Abs, Arithmetics, FromScalar, IsValidDivisor, MinMax, One, PartialCmp, Real, Recip, Round, Trigonometry, Zero, }, oklab::oklab_to_linear_srgb, rgb::{RgbSpace, RgbStandard}, stimulus::{FromStimulus, Stimulus, StimulusColor}, white_point::{Any, WhitePoint, D65}, FromColor, GetHue, Hsl, Hsv, IntoColor, Luma, Oklab, RgbHue, Xyz, Yxy, }; use super::Primaries; /// Generic RGB with an alpha component. See the [`Rgba` implementation in /// `Alpha`](crate::Alpha#Rgba). pub type Rgba = Alpha, T>; /// Generic RGB. /// /// RGB is probably the most common color space, when it comes to computer /// graphics, and it's defined as an additive mixture of red, green and blue /// light, where gray scale colors are created when these three channels are /// equal in strength. /// /// # Creating a Value /// /// RGB comes in different shapes and formats. You will probably want to start /// with either the [`Srgb`](crate::Srgb) or [`Srgba`](crate::Srgba) alias, /// which represents the common sRGB format that most images and tools use. /// Then, depending on your input, you can either just call [`new`](Rgb::new) or /// convert from another data format. /// /// ``` /// use palette::Srgb; /// /// let rgb_u8 = Srgb::new(171u8, 193, 35); /// let rgb_f32 = Srgb::new(0.3f32, 0.8, 0.1); /// /// // `new` is also `const`: /// const RGB_U8: Srgb = Srgb::new(171, 193, 35); /// /// // Converting from one number format to another is as simple as this: /// let rgb_u8_from_f32 = Srgb::new(0.3f32, 0.8, 0.1).into_format::(); /// /// // Hexadecimal is also supported, with or without the #: /// let rgb_from_hex1: Srgb = "#f034e6".parse().unwrap(); /// let rgb_from_hex2: Srgb = "f034e6".parse().unwrap(); /// assert_eq!(rgb_from_hex1, rgb_from_hex2); /// /// // This includes the shorthand format: /// let rgb_from_short_hex: Srgb = "f3e".parse().unwrap(); /// let rgb_from_long_hex: Srgb = "ff33ee".parse().unwrap(); /// assert_eq!(rgb_from_short_hex, rgb_from_long_hex); /// /// // It's also possible to convert from (and to) arrays, tuples and `u32` values: /// let rgb_from_array = Srgb::from([171u8, 193, 35]); /// let rgb_from_tuple = Srgb::from((171u8, 193, 35)); /// let rgb_from_u32 = Srgb::from(0x607F00); /// ``` /// /// # Linear, sRGB and Gamma Correction /// /// Many conversions and operations on RGB require that it's linear, meaning /// that gamma correction is required when converting to and from displayable /// RGB, such as sRGB. It's common to store and send RGB values where the /// numbers are on a non-linear scale. In a non-linear format, a value of, for /// example, 0.5 would not represent a light intensity of 50%, which makes some /// operations (such as blurring) give incorrect results. /// /// You will probably encounter or use [`LinSrgb`](crate::LinSrgb) or /// [`LinSrgba`](crate::LinSrgba) at some point. These are aliases for linear /// sRGB and would usually be obtained by converting an [`Srgb`] value with /// [`into_linear`](Rgb::into_linear). /// /// ```no_run /// use palette::{LinSrgb, Srgb}; /// /// // This function uses linear sRGB for something. But how do we interface with it? /// fn uses_linear_srgb(input: LinSrgb) -> LinSrgb { todo!() } /// /// // Linear sRGB will usually be created from non-linear sRGB: /// let output = uses_linear_srgb(Srgb::new(0.3, 0.8, 0.1).into_linear()); /// /// // It's also possible to convert directly from u8 to f32 for sRGB. /// // This is much faster than using `into_format` first: /// let output = uses_linear_srgb(Srgb::new(171u8, 193, 35).into_linear()); /// /// // Converting the output back to `Srgb` (or `Srgb`) is just as simple: /// let output_u8 = Srgb::::from_linear(output); /// // ..or: /// let output_u8: Srgb = output.into_encoding(); /// ``` /// /// It's of course also possible to create a linear value from constants, but /// it's not necessarily as intuitive. It's best to avoid storing them as /// `LinSrgb` (or `LinRgb<_, u8>`) values, to avoid banding among dark /// colors. /// /// See the [`encoding`](crate::encoding) module for built-in encoding formats. /// /// # Storage Formats and Pixel Buffers /// /// It's common to read and write RGB values as bytes, hexadecimal strings, or /// sometimes `u32` values. A single RGB value can be converted to all of these /// formats and more. /// /// ```no_run /// use palette::{Srgb, LinSrgb}; /// /// let source: LinSrgb = todo!(); /// /// let u8_array: [u8; 3] = Srgb::from_linear(source).into(); /// let hex_string1 = format!("#{:x}", Srgb::::from_linear(source)); // The # is optional. /// let u32_value: u32 = Srgb::from_linear(source).into(); /// ``` /// /// It's also possible to control the component order. /// [`PackedArgb`](crate::rgb::PackedArgb) is one of a few aliases for /// [`Packed`], which represents a color that has been "packed" into a specific /// data format. This can be a `u32` or `[u8; 4]`, for example. This is helpful /// for reading and writing colors with a different order than the default RGBA. /// /// ```no_run /// use palette::{rgb::PackedArgb, Srgba, LinSrgba}; /// /// let source: LinSrgba = todo!(); /// /// let u8_array: [u8; 4] = PackedArgb::from(Srgba::from_linear(source)).into(); /// let u32_value: u32 = PackedArgb::from(Srgba::from_linear(source)).into(); /// ``` /// /// If you need to work with colors in a byte buffer, such as `[u8]`, `Vec` /// or the `image` crate, there's a quick way to borrow that buffer as a slice /// of RGB(A) colors. The [`cast`](crate::cast) module has a number of traits /// and functions for casting values without copying them. /// /// ```no_run /// use image::RgbImage; /// use palette::{cast::ComponentsAsMut, Srgb}; /// /// let mut image: RgbImage = todo!(); /// let pixels: &mut [Srgb] = image.components_as_mut(); /// /// for pixel in pixels { /// std::mem::swap(&mut pixel.red, &mut pixel.blue); /// } /// ``` #[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] #[palette( palette_internal, rgb_standard = "S", component = "T", skip_derives(Xyz, Hsv, Hsl, Luma, Rgb, Oklab) )] #[repr(C)] pub struct Rgb { /// The amount of red light, where 0.0 is no red light and 1.0 (or 255u8) is /// the highest displayable amount. pub red: T, /// The amount of green light, where 0.0 is no green light and 1.0 (or /// 255u8) is the highest displayable amount. pub green: T, /// The amount of blue light, where 0.0 is no blue light and 1.0 (or 255u8) /// is the highest displayable amount. pub blue: T, /// The kind of RGB standard. sRGB is the default. #[cfg_attr(feature = "serializing", serde(skip))] #[palette(unsafe_zero_sized)] pub standard: PhantomData, } impl Rgb { /// Create an RGB color. /// /// It's possible to create a color in one number format and convert it to /// another format with either [`into_format`](Rgb::into_format) or /// [`into_linear`](Rgb::into_linear). /// /// ``` /// use palette::{Srgb, LinSrgb}; /// /// // Changes only the number format: /// let rgb_f32: Srgb = Srgb::new(171u8, 193, 35).into_format(); /// /// // Changes the number format and converts to linear in one go. /// // This is faster than `.into_format().into_linear()`: /// let linear: LinSrgb = Srgb::new(171u8, 193, 35).into_linear(); /// ``` pub const fn new(red: T, green: T, blue: T) -> Rgb { Rgb { red, green, blue, standard: PhantomData, } } /// Convert the RGB components into another number type. /// /// ``` /// use palette::Srgb; /// /// let rgb_u8: Srgb = Srgb::new(0.3, 0.7, 0.2).into_format(); /// ``` /// /// See also [`into_linear`](Rgb::into_linear) and /// [`into_encoding`](Rgb::into_encoding) for a faster option if you need to /// change between linear and non-linear encoding at the same time. pub fn into_format(self) -> Rgb where U: FromStimulus, { Rgb { red: U::from_stimulus(self.red), green: U::from_stimulus(self.green), blue: U::from_stimulus(self.blue), standard: PhantomData, } } /// Convert the RGB components from another number type. /// /// ``` /// use palette::Srgb; /// /// let rgb_u8 = Srgb::::from_format(Srgb::new(0.3, 0.7, 0.2)); /// ``` /// /// See also [`from_linear`](Rgb::from_linear) and /// [`from_encoding`](Rgb::from_encoding) for a faster option if you need to /// change between linear and non-linear encoding at the same time. pub fn from_format(color: Rgb) -> Self where T: FromStimulus, { color.into_format() } /// Convert to a `(red, green, blue)` tuple. pub fn into_components(self) -> (T, T, T) { (self.red, self.green, self.blue) } /// Convert from a `(red, green, blue)` tuple. pub fn from_components((red, green, blue): (T, T, T)) -> Self { Self::new(red, green, blue) } } impl Rgb where T: Stimulus, { /// Return the `red` value minimum. pub fn min_red() -> T { T::zero() } /// Return the `red` value maximum. pub fn max_red() -> T { T::max_intensity() } /// Return the `green` value minimum. pub fn min_green() -> T { T::zero() } /// Return the `green` value maximum. pub fn max_green() -> T { T::max_intensity() } /// Return the `blue` value minimum. pub fn min_blue() -> T { T::zero() } /// Return the `blue` value maximum. pub fn max_blue() -> T { T::max_intensity() } } impl Rgb { /// Convert to a packed `u32` with with specifiable component order. /// /// ``` /// use palette::{rgb, Srgb}; /// /// let integer = Srgb::new(96u8, 127, 0).into_u32::(); /// assert_eq!(0x607F00FF, integer); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xAARRGGBB` component order: /// /// ``` /// use palette::Srgb; /// /// let integer = u32::from(Srgb::new(96u8, 127, 0)); /// assert_eq!(0xFF607F00, integer); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn into_u32(self) -> u32 where O: ComponentOrder, u32>, { O::pack(Rgba::from(self)) } /// Convert from a packed `u32` with specifiable component order. /// /// ``` /// use palette::{rgb, Srgb}; /// /// let rgb = Srgb::from_u32::(0x607F00FF); /// assert_eq!(Srgb::new(96u8, 127, 0), rgb); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xAARRGGBB` component order: /// /// ``` /// use palette::Srgb; /// /// let rgb = Srgb::from(0x607F00); /// assert_eq!(Srgb::new(96u8, 127, 0), rgb); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn from_u32(color: u32) -> Self where O: ComponentOrder, u32>, { O::unpack(color).color } } impl Rgb { /// Convert the color to linear RGB. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgb, LinSrgb}; /// /// let linear: LinSrgb = Srgb::new(96u8, 127, 0).into_linear(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. #[inline(always)] pub fn into_linear(self) -> Rgb, U> where S::TransferFn: IntoLinear, { Rgb::new( S::TransferFn::into_linear(self.red), S::TransferFn::into_linear(self.green), S::TransferFn::into_linear(self.blue), ) } /// Convert linear RGB to non-linear RGB. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgb, LinSrgb}; /// /// let encoded = Srgb::::from_linear(LinSrgb::new(0.95f32, 0.90, 0.30)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. #[inline(always)] pub fn from_linear(color: Rgb, U>) -> Self where S::TransferFn: FromLinear, { Rgb::new( S::TransferFn::from_linear(color.red), S::TransferFn::from_linear(color.green), S::TransferFn::from_linear(color.blue), ) } } impl Rgb, T> { /// Convert a linear color to a different encoding. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgb, LinSrgb}; /// /// let encoded: Srgb = LinSrgb::new(0.95f32, 0.90, 0.30).into_encoding(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn into_encoding(self) -> Rgb where St: RgbStandard, St::TransferFn: FromLinear, { Rgb::::from_linear(self) } /// Convert linear RGB from a different encoding. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgb, LinSrgb}; /// /// let linear = LinSrgb::::from_encoding(Srgb::new(96u8, 127, 0)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn from_encoding(color: Rgb) -> Self where St: RgbStandard, St::TransferFn: IntoLinear, { color.into_linear() } } impl Rgb where S: RgbStandard, { #[inline] pub(crate) fn reinterpret_as(self) -> Rgb where S::Space: RgbSpace::WhitePoint>, St: RgbStandard, { Rgb { red: self.red, green: self.green, blue: self.blue, standard: PhantomData, } } } /// [`Rgba`](crate::rgb::Rgba) implementations. impl Alpha, A> { /// Non-linear RGB. pub const fn new(red: T, green: T, blue: T, alpha: A) -> Self { Alpha { color: Rgb::new(red, green, blue), alpha, } } /// Convert the RGBA components into other number types. /// /// ``` /// use palette::Srgba; /// /// let rgba_u8: Srgba = Srgba::new(0.3, 0.7, 0.2, 0.5).into_format(); /// ``` /// /// See also `into_linear` and `into_encoding` for a faster option if you /// need to change between linear and non-linear encoding at the same time. pub fn into_format(self) -> Alpha, B> where U: FromStimulus, B: FromStimulus, { Alpha { color: self.color.into_format(), alpha: B::from_stimulus(self.alpha), } } /// Convert the RGBA components from other number types. /// /// ``` /// use palette::Srgba; /// /// let rgba_u8 = Srgba::::from_format(Srgba::new(0.3, 0.7, 0.2, 0.5)); /// ``` /// /// See also `from_linear` and `from_encoding` for a faster option if you /// need to change between linear and non-linear encoding at the same time. pub fn from_format(color: Alpha, B>) -> Self where T: FromStimulus, A: FromStimulus, { color.into_format() } /// Convert to a `(red, green, blue, alpha)` tuple. pub fn into_components(self) -> (T, T, T, A) { ( self.color.red, self.color.green, self.color.blue, self.alpha, ) } /// Convert from a `(red, green, blue, alpha)` tuple. pub fn from_components((red, green, blue, alpha): (T, T, T, A)) -> Self { Self::new(red, green, blue, alpha) } } impl Rgba { /// Convert to a packed `u32` with with specifiable component order. /// /// ``` /// use palette::{rgb, Srgba}; /// /// let integer = Srgba::new(96u8, 127, 0, 255).into_u32::(); /// assert_eq!(0xFF607F00, integer); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xRRGGBBAA` component order: /// /// ``` /// use palette::Srgba; /// /// let integer = u32::from(Srgba::new(96u8, 127, 0, 255)); /// assert_eq!(0x607F00FF, integer); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn into_u32(self) -> u32 where O: ComponentOrder, u32>, { O::pack(self) } /// Convert from a packed `u32` with specifiable component order. /// /// ``` /// use palette::{rgb, Srgba}; /// /// let rgba = Srgba::from_u32::(0xFF607F00); /// assert_eq!(Srgba::new(96u8, 127, 0, 255), rgba); /// ``` /// /// It's also possible to use `From` and `Into`, which defaults to the /// `0xRRGGBBAA` component order: /// /// ``` /// use palette::Srgba; /// /// let rgba = Srgba::from(0x607F00FF); /// assert_eq!(Srgba::new(96u8, 127, 0, 255), rgba); /// ``` /// /// See [Packed](crate::cast::Packed) for more details. #[inline] pub fn from_u32(color: u32) -> Self where O: ComponentOrder, u32>, { O::unpack(color) } } impl Alpha, A> { /// Convert the color to linear RGB with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgba, LinSrgba}; /// /// let linear: LinSrgba = Srgba::new(96u8, 127, 0, 38).into_linear(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn into_linear(self) -> Alpha, U>, B> where S::TransferFn: IntoLinear, B: FromStimulus, { Alpha { color: self.color.into_linear(), alpha: B::from_stimulus(self.alpha), } } /// Convert linear RGB to non-linear RGB with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgba, LinSrgba}; /// /// let encoded = Srgba::::from_linear(LinSrgba::new(0.95f32, 0.90, 0.30, 0.75)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn from_linear(color: Alpha, U>, B>) -> Self where S::TransferFn: FromLinear, A: FromStimulus, { Alpha { color: Rgb::from_linear(color.color), alpha: A::from_stimulus(color.alpha), } } } impl Alpha, T>, A> { /// Convert a linear color to a different encoding with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgba, LinSrgba}; /// /// let encoded: Srgba = LinSrgba::new(0.95f32, 0.90, 0.30, 0.75).into_encoding(); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn into_encoding(self) -> Alpha, B> where St: RgbStandard, St::TransferFn: FromLinear, B: FromStimulus, { Alpha::, B>::from_linear(self) } /// Convert RGB from a different encoding to linear with transparency. /// /// Some transfer functions allow the component type to be converted at the /// same time. This is usually offered with increased performance, compared /// to using [`into_format`][Rgb::into_format]. /// /// ``` /// use palette::{Srgba, LinSrgba}; /// /// let linear = LinSrgba::::from_encoding(Srgba::new(96u8, 127, 0, 38)); /// ``` /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. pub fn from_encoding(color: Alpha, B>) -> Self where St: RgbStandard, St::TransferFn: IntoLinear, A: FromStimulus, { color.into_linear() } } impl_reference_component_methods!(Rgb, [red, green, blue], standard); impl_struct_of_arrays_methods!(Rgb, [red, green, blue], standard); impl FromColorUnclamped> for Rgb where S1: RgbStandard + 'static, S2: RgbStandard + 'static, S1::TransferFn: FromLinear, S2::TransferFn: IntoLinear, S2::Space: RgbSpace::WhitePoint>, Xyz<::WhitePoint, T>: FromColorUnclamped>, Rgb: FromColorUnclamped::WhitePoint, T>>, { fn from_color_unclamped(rgb: Rgb) -> Self { let rgb_space1 = TypeId::of::<::Primaries>(); let rgb_space2 = TypeId::of::<::Primaries>(); if TypeId::of::() == TypeId::of::() { rgb.reinterpret_as() } else if rgb_space1 == rgb_space2 { Self::from_linear(rgb.into_linear().reinterpret_as()) } else { Self::from_color_unclamped(Xyz::from_color_unclamped(rgb)) } } } impl FromColorUnclamped::WhitePoint, T>> for Rgb where S: RgbStandard, S::TransferFn: FromLinear, ::Primaries: Primaries, ::WhitePoint: WhitePoint, T: Arithmetics + FromScalar, T::Scalar: Real + Recip + IsValidDivisor + Arithmetics + Clone + FromScalar, Yxy: IntoColorUnclamped>, { fn from_color_unclamped(color: Xyz<::WhitePoint, T>) -> Self { let transform_matrix = S::Space::xyz_to_rgb_matrix().map_or_else( || matrix_inverse(rgb_to_xyz_matrix::()), |matrix| matrix_map(matrix, T::Scalar::from_f64), ); Self::from_linear(multiply_xyz_to_rgb(transform_matrix, color)) } } impl FromColorUnclamped> for Rgb where T: Real + RealAngle + UnsignedAngle + Zero + One + Abs + Round + PartialCmp + Arithmetics + Clone, T::Mask: LazySelect + BitOps + Clone, { fn from_color_unclamped(hsl: Hsl) -> Self { let c = (T::one() - (hsl.lightness.clone() * T::from_f64(2.0) - T::one()).abs()) * hsl.saturation; let h = hsl.hue.into_positive_degrees() / T::from_f64(60.0); // We avoid using %, since it's not always (or never?) supported in SIMD let h_mod_two = h.clone() - Round::floor(h.clone() * T::from_f64(0.5)) * T::from_f64(2.0); let x = c.clone() * (T::one() - (h_mod_two - T::one()).abs()); let m = hsl.lightness - c.clone() * T::from_f64(0.5); let is_zone0 = h.gt_eq(&T::zero()) & h.lt(&T::one()); let is_zone1 = h.gt_eq(&T::one()) & h.lt(&T::from_f64(2.0)); let is_zone2 = h.gt_eq(&T::from_f64(2.0)) & h.lt(&T::from_f64(3.0)); let is_zone3 = h.gt_eq(&T::from_f64(3.0)) & h.lt(&T::from_f64(4.0)); let is_zone4 = h.gt_eq(&T::from_f64(4.0)) & h.lt(&T::from_f64(5.0)); let red = lazy_select! { if is_zone1.clone() | &is_zone4 => x.clone(), if is_zone2.clone() | &is_zone3 => T::zero(), else => c.clone(), }; let green = lazy_select! { if is_zone0.clone() | &is_zone3 => x.clone(), if is_zone1.clone() | &is_zone2 => c.clone(), else => T::zero(), }; let blue = lazy_select! { if is_zone0 | is_zone1 => T::zero(), if is_zone3 | is_zone4 => c, else => x, }; Rgb { red: red + m.clone(), green: green + m.clone(), blue: blue + m, standard: PhantomData, } } } impl FromColorUnclamped> for Rgb where T: Real + RealAngle + UnsignedAngle + Round + Zero + One + Abs + PartialCmp + Arithmetics + Clone, T::Mask: LazySelect + BitOps + Clone, { fn from_color_unclamped(hsv: Hsv) -> Self { let c = hsv.value.clone() * hsv.saturation; let h = hsv.hue.into_positive_degrees() / T::from_f64(60.0); // We avoid using %, since it's not always (or never?) supported in SIMD let h_mod_two = h.clone() - Round::floor(h.clone() * T::from_f64(0.5)) * T::from_f64(2.0); let x = c.clone() * (T::one() - (h_mod_two - T::one()).abs()); let m = hsv.value - c.clone(); let is_zone0 = h.gt_eq(&T::zero()) & h.lt(&T::one()); let is_zone1 = h.gt_eq(&T::one()) & h.lt(&T::from_f64(2.0)); let is_zone2 = h.gt_eq(&T::from_f64(2.0)) & h.lt(&T::from_f64(3.0)); let is_zone3 = h.gt_eq(&T::from_f64(3.0)) & h.lt(&T::from_f64(4.0)); let is_zone4 = h.gt_eq(&T::from_f64(4.0)) & h.lt(&T::from_f64(5.0)); let red = lazy_select! { if is_zone1.clone() | &is_zone4 => x.clone(), if is_zone2.clone() | &is_zone3 => T::zero(), else => c.clone(), }; let green = lazy_select! { if is_zone0.clone() | &is_zone3 => x.clone(), if is_zone1.clone() | &is_zone2 => c.clone(), else => T::zero(), }; let blue = lazy_select! { if is_zone0 | is_zone1 => T::zero(), if is_zone3 | is_zone4 => c, else => x, }; Rgb { red: red + m.clone(), green: green + m.clone(), blue: blue + m, standard: PhantomData, } } } impl FromColorUnclamped> for Rgb where S: RgbStandard + 'static, St: LumaStandard::WhitePoint> + 'static, S::TransferFn: FromLinear, St::TransferFn: IntoLinear, T: Clone, { #[inline] fn from_color_unclamped(color: Luma) -> Self { if TypeId::of::() == TypeId::of::() { Rgb { red: color.luma.clone(), green: color.luma.clone(), blue: color.luma, standard: PhantomData, } } else { let luma = color.into_linear(); Self::from_linear(Rgb { red: luma.luma.clone(), green: luma.luma.clone(), blue: luma.luma, standard: PhantomData, }) } } } impl FromColorUnclamped> for Rgb where T: Real + Arithmetics + Copy, S: RgbStandard, S::TransferFn: FromLinear, S::Space: RgbSpace + 'static, Rgb, T>: IntoColorUnclamped, Xyz: FromColorUnclamped> + IntoColorUnclamped, { fn from_color_unclamped(oklab: Oklab) -> Self { if TypeId::of::<::Space>() == TypeId::of::() { // Use direct sRGB to Oklab conversion // Rounding errors are likely a contributing factor to differences. // Also the conversion via XYZ doesn't use pre-defined matrices (yet) oklab_to_linear_srgb(oklab).into_color_unclamped() } else { // Convert via XYZ Xyz::from_color_unclamped(oklab).into_color_unclamped() } } } impl_is_within_bounds! { Rgb { red => [Self::min_red(), Self::max_red()], green => [Self::min_green(), Self::max_green()], blue => [Self::min_blue(), Self::max_blue()] } where T: Stimulus } impl_clamp! { Rgb { red => [Self::min_red(), Self::max_red()], green => [Self::min_green(), Self::max_green()], blue => [Self::min_blue(), Self::max_blue()] } other {standard} where T: Stimulus } impl_mix!(Rgb); impl_lighten! { Rgb increase { red => [Self::min_red(), Self::max_red()], green => [Self::min_green(), Self::max_green()], blue => [Self::min_blue(), Self::max_blue()] } other {} phantom: standard where T: Stimulus, } impl GetHue for Rgb where T: Real + RealAngle + Trigonometry + Arithmetics + Clone, { type Hue = RgbHue; fn get_hue(&self) -> RgbHue { let sqrt_3: T = T::from_f64(1.73205081); RgbHue::from_cartesian( self.red.clone() * T::from_f64(2.0) - self.green.clone() - self.blue.clone(), sqrt_3 * (self.green.clone() - self.blue.clone()), ) } } impl_premultiply!(Rgb {red, green, blue} phantom: standard); impl_euclidean_distance!(Rgb {red, green, blue}); impl StimulusColor for Rgb where T: Stimulus {} impl HasBoolMask for Rgb where T: HasBoolMask, { type Mask = T::Mask; } impl Default for Rgb where T: Stimulus, { fn default() -> Rgb { Rgb::new(Self::min_red(), Self::min_green(), Self::min_blue()) } } impl_color_add!(Rgb, [red, green, blue], standard); impl_color_sub!(Rgb, [red, green, blue], standard); impl_color_mul!(Rgb, [red, green, blue], standard); impl_color_div!(Rgb, [red, green, blue], standard); impl_tuple_conversion!(Rgb as (T, T, T)); impl_array_casts!(Rgb, [T; 3]); impl_simd_array_conversion!(Rgb, [red, green, blue], standard); impl_struct_of_array_traits!(Rgb, [red, green, blue], standard); impl_eq!(Rgb, [red, green, blue]); impl_copy_clone!(Rgb, [red, green, blue], standard); impl fmt::LowerHex for Rgb where T: fmt::LowerHex, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let size = f.width().unwrap_or(::core::mem::size_of::() * 2); write!( f, "{:0width$x}{:0width$x}{:0width$x}", self.red, self.green, self.blue, width = size ) } } impl fmt::UpperHex for Rgb where T: fmt::UpperHex, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let size = f.width().unwrap_or(::core::mem::size_of::() * 2); write!( f, "{:0width$X}{:0width$X}{:0width$X}", self.red, self.green, self.blue, width = size ) } } /// Error type for parsing a string of hexadecimal characters to an `Rgb` color. #[derive(Debug)] pub enum FromHexError { /// An error occurred while parsing the string into a valid integer. ParseIntError(ParseIntError), /// The hex value was not in a valid 3 or 6 character format. HexFormatError(&'static str), /// The hex value was not in a valid 4 or 8 character format. RgbaHexFormatError(&'static str), } impl From for FromHexError { fn from(err: ParseIntError) -> FromHexError { FromHexError::ParseIntError(err) } } impl From<&'static str> for FromHexError { fn from(err: &'static str) -> FromHexError { FromHexError::HexFormatError(err) } } impl core::fmt::Display for FromHexError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FromHexError::ParseIntError(e) => write!(f, "{}", e), FromHexError::HexFormatError(s) => write!( f, "{}, please use format '#fff', 'fff', '#ffffff' or 'ffffff'.", s ), FromHexError::RgbaHexFormatError(s) => write!( f, "{}, please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'.", s ), } } } #[cfg(feature = "std")] impl std::error::Error for FromHexError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { FromHexError::HexFormatError(_s) => None, FromHexError::RgbaHexFormatError(_s) => None, FromHexError::ParseIntError(e) => Some(e), } } } impl FromStr for Rgb { type Err = FromHexError; /// Parses a color hex code of format '#ff00bb' or '#abc' (with or without the leading '#') into a /// [`Rgb`] instance. fn from_str(hex: &str) -> Result { let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); match hex_code.len() { 3 => { let red = u8::from_str_radix(&hex_code[..1], 16)?; let green = u8::from_str_radix(&hex_code[1..2], 16)?; let blue = u8::from_str_radix(&hex_code[2..3], 16)?; let col: Rgb = Rgb::new(red * 17, green * 17, blue * 17); Ok(col) } 6 => { let red = u8::from_str_radix(&hex_code[..2], 16)?; let green = u8::from_str_radix(&hex_code[2..4], 16)?; let blue = u8::from_str_radix(&hex_code[4..6], 16)?; let col: Rgb = Rgb::new(red, green, blue); Ok(col) } _ => Err(FromHexError::HexFormatError("invalid hex code format")), } } } impl FromStr for Rgba { type Err = FromHexError; /// Parses a color hex code of format '#ff00bbff' or '#abcd' (with or without the leading '#') into a /// [`Rgba`] instance. fn from_str(hex: &str) -> Result { let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped); match hex_code.len() { 4 => { let red = u8::from_str_radix(&hex_code[..1], 16)?; let green = u8::from_str_radix(&hex_code[1..2], 16)?; let blue = u8::from_str_radix(&hex_code[2..3], 16)?; let alpha = u8::from_str_radix(&hex_code[3..4], 16)?; let col: Rgba = Rgba::new(red * 17, green * 17, blue * 17, alpha * 17); Ok(col) } 8 => { let red = u8::from_str_radix(&hex_code[..2], 16)?; let green = u8::from_str_radix(&hex_code[2..4], 16)?; let blue = u8::from_str_radix(&hex_code[4..6], 16)?; let alpha = u8::from_str_radix(&hex_code[6..8], 16)?; let col: Rgba = Rgba::new(red, green, blue, alpha); Ok(col) } _ => Err(FromHexError::RgbaHexFormatError("invalid hex code format")), } } } impl From> for Packed where O: ComponentOrder, P>, Rgba: From>, { #[inline] fn from(color: Rgb) -> Self { Self::from(Rgba::from(color)) } } impl From> for Packed where O: ComponentOrder, P>, { #[inline] fn from(color: Rgba) -> Self { Packed::pack(color) } } impl From> for Rgb where O: ComponentOrder, P>, { #[inline] fn from(packed: Packed) -> Self { Rgba::from(packed).color } } impl From> for Rgba where O: ComponentOrder, P>, { #[inline] fn from(packed: Packed) -> Self { packed.unpack() } } impl From for Rgb { #[inline] fn from(color: u32) -> Self { Self::from_u32::(color) } } impl From for Rgba { #[inline] fn from(color: u32) -> Self { Self::from_u32::(color) } } impl From> for u32 { #[inline] fn from(color: Rgb) -> Self { Rgb::into_u32::(color) } } impl From> for u32 { #[inline] fn from(color: Rgba) -> Self { Rgba::into_u32::(color) } } #[allow(deprecated)] impl crate::RelativeContrast for Rgb where T: Real + Arithmetics + PartialCmp, T::Mask: LazySelect, S: RgbStandard, Xyz<<::Space as RgbSpace>::WhitePoint, T>: FromColor, { type Scalar = T; #[inline] fn get_contrast_ratio(self, other: Self) -> T { let xyz1 = Xyz::from_color(self); let xyz2 = Xyz::from_color(other); crate::contrast_ratio(xyz1.y, xyz2.y) } } impl Wcag21RelativeContrast for Rgb where Self: IntoColor, T>>, S: RgbStandard, T: Real + Add + Div + PartialCmp + MinMax, { type Scalar = T; fn relative_luminance(self) -> Luma, Self::Scalar> { self.into_color() } } impl_rand_traits_cartesian!(UniformRgb, Rgb {red, green, blue} phantom: standard: PhantomData); #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Zeroable for Rgb where T: bytemuck::Zeroable {} #[cfg(feature = "bytemuck")] unsafe impl bytemuck::Pod for Rgb where T: bytemuck::Pod {} #[cfg(test)] mod test { use core::str::FromStr; use crate::encoding::Srgb; use crate::rgb::channels; use super::{Rgb, Rgba}; test_convert_into_from_xyz!(Rgb); #[test] fn ranges() { assert_ranges! { Rgb; clamped { red: 0.0 => 1.0, green: 0.0 => 1.0, blue: 0.0 => 1.0 } clamped_min {} unclamped {} } } raw_pixel_conversion_tests!(Rgb: red, green, blue); raw_pixel_conversion_fail_tests!(Rgb: red, green, blue); #[test] fn lower_hex() { assert_eq!( format!("{:x}", Rgb::::new(171, 193, 35)), "abc123" ); } #[test] fn lower_hex_small_numbers() { assert_eq!(format!("{:x}", Rgb::::new(1, 2, 3)), "010203"); assert_eq!( format!("{:x}", Rgb::::new(1, 2, 3)), "000100020003" ); assert_eq!( format!("{:x}", Rgb::::new(1, 2, 3)), "000000010000000200000003" ); assert_eq!( format!("{:x}", Rgb::::new(1, 2, 3)), "000000000000000100000000000000020000000000000003" ); } #[test] fn lower_hex_custom_width() { assert_eq!( format!("{:03x}", Rgb::::new(1, 2, 3)), "001002003" ); assert_eq!( format!("{:03x}", Rgb::::new(1, 2, 3)), "001002003" ); assert_eq!( format!("{:03x}", Rgb::::new(1, 2, 3)), "001002003" ); assert_eq!( format!("{:03x}", Rgb::::new(1, 2, 3)), "001002003" ); } #[test] fn upper_hex() { assert_eq!( format!("{:X}", Rgb::::new(171, 193, 35)), "ABC123" ); } #[test] fn upper_hex_small_numbers() { assert_eq!(format!("{:X}", Rgb::::new(1, 2, 3)), "010203"); assert_eq!( format!("{:X}", Rgb::::new(1, 2, 3)), "000100020003" ); assert_eq!( format!("{:X}", Rgb::::new(1, 2, 3)), "000000010000000200000003" ); assert_eq!( format!("{:X}", Rgb::::new(1, 2, 3)), "000000000000000100000000000000020000000000000003" ); } #[test] fn upper_hex_custom_width() { assert_eq!( format!("{:03X}", Rgb::::new(1, 2, 3)), "001002003" ); assert_eq!( format!("{:03X}", Rgb::::new(1, 2, 3)), "001002003" ); assert_eq!( format!("{:03X}", Rgb::::new(1, 2, 3)), "001002003" ); assert_eq!( format!("{:03X}", Rgb::::new(1, 2, 3)), "001002003" ); } #[test] fn rgb_hex_into_from() { let c1 = Rgb::::from_u32::(0x1100_7FFF); let c2 = Rgb::::new(0u8, 127, 255); assert_eq!(c1, c2); assert_eq!(Rgb::::into_u32::(c1), 0xFF00_7FFF); let c1 = Rgba::::from_u32::(0x007F_FF80); let c2 = Rgba::::new(0u8, 127, 255, 128); assert_eq!(c1, c2); assert_eq!( Rgba::::into_u32::(c1), 0x007F_FF80 ); assert_eq!( Rgb::::from(0x7FFF_FF80), Rgb::from((255u8, 255, 128)) ); assert_eq!( Rgba::::from(0x7FFF_FF80), Rgba::from((127u8, 255, 255, 128)) ); } #[cfg(feature = "serializing")] #[test] fn serialize() { let serialized = ::serde_json::to_string(&Rgb::::new(0.3, 0.8, 0.1)).unwrap(); assert_eq!(serialized, r#"{"red":0.3,"green":0.8,"blue":0.1}"#); } #[cfg(feature = "serializing")] #[test] fn deserialize() { let deserialized: Rgb = ::serde_json::from_str(r#"{"red":0.3,"green":0.8,"blue":0.1}"#).unwrap(); assert_eq!(deserialized, Rgb::::new(0.3, 0.8, 0.1)); } #[test] fn from_str() { let c = Rgb::::from_str("#ffffff"); assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(255, 255, 255)); let c = Rgb::::from_str("#gggggg"); assert!(c.is_err()); let c = Rgb::::from_str("#fff"); assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(255, 255, 255)); let c = Rgb::::from_str("#000000"); assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(0, 0, 0)); let c = Rgb::::from_str(""); assert!(c.is_err()); let c = Rgb::::from_str("#123456"); assert!(c.is_ok()); assert_eq!(c.unwrap(), Rgb::::new(18, 52, 86)); let c = Rgb::::from_str("#iii"); assert!(c.is_err()); assert_eq!( format!("{}", c.err().unwrap()), "invalid digit found in string" ); let c = Rgb::::from_str("#08f"); assert_eq!(c.unwrap(), Rgb::::new(0, 136, 255)); let c = Rgb::::from_str("08f"); assert_eq!(c.unwrap(), Rgb::::new(0, 136, 255)); let c = Rgb::::from_str("ffffff"); assert_eq!(c.unwrap(), Rgb::::new(255, 255, 255)); let c = Rgb::::from_str("#12"); assert!(c.is_err()); assert_eq!( format!("{}", c.err().unwrap()), "invalid hex code format, \ please use format \'#fff\', \'fff\', \'#ffffff\' or \'ffffff\'." ); let c = Rgb::::from_str("da0bce"); assert_eq!(c.unwrap(), Rgb::::new(218, 11, 206)); let c = Rgb::::from_str("f034e6"); assert_eq!(c.unwrap(), Rgb::::new(240, 52, 230)); let c = Rgb::::from_str("abc"); assert_eq!(c.unwrap(), Rgb::::new(170, 187, 204)); let c = Rgba::::from_str("#08ff"); assert_eq!(c.unwrap(), Rgba::::new(0, 136, 255, 255)); let c = Rgba::::from_str("08f0"); assert_eq!(c.unwrap(), Rgba::::new(0, 136, 255, 0)); let c = Rgba::::from_str("#da0bce80"); assert_eq!(c.unwrap(), Rgba::::new(218, 11, 206, 128)); let c = Rgba::::from_str("f034e680"); assert_eq!(c.unwrap(), Rgba::::new(240, 52, 230, 128)); let c = Rgba::::from_str("#ffffffff"); assert_eq!(c.unwrap(), Rgba::::new(255, 255, 255, 255)); let c = Rgba::::from_str("#ffff"); assert_eq!(c.unwrap(), Rgba::::new(255, 255, 255, 255)); let c = Rgba::::from_str("#gggggggg"); assert!(c.is_err()); assert_eq!( format!("{}", c.err().unwrap()), "invalid digit found in string" ); let c = Rgba::::from_str("#fff"); assert_eq!( format!("{}", c.err().unwrap()), "invalid hex code format, \ please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'." ); let c = Rgba::::from_str("#ffffff"); assert_eq!( format!("{}", c.err().unwrap()), "invalid hex code format, \ please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'." ); } #[test] fn check_min_max_components() { assert_eq!(Rgb::::min_red(), 0.0); assert_eq!(Rgb::::min_green(), 0.0); assert_eq!(Rgb::::min_blue(), 0.0); assert_eq!(Rgb::::max_red(), 1.0); assert_eq!(Rgb::::max_green(), 1.0); assert_eq!(Rgb::::max_blue(), 1.0); } struct_of_arrays_tests!( Rgb[red, green, blue] phantom: standard, Rgba::new(0.1f32, 0.2, 0.3, 0.4), Rgba::new(0.2, 0.3, 0.4, 0.5), Rgba::new(0.3, 0.4, 0.5, 0.6) ); test_uniform_distribution! { Rgb { red: (0.0, 1.0), green: (0.0, 1.0), blue: (0.0, 1.0) }, min: Rgb::new(0.0f32, 0.0, 0.0), max: Rgb::new(1.0, 1.0, 1.0) } } palette-0.7.5/src/rgb.rs000064400000000000000000000240271046102023000132070ustar 00000000000000//! Types for the RGB color space, including spaces and standards. //! //! # Linear And Non-linear RGB //! //! Colors in images are often "gamma corrected", or converted using some //! non-linear transfer function into a format like sRGB before being stored or //! displayed. This is done as a compression method and to prevent banding; it's //! also a bit of a legacy from the ages of the CRT monitors, where the output //! from the electron gun was non-linear. The problem is that these formats are //! *non-linear color spaces*, which means that many operations that you may //! want to perform on colors (addition, subtraction, multiplication, linear //! interpolation, etc.) will work unexpectedly when performed in such a //! non-linear color space. Thus, the compression has to be reverted to restore //! linearity and ensure that many operations on the colors behave as expected. //! //! But, even when colors *are* 'linear', there is yet more to explore. //! //! The most common way that colors are defined, especially for computer //! storage, is in terms of so-called *tristimulus values*, meaning that all //! colors can be represented as a vector of three values. //! The reason colors can generally be stored as only a three-dimensional //! vector, and not an *N*-dimensional one, where *N* is some number of possible //! wavelengths of light, is because our eyes contain only three types of cones. //! Each of these cones has its own sensitivity curve in response to the //! wavelengths of visible light, giving us three "dimensions" of sensitivity to color. //! These cones are often called the L, M, and S (for long, medium, and short) //! cones, and their sensitivity curves *roughly* position them as most //! sensitive to "red", "green", and "blue" parts of the spectrum. As such, we //! can choose only three values to represent any possible color that a human is //! able to see. An interesting consequence of this is that humans can see two //! different objects which are emitting *completely different actual light //! spectra* as the *exact same perceptual color* so long as those wavelengths, //! when transformed by the sensitivity curves of our cones, end up resulting in //! the same L, M, and S values sent to our brains. //! //! A **color space** (which simply refers to a set of standards by which we map //! a set of arbitrary values to real-world colors) which uses tristimulus //! values is often defined in terms of //! //! 1. Its **primaries** //! 2. Its **reference white** or **white point** //! //! The **primaries** together represent the total *gamut* (i.e. displayable //! range of colors) of that color space. The **white point** defines a //! concrete tristimulus value that corresponds to a real, physical white //! reflecting object being lit by a known light source and observed by the //! 'standard observer' (i.e. a standardized model of human color perception). //! //! The informal "RGB" color space is such a tristimulus color space, since it //! is defined by three values, but it is underspecified since we don't know //! which primaries are being used (i.e. how exactly are the canonical "red", //! "green", and "blue" defined?), nor its white point. In most cases, when //! people talk about "RGB" or "Linear RGB" colors, what they are *actually* //! talking about is the "Linear sRGB" color space, which uses the primaries and //! white point defined in the sRGB standard, but which *does not* have the //! (non-linear) sRGB *transfer function* applied. //! //! Palette takes these details into account and encodes them as type //! parameters, with sRGB as the default. The goal is to make it easy to use //! colors correctly and still allow advanced users a high degree of //! flexibility. use crate::{ encoding::{self, FromLinear, Gamma, IntoLinear, Linear}, stimulus::{FromStimulus, Stimulus}, white_point::Any, Mat3, Yxy, }; pub use self::rgb::{FromHexError, Iter, Rgb, Rgba}; pub mod channels; #[allow(clippy::module_inception)] mod rgb; /// Non-linear sRGB, the most common RGB input/output format. /// /// If you are looking for "just RGB", this is probably it. This type alias /// helps by locking the more generic [`Rgb`] type to the sRGB format. /// /// See [`Rgb`] for more details on how to create a value and use it. pub type Srgb = Rgb; /// Non-linear sRGB with an alpha component. /// /// This is a transparent version of [`Srgb`], which is commonly used as the /// input or output format. If you are looking for "just RGBA", this is probably /// it. /// /// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to /// create a value and use it. pub type Srgba = Rgba; /// Linear sRGB. /// /// You probably want [`Srgb`] if you are looking for an input or output format /// (or "just RGB"). This is the linear version of sRGB, which is what you would /// usually convert to before working with the color. /// /// See [`Rgb`] for more details on how to create a value and use it. #[doc(alias = "linear")] pub type LinSrgb = Rgb, T>; /// Linear sRGB with an alpha component. /// /// You probably want [`Srgba`] if you are looking for an input or output format /// (or "just RGB"). This is the linear version of sRGBA, which is what you /// would usually convert to before working with the color. /// /// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to /// create a value and use it. #[doc(alias = "linear")] pub type LinSrgba = Rgba, T>; /// Gamma 2.2 encoded sRGB. /// /// This is similar to [`Srgb`], but uses the exponent function as an /// approximation. It's a common trick to speed up conversion when accuracy can /// be sacrificed. It's still faster to use `Srgb` when also converting to and /// from `u8` at the same time. /// /// See [`Rgb`] for more details on how to create a value and use it. pub type GammaSrgb = Rgb, T>; /// Gamma 2.2 encoded sRGB with an alpha component. /// /// This is similar to [`Srgba`], but uses the exponent function as an /// approximation. It's a common trick to speed up conversion when accuracy can /// be sacrificed. It's still faster to use `Srgba` when also converting to and /// from `u8` at the same time. /// /// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to /// create a value and use it. pub type GammaSrgba = Rgba, T>; /// An RGB space and a transfer function. pub trait RgbStandard { /// The RGB color space. type Space: RgbSpace; /// The transfer function for the color components. type TransferFn; } impl RgbStandard for (Sp, Tf) where Sp: RgbSpace, { type Space = Sp; type TransferFn = Tf; } impl RgbStandard for (Pr, Wp, Tf) where (Pr, Wp): RgbSpace, { type Space = (Pr, Wp); type TransferFn = Tf; } /// A set of primaries and a white point. pub trait RgbSpace { /// The primaries of the RGB color space. type Primaries; /// The white point of the RGB color space. type WhitePoint; /// Get a pre-defined matrix for converting an RGB value with this standard /// into an XYZ value. /// /// Returning `None` (as in the default implementation) means that the /// matrix will be computed dynamically, which is significantly slower. #[inline(always)] fn rgb_to_xyz_matrix() -> Option> { None } /// Get a pre-defined matrix for converting an XYZ value into an RGB value /// with this standard. /// /// Returning `None` (as in the default implementation) means that the /// matrix will be computed dynamically, which is significantly slower. #[inline(always)] fn xyz_to_rgb_matrix() -> Option> { None } } impl RgbSpace for (P, W) { type Primaries = P; type WhitePoint = W; } /// Represents the red, green and blue primaries of an RGB space. pub trait Primaries { /// Primary red. fn red() -> Yxy; /// Primary green. fn green() -> Yxy; /// Primary blue. fn blue() -> Yxy; } impl From> for Srgb where crate::encoding::Srgb: RgbStandard + FromLinear, { #[inline] fn from(lin_srgb: LinSrgb) -> Self { lin_srgb.into_encoding() } } impl From> for LinSrgb where crate::encoding::Srgb: RgbStandard + IntoLinear, { #[inline] fn from(srgb: Srgb) -> Self { srgb.into_linear() } } impl From> for Srgba where U: Stimulus, crate::encoding::Srgb: RgbStandard + FromLinear, { #[inline] fn from(lin_srgb: LinSrgb) -> Self { let non_lin = Srgb::from_linear(lin_srgb); non_lin.into() } } impl From> for Srgba where U: FromStimulus, crate::encoding::Srgb: RgbStandard + FromLinear, { #[inline] fn from(lin_srgba: LinSrgba) -> Self { Srgba::from_linear(lin_srgba) } } impl From> for LinSrgba where U: Stimulus, crate::encoding::Srgb: RgbStandard + IntoLinear, { #[inline] fn from(srgb: Srgb) -> Self { srgb.into_linear().into() } } impl From> for LinSrgba where U: FromStimulus, crate::encoding::Srgb: RgbStandard + IntoLinear, { #[inline] fn from(srgba: Srgba) -> Self { srgba.into_linear() } } /// A packed representation of RGBA in RGBA order. pub type PackedRgba