├── .gitignore ├── .editorconfig ├── src ├── rand.rs ├── serde.rs └── lib.rs ├── rustfmt.toml ├── LICENSE-MIT ├── .github └── workflows │ └── ci.yml ├── Cargo.toml ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /target 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /src/rand.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::{Distribution, Standard}; 2 | use rand::Rng; 3 | 4 | use crate::HexColor; 5 | 6 | #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))] 7 | impl Distribution for Standard { 8 | #[inline] 9 | fn sample(&self, rng: &mut R) -> HexColor where R: ?Sized + Rng { 10 | let [r, g, b, _] = rng.next_u32().to_ne_bytes(); 11 | HexColor::rgb(r, g, b) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | condense_wildcard_suffixes = true 2 | force_multiline_blocks = true 3 | format_code_in_doc_comments = true 4 | format_macro_bodies = true 5 | format_macro_matchers = true 6 | format_strings = true 7 | group_imports = "StdExternalCrate" 8 | imports_granularity = "Module" 9 | imports_layout = "HorizontalVertical" 10 | newline_style = "Unix" 11 | normalize_comments = true 12 | normalize_doc_attributes = true 13 | overflow_delimited_expr = true 14 | reorder_impl_items = true 15 | unstable_features = true 16 | use_field_init_shorthand = true 17 | use_try_shorthand = true 18 | wrap_comments = true 19 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | toolchain: stable 13 | - uses: actions-rs/cargo@v1 14 | with: 15 | command: test 16 | args: --all-features 17 | clippy: 18 | runs-on: ubuntu-latest 19 | if: github.event_name != 'pull_request' 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | - uses: actions-rs/cargo@v1 26 | with: 27 | command: clippy 28 | args: --all-features --tests 29 | docs: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | toolchain: nightly 36 | - uses: actions-rs/cargo@v1 37 | with: 38 | toolchain: nightly 39 | command: rustdoc 40 | args: --all-features -- --cfg doc_cfg 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hex_color" 3 | version = "3.0.1" # update html_root_url in src/lib.rs and README.md 4 | 5 | authors = ["Sean C. Roach "] 6 | categories = ["data-structures", "graphics", "multimedia::images", "no-std"] 7 | description = "A simple, lightweight library for working with RGB(A) hexadecimal colors." 8 | edition = "2021" 9 | keywords = ["color", "hex", "rgb", "rgba"] 10 | license = "MIT OR Apache-2.0" 11 | readme = "README.md" 12 | repository = "https://github.com/seancroach/hex_color" 13 | 14 | [features] 15 | default = ["std"] 16 | 17 | rand = ["dep:rand"] 18 | serde = ["dep:serde", "dep:arrayvec"] 19 | std = ["rand?/std", "rand?/std_rng"] 20 | 21 | [dependencies] 22 | arrayvec = { version = "0.7", optional = true, default-features = false } 23 | rand = { version = "0.8", optional = true, default-features = false } 24 | serde = { version = "1.0", optional = true, default-features = false } 25 | 26 | [dev-dependencies] 27 | serde = { version = "1.0", features = ["derive"] } 28 | serde_json = "1.0" 29 | 30 | [package.metadata.docs.rs] 31 | all-features = true 32 | rustdoc-args = ["--cfg", "doc_cfg"] 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hex_color 2 | 3 | A simple, lightweight library for working with RGB(A) hexadecimal colors. 4 | 5 | [![Build Status]][actions] 6 | [![Latest Version]][crates.io] 7 | 8 | [Build Status]: https://img.shields.io/github/actions/workflow/status/seancroach/hex_color/ci.yml?logo=github 9 | [actions]: https://github.com/seancroach/hex_color/actions/workflows/ci.yml 10 | [Latest Version]: https://img.shields.io/crates/v/hex_color?logo=rust 11 | [crates.io]: https://crates.io/crates/hex_color 12 | 13 | ## Documentation 14 | 15 | [Module documentation with examples](https://docs.rs/hex_color). The module documentation also 16 | includes a comprehensive description of the syntax supported for parsing hex colors. 17 | 18 | ## Usage 19 | 20 | This crate is [on crates.io][crates] and can be used by adding `hex_color` 21 | to your dependencies in your project's `Cargo.toml`: 22 | 23 | ```toml 24 | [dependencies] 25 | hex_color = "3" 26 | ``` 27 | 28 | [crates]: https://crates.io/crates/hex_color 29 | 30 | ## Examples 31 | 32 | Basic parsing: 33 | 34 | ```rust 35 | use hex_color::HexColor; 36 | 37 | let cyan = HexColor::parse("#0FF")?; 38 | assert_eq!(cyan, HexColor::CYAN); 39 | 40 | let transparent_plum = HexColor::parse("#DDA0DD80")?; 41 | assert_eq!(transparent_plum, HexColor::rgba(221, 160, 221, 128)); 42 | 43 | // Strictly enforce only an RGB color through parse_rgb: 44 | let pink = HexColor::parse_rgb("#FFC0CB")?; 45 | assert_eq!(pink, HexColor::rgb(255, 192, 203)); 46 | 47 | // Strictly enforce an alpha component through parse_rgba: 48 | let opaque_white = HexColor::parse_rgba("#FFFF")?; 49 | assert_eq!(opaque_white, HexColor::WHITE); 50 | ``` 51 | 52 | Flexible constructors: 53 | 54 | ```rust 55 | use hex_color::HexColor; 56 | 57 | let violet = HexColor::rgb(238, 130, 238); 58 | let transparent_maroon= HexColor::rgba(128, 0, 0, 128); 59 | let transparent_gray = HexColor::GRAY.with_a(128); 60 | let lavender = HexColor::from_u24(0x00E6_E6FA); 61 | let transparent_lavender = HexColor::from_u32(0xE6E6_FA80); 62 | let floral_white = HexColor::WHITE 63 | .with_g(250) 64 | .with_b(240); 65 | ``` 66 | 67 | Comprehensive arithmetic: 68 | 69 | ```rust 70 | use hex_color::HexColor; 71 | 72 | assert_eq!(HexColor::BLUE + HexColor::RED, HexColor::MAGENTA); 73 | assert_eq!( 74 | HexColor::CYAN.saturating_add(HexColor::GRAY), 75 | HexColor::rgb(128, 255, 255), 76 | ); 77 | assert_eq!( 78 | HexColor::BLACK.wrapping_sub(HexColor::achromatic(1)), 79 | HexColor::WHITE, 80 | ); 81 | ``` 82 | 83 | ### With [`rand`] 84 | 85 | Using `rand` + `std` features to generate random colors via [`rand`][`rand`] 86 | out of the box: 87 | 88 | [`rand`]: https://docs.rs/rand 89 | 90 | ```rust 91 | use hex_color::HexColor; 92 | 93 | let random_rgb: HexColor = rand::random(); 94 | ``` 95 | 96 | To specify whether an RGB or RGBA color is randomly created, use 97 | `HexColor::random_rgb` or `HexColor::random_rgba` respectively: 98 | 99 | ```rust 100 | use hex_color::HexColor; 101 | 102 | let random_rgb = HexColor::random_rgb(); 103 | let random_rgba = HexColor::random_rgba(); 104 | ``` 105 | 106 | ### With [`serde`] 107 | 108 | Use [`serde`] to serialize and deserialize colors in multiple 109 | formats: `u24`, `u32`, `rgb`, or `rgba`: 110 | 111 | [`serde`]: https://docs.rs/serde 112 | 113 | ```rust 114 | use hex_color::HexColor; 115 | use serde::{Deserialize, Serialize}; 116 | use serde_json::json; 117 | 118 | #[derive(Debug, PartialEq, Deserialize, Serialize)] 119 | struct Color { 120 | name: String, 121 | value: HexColor, 122 | } 123 | 124 | let json_input = json!({ 125 | "name": "Light Coral", 126 | "value": "#F08080", 127 | }); 128 | assert_eq!( 129 | serde_json::from_value::(json_input)?, 130 | Color { 131 | name: String::from("Light Coral"), 132 | value: HexColor::rgb(240, 128, 128), 133 | }, 134 | ); 135 | 136 | let my_color = Color { 137 | name: String::from("Dark Salmon"), 138 | value: HexColor::rgb(233, 150, 122), 139 | }; 140 | assert_eq!( 141 | serde_json::to_value(my_color)?, 142 | json!({ 143 | "name": "Dark Salmon", 144 | "value": "#E9967A", 145 | }), 146 | ); 147 | 148 | #[derive(Debug, PartialEq, Deserialize, Serialize)] 149 | struct NumericColor { 150 | name: String, 151 | #[serde(with = "hex_color::u24")] 152 | value: HexColor, 153 | } 154 | 155 | let json_input = json!({ 156 | "name": "Light Coral", 157 | "value": 0x00F0_8080_u32, 158 | }); 159 | assert_eq!( 160 | serde_json::from_value::(json_input)?, 161 | NumericColor { 162 | name: String::from("Light Coral"), 163 | value: HexColor::rgb(240, 128, 128), 164 | }, 165 | ); 166 | 167 | let my_color = NumericColor { 168 | name: String::from("Dark Salmon"), 169 | value: HexColor::rgb(233, 150, 122), 170 | }; 171 | assert_eq!( 172 | serde_json::to_value(my_color)?, 173 | json!({ 174 | "name": "Dark Salmon", 175 | "value": 0x00E9_967A_u32, 176 | }), 177 | ); 178 | ``` 179 | 180 | ## Features 181 | 182 | * `rand` enables out-of-the-box compatability with the [`rand`] 183 | crate. 184 | * `serde` enables serialization and deserialization with the 185 | [`serde`] crate. 186 | * `std` enables `std::error::Error` on `ParseHexColorError`. Otherwise, 187 | it's needed with `rand` for `HexColor::random_rgb`, `HexColor::random_rgba`, 188 | and, of course, `rand::random`. 189 | 190 | *Note*: Only the `std` feature is enabled by default. 191 | 192 | ## License 193 | 194 | Licensed under either of 195 | 196 | - Apache License, Version 2.0 197 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 198 | - MIT license 199 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 200 | 201 | at your option. 202 | 203 | ## Contribution 204 | 205 | Unless you explicitly state otherwise, any contribution intentionally submitted 206 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 207 | dual licensed as above, without any additional terms or conditions. 208 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | // All the casts are validated and the "error" sections for all of these 2 | // functions is equally too nebulous while also self-evident. 3 | #![allow( 4 | clippy::cast_possible_truncation, 5 | clippy::cast_sign_loss, 6 | clippy::missing_errors_doc 7 | )] 8 | 9 | use core::fmt::{self, Write}; 10 | 11 | use arrayvec::ArrayString; 12 | use serde::de::{Error, Unexpected, Visitor}; 13 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 14 | 15 | use crate::{HexColor, ParseMode}; 16 | 17 | impl HexColor { 18 | fn to_rgb_string(self) -> ArrayString<7> { 19 | let mut string = ArrayString::new(); 20 | unsafe { write!(string, "{}", self.display_rgb()).unwrap_unchecked() }; 21 | string 22 | } 23 | 24 | fn to_rgba_string(self) -> ArrayString<9> { 25 | let mut string = ArrayString::new(); 26 | unsafe { write!(string, "{}", self.display_rgba()).unwrap_unchecked() }; 27 | string 28 | } 29 | } 30 | 31 | struct HexColorStringVisitor { 32 | mode: ParseMode, 33 | } 34 | 35 | impl HexColorStringVisitor { 36 | fn new(mode: ParseMode) -> Self { 37 | HexColorStringVisitor { mode } 38 | } 39 | } 40 | 41 | impl<'de> Visitor<'de> for HexColorStringVisitor { 42 | type Value = HexColor; 43 | 44 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 45 | let message = match self.mode { 46 | ParseMode::Any => "an RGB(A) hexadecimal color", 47 | ParseMode::Rgb => "an RGB hexadecimal color", 48 | ParseMode::Rgba => "an RGBA hexadecimal color", 49 | }; 50 | formatter.write_str(message) 51 | } 52 | 53 | fn visit_str(self, s: &str) -> Result 54 | where 55 | E: Error, 56 | { 57 | HexColor::parse_internals(s, self.mode) 58 | .map_err(|_| E::invalid_value(Unexpected::Str(s), &self)) 59 | } 60 | } 61 | 62 | enum NumberMode { 63 | U24, 64 | U32, 65 | } 66 | 67 | struct HexColorNumberVisitor { 68 | mode: NumberMode, 69 | } 70 | 71 | impl HexColorNumberVisitor { 72 | fn new(mode: NumberMode) -> Self { 73 | HexColorNumberVisitor { mode } 74 | } 75 | } 76 | 77 | impl<'de> Visitor<'de> for HexColorNumberVisitor { 78 | type Value = HexColor; 79 | 80 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 81 | let message = match self.mode { 82 | NumberMode::U24 => "a value in the range 0x0000_0000..=0x00FF_FFFF", 83 | NumberMode::U32 => "a value in the range 0x0000_0000..=0xFFFF_FFFF", 84 | }; 85 | formatter.write_str(message) 86 | } 87 | 88 | fn visit_i64(self, n: i64) -> Result 89 | where 90 | E: Error, 91 | { 92 | if n < 0x0000_0000 { 93 | return Err(E::invalid_type(Unexpected::Signed(n), &self)); 94 | } 95 | 96 | match self.mode { 97 | NumberMode::U24 if n <= 0x00FF_FFFF => Ok(HexColor::from_u24(n as u32)), 98 | NumberMode::U32 if n <= 0xFFFF_FFFF => Ok(HexColor::from_u32(n as u32)), 99 | _ => Err(E::invalid_value(Unexpected::Unsigned(n as u64), &self)), 100 | } 101 | } 102 | 103 | fn visit_u64(self, n: u64) -> Result 104 | where 105 | E: Error, 106 | { 107 | match self.mode { 108 | NumberMode::U24 if n <= 0x00FF_FFFF => Ok(HexColor::from_u24(n as u32)), 109 | NumberMode::U32 if n <= 0xFFFF_FFFF => Ok(HexColor::from_u32(n as u32)), 110 | _ => Err(E::invalid_value(Unexpected::Unsigned(n), &self)), 111 | } 112 | } 113 | } 114 | 115 | /// Deserialize and serialize [`HexColor`] values as RGB strings. 116 | /// 117 | /// # Examples 118 | /// 119 | /// ``` 120 | /// use hex_color::HexColor; 121 | /// use serde::{Deserialize, Serialize}; 122 | /// use serde_json::json; 123 | /// 124 | /// #[derive(Debug, PartialEq, Deserialize, Serialize)] 125 | /// struct Color { 126 | /// name: String, 127 | /// #[serde(with = "hex_color::rgb")] 128 | /// value: HexColor, 129 | /// } 130 | /// 131 | /// # fn main() -> serde_json::Result<()> { 132 | /// let dodger_blue = json!({ 133 | /// "name": "Dodger Blue", 134 | /// "value": "#1E90FF", 135 | /// }); 136 | /// assert_eq!( 137 | /// serde_json::from_value::(dodger_blue)?, 138 | /// Color { 139 | /// name: String::from("Dodger Blue"), 140 | /// value: HexColor::rgb(30, 144, 255), 141 | /// }, 142 | /// ); 143 | /// 144 | /// let tomato = Color { 145 | /// name: String::from("Tomato"), 146 | /// value: HexColor::rgb(255, 99, 71), 147 | /// }; 148 | /// assert_eq!( 149 | /// serde_json::to_value(tomato)?, 150 | /// json!({ 151 | /// "name": "Tomato", 152 | /// "value": "#FF6347", 153 | /// }), 154 | /// ); 155 | /// # Ok(()) 156 | /// # } 157 | /// ``` 158 | #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] 159 | pub mod rgb { 160 | use serde::{Deserializer, Serializer}; 161 | 162 | use super::HexColorStringVisitor; 163 | use crate::{HexColor, ParseMode}; 164 | 165 | /// Deserializes a [`HexColor`] from a string using the same rules as 166 | /// [`HexColor::parse_rgb`]. 167 | /// 168 | /// To strictly deserialize an RGBA value from a string, use 169 | /// [`rgba::deserialize`]. 170 | /// 171 | /// [`rgba::deserialize`]: crate::rgba::deserialize 172 | /// 173 | /// # Examples 174 | /// 175 | /// ``` 176 | /// use hex_color::HexColor; 177 | /// use serde::Deserialize; 178 | /// use serde_json::json; 179 | /// 180 | /// #[derive(Debug, PartialEq, Deserialize)] 181 | /// struct Color { 182 | /// name: String, 183 | /// #[serde(deserialize_with = "hex_color::rgb::deserialize")] 184 | /// value: HexColor, 185 | /// } 186 | /// 187 | /// # fn main() -> serde_json::Result<()> { 188 | /// let cadet_blue = json!({ 189 | /// "name": "Cadet Blue", 190 | /// "value": "#5F9EA0", 191 | /// }); 192 | /// assert_eq!( 193 | /// serde_json::from_value::(cadet_blue)?, 194 | /// Color { 195 | /// name: String::from("Cadet Blue"), 196 | /// value: HexColor::rgb(95, 158, 160), 197 | /// }, 198 | /// ); 199 | /// # Ok(()) 200 | /// # } 201 | /// ``` 202 | #[inline] 203 | pub fn deserialize<'de, D>(deserializer: D) -> Result 204 | where 205 | D: Deserializer<'de>, 206 | { 207 | deserializer.deserialize_str(HexColorStringVisitor::new(ParseMode::Rgb)) 208 | } 209 | 210 | /// Serializes a [`HexColor`] as a string in the format `#RRGGBB`. 211 | /// 212 | /// To serialize a [`HexColor`] as a string in the format `#RRGGBBAA`, use 213 | /// [`rgba::serialize`] 214 | /// 215 | /// [`rgba::serialize`]: crate::rgba::serialize 216 | /// 217 | /// # Examples 218 | /// 219 | /// ``` 220 | /// use hex_color::HexColor; 221 | /// use serde::Serialize; 222 | /// use serde_json::json; 223 | /// 224 | /// #[derive(Serialize)] 225 | /// struct Color { 226 | /// name: &'static str, 227 | /// #[serde(serialize_with = "hex_color::rgb::serialize")] 228 | /// value: HexColor, 229 | /// } 230 | /// 231 | /// # fn main() -> Result<(), serde_json::Error> { 232 | /// let mint_cream = Color { 233 | /// name: "Mint Cream", 234 | /// value: HexColor::rgb(245,255,250), 235 | /// }; 236 | /// assert_eq!( 237 | /// serde_json::to_value(mint_cream)?, 238 | /// json!({ 239 | /// "name": "Mint Cream", 240 | /// "value": "#F5FFFA", 241 | /// }), 242 | /// ); 243 | /// # Ok(()) 244 | /// # } 245 | /// ``` 246 | #[inline] 247 | pub fn serialize(color: &HexColor, s: S) -> Result 248 | where 249 | S: Serializer, 250 | { 251 | s.serialize_str(&color.to_rgb_string()) 252 | } 253 | } 254 | 255 | /// Deserialize and serialize [`HexColor`] values as RGBA strings. 256 | /// 257 | /// # Examples 258 | /// 259 | /// ``` 260 | /// use hex_color::HexColor; 261 | /// use serde::{Deserialize, Serialize}; 262 | /// use serde_json::json; 263 | /// 264 | /// #[derive(Debug, PartialEq, Deserialize, Serialize)] 265 | /// struct Color { 266 | /// name: String, 267 | /// #[serde(with = "hex_color::rgba")] 268 | /// value: HexColor, 269 | /// } 270 | /// 271 | /// # fn main() -> serde_json::Result<()> { 272 | /// let transparent_ivory = json!({ 273 | /// "name": "Transparent Ivory", 274 | /// "value": "#FFFFF080", 275 | /// }); 276 | /// assert_eq!( 277 | /// serde_json::from_value::(transparent_ivory)?, 278 | /// Color { 279 | /// name: String::from("Transparent Ivory"), 280 | /// value: HexColor::rgba(255, 255, 240, 128), 281 | /// }, 282 | /// ); 283 | /// 284 | /// let medium_purple = Color { 285 | /// name: String::from("Medium Purple"), 286 | /// value: HexColor::rgb(147, 112, 219), 287 | /// }; 288 | /// assert_eq!( 289 | /// serde_json::to_value(medium_purple)?, 290 | /// json!({ 291 | /// "name": "Medium Purple", 292 | /// "value": "#9370DBFF", 293 | /// }), 294 | /// ); 295 | /// # Ok(()) 296 | /// # } 297 | /// ``` 298 | #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] 299 | pub mod rgba { 300 | use serde::{Deserializer, Serializer}; 301 | 302 | use super::HexColorStringVisitor; 303 | use crate::{HexColor, ParseMode}; 304 | 305 | /// Deserializes a [`HexColor`] from a string using the same rules as 306 | /// [`HexColor::parse_rgba`]. 307 | /// 308 | /// To strictly deserialize an RGB value from a string, use 309 | /// [`rgb::deserialize`]. 310 | /// 311 | /// [`rgb::deserialize`]: crate::rgb::deserialize 312 | /// 313 | /// # Examples 314 | /// 315 | /// ``` 316 | /// use hex_color::HexColor; 317 | /// use serde::Deserialize; 318 | /// use serde_json::json; 319 | /// 320 | /// #[derive(Debug, PartialEq, Deserialize)] 321 | /// struct Color { 322 | /// name: String, 323 | /// #[serde(deserialize_with = "hex_color::rgba::deserialize")] 324 | /// value: HexColor, 325 | /// } 326 | /// 327 | /// # fn main() -> serde_json::Result<()> { 328 | /// let lavender = json!({ 329 | /// "name": "Lavender", 330 | /// "value": "#E6E6FAFF", 331 | /// }); 332 | /// assert_eq!( 333 | /// serde_json::from_value::(lavender)?, 334 | /// Color { 335 | /// name: String::from("Lavender"), 336 | /// value: HexColor::rgb(230, 230, 250), 337 | /// }, 338 | /// ); 339 | /// # Ok(()) 340 | /// # } 341 | /// ``` 342 | #[inline] 343 | pub fn deserialize<'de, D>(deserializer: D) -> Result 344 | where 345 | D: Deserializer<'de>, 346 | { 347 | deserializer.deserialize_str(HexColorStringVisitor::new(ParseMode::Rgba)) 348 | } 349 | 350 | /// Serializes a [`HexColor`] as a string in the format `#RRGGBBAA`. 351 | /// 352 | /// To serialize a [`HexColor`] as a string in the format `#RRGGBB`, use 353 | /// [`rgb::serialize`] 354 | /// 355 | /// [`rgb::serialize`]: crate::rgb::serialize 356 | /// 357 | /// # Examples 358 | /// 359 | /// ``` 360 | /// use hex_color::HexColor; 361 | /// use serde::Serialize; 362 | /// use serde_json::json; 363 | /// 364 | /// #[derive(Serialize)] 365 | /// struct Color { 366 | /// name: &'static str, 367 | /// #[serde(serialize_with = "hex_color::rgba::serialize")] 368 | /// value: HexColor, 369 | /// } 370 | /// 371 | /// # fn main() -> Result<(), serde_json::Error> { 372 | /// let transparent_bisque = Color { 373 | /// name: "Transparent Bisque", 374 | /// value: HexColor::rgba(255, 228, 196, 128), 375 | /// }; 376 | /// assert_eq!( 377 | /// serde_json::to_value(transparent_bisque)?, 378 | /// json!({ 379 | /// "name": "Transparent Bisque", 380 | /// "value": "#FFE4C480", 381 | /// }), 382 | /// ); 383 | /// # Ok(()) 384 | /// # } 385 | /// ``` 386 | #[inline] 387 | pub fn serialize(color: &HexColor, s: S) -> Result 388 | where 389 | S: Serializer, 390 | { 391 | s.serialize_str(&color.to_rgba_string()) 392 | } 393 | } 394 | 395 | /// Deserialize and serialize [`HexColor`] values as `u32` values by truncating 396 | /// the alpha byte. 397 | /// 398 | /// # Examples 399 | /// 400 | /// ``` 401 | /// use hex_color::HexColor; 402 | /// use serde::{Deserialize, Serialize}; 403 | /// use serde_json::json; 404 | /// 405 | /// #[derive(Debug, PartialEq, Deserialize, Serialize)] 406 | /// struct Color { 407 | /// name: String, 408 | /// #[serde(with = "hex_color::u24")] 409 | /// value: HexColor, 410 | /// } 411 | /// 412 | /// # fn main() -> serde_json::Result<()> { 413 | /// let light_sky_blue = json!({ 414 | /// "name": "Light Sky Blue", 415 | /// "value": 8_900_346_u32, 416 | /// }); 417 | /// assert_eq!( 418 | /// serde_json::from_value::(light_sky_blue)?, 419 | /// Color { 420 | /// name: String::from("Light Sky Blue"), 421 | /// value: HexColor::from_u24(0x0087_CEFA), 422 | /// }, 423 | /// ); 424 | /// 425 | /// let pale_violet_red = Color { 426 | /// name: String::from("Pale Violet Red"), 427 | /// value: HexColor::from_u24(0x00DB_7093), 428 | /// }; 429 | /// assert_eq!( 430 | /// serde_json::to_value(pale_violet_red)?, 431 | /// json!({ 432 | /// "name": "Pale Violet Red", 433 | /// "value": 14_381_203_u32, 434 | /// }), 435 | /// ); 436 | /// # Ok(()) 437 | /// # } 438 | /// ``` 439 | #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] 440 | pub mod u24 { 441 | use serde::{Deserializer, Serializer}; 442 | 443 | use super::{HexColorNumberVisitor, NumberMode}; 444 | use crate::HexColor; 445 | 446 | /// Deserializes a [`HexColor`] from a `u32` in the range 447 | /// `0x0000_0000..=0x00FF_FFFF`. 448 | /// 449 | /// To deserialize a [`HexColor`] from a `u32` with the alpha component 450 | /// included, use [`u32::deserialize`] 451 | /// 452 | /// [`u32::deserialize`]: crate::u32::deserialize 453 | /// 454 | /// # Examples 455 | /// 456 | /// ``` 457 | /// use hex_color::HexColor; 458 | /// use serde::Deserialize; 459 | /// use serde_json::json; 460 | /// 461 | /// #[derive(Debug, PartialEq, Deserialize)] 462 | /// struct Color { 463 | /// name: String, 464 | /// #[serde(deserialize_with = "hex_color::u24::deserialize")] 465 | /// value: HexColor, 466 | /// } 467 | /// 468 | /// # fn main() -> serde_json::Result<()> { 469 | /// let crimson = json!({ 470 | /// "name": "Crimson", 471 | /// "value": 14_423_100_u32, 472 | /// }); 473 | /// assert_eq!( 474 | /// serde_json::from_value::(crimson)?, 475 | /// Color { 476 | /// name: String::from("Crimson"), 477 | /// value: HexColor::from_u24(0x00DC_143C), 478 | /// }, 479 | /// ); 480 | /// # Ok(()) 481 | /// # } 482 | /// ``` 483 | #[inline] 484 | pub fn deserialize<'de, D>(deserializer: D) -> Result 485 | where 486 | D: Deserializer<'de>, 487 | { 488 | deserializer.deserialize_u64(HexColorNumberVisitor::new(NumberMode::U24)) 489 | } 490 | 491 | /// Serializes a [`HexColor`] as a `u32` in the range 492 | /// `0x0000_0000..=0x00FF_FFFF`, truncating the alpha component. 493 | /// 494 | /// To serialize a [`HexColor`] as a `u32` with the alpha component 495 | /// included, use [`u32::serialize`]. 496 | /// 497 | /// [`u32::serialize`]: crate::u32::serialize 498 | /// 499 | /// # Examples 500 | /// 501 | /// ``` 502 | /// use hex_color::HexColor; 503 | /// use serde::Serialize; 504 | /// use serde_json::json; 505 | /// 506 | /// #[derive(Serialize)] 507 | /// struct Color { 508 | /// name: &'static str, 509 | /// #[serde(serialize_with = "hex_color::u24::serialize")] 510 | /// value: HexColor, 511 | /// } 512 | /// 513 | /// # fn main() -> Result<(), serde_json::Error> { 514 | /// let cornsilk = Color { 515 | /// name: "Cornsilk", 516 | /// value: HexColor::from_u24(0x00FF_F8DC), 517 | /// }; 518 | /// assert_eq!( 519 | /// serde_json::to_value(cornsilk)?, 520 | /// json!({ 521 | /// "name": "Cornsilk", 522 | /// "value": 16_775_388_u32, 523 | /// }), 524 | /// ); 525 | /// # Ok(()) 526 | /// # } 527 | /// ``` 528 | #[inline] 529 | pub fn serialize(color: &HexColor, s: S) -> Result 530 | where 531 | S: Serializer, 532 | { 533 | s.serialize_u32(color.to_u24()) 534 | } 535 | } 536 | 537 | /// Deserialize and serialize [`HexColor`] values as `u32` values. 538 | /// 539 | /// # Examples 540 | /// 541 | /// ``` 542 | /// use hex_color::HexColor; 543 | /// use serde::{Deserialize, Serialize}; 544 | /// use serde_json::json; 545 | /// 546 | /// #[derive(Debug, PartialEq, Deserialize, Serialize)] 547 | /// struct Color { 548 | /// name: String, 549 | /// #[serde(with = "hex_color::u32")] 550 | /// value: HexColor, 551 | /// } 552 | /// 553 | /// # fn main() -> serde_json::Result<()> { 554 | /// let sea_shell = json!({ 555 | /// "name": "Sea Shell", 556 | /// "value": 4_294_307_583_u32, 557 | /// }); 558 | /// assert_eq!( 559 | /// serde_json::from_value::(sea_shell)?, 560 | /// Color { 561 | /// name: String::from("Sea Shell"), 562 | /// value: HexColor::from_u32(0xFFF5_EEFF), 563 | /// }, 564 | /// ); 565 | /// 566 | /// let spring_green = Color { 567 | /// name: String::from("Spring Green"), 568 | /// value: HexColor::from_u32(0x00FF_7FFF), 569 | /// }; 570 | /// assert_eq!( 571 | /// serde_json::to_value(spring_green)?, 572 | /// json!({ 573 | /// "name": "Spring Green", 574 | /// "value": 16_744_447_u32, 575 | /// }), 576 | /// ); 577 | /// # Ok(()) 578 | /// # } 579 | /// ``` 580 | #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] 581 | pub mod u32 { 582 | use serde::{Deserializer, Serializer}; 583 | 584 | use super::{HexColorNumberVisitor, NumberMode}; 585 | use crate::HexColor; 586 | 587 | /// Deserializes a [`HexColor`] from a `u32`. 588 | /// 589 | /// To deserialize a value in the range `0x0000_0000..=0x00FF_FFFF` as only 590 | /// an RGB color, use [`u24::deserialize`] instead. 591 | /// 592 | /// [`u24::deserialize`]: crate::u24::deserialize 593 | /// 594 | /// # Examples 595 | /// 596 | /// ``` 597 | /// use hex_color::HexColor; 598 | /// use serde::Deserialize; 599 | /// use serde_json::json; 600 | /// 601 | /// #[derive(Debug, PartialEq, Deserialize)] 602 | /// struct Color { 603 | /// name: String, 604 | /// #[serde(deserialize_with = "hex_color::u32::deserialize")] 605 | /// value: HexColor, 606 | /// } 607 | /// 608 | /// # fn main() -> serde_json::Result<()> { 609 | /// let transparent_moccasin = json!({ 610 | /// "name": "Transparent Moccasin", 611 | /// "value": 4_293_178_752_u32, 612 | /// }); 613 | /// assert_eq!( 614 | /// serde_json::from_value::(transparent_moccasin)?, 615 | /// Color { 616 | /// name: String::from("Transparent Moccasin"), 617 | /// value: HexColor::from_u32(0xFFE4_B580), 618 | /// }, 619 | /// ); 620 | /// # Ok(()) 621 | /// # } 622 | /// ``` 623 | #[inline] 624 | pub fn deserialize<'de, D>(deserializer: D) -> Result 625 | where 626 | D: Deserializer<'de>, 627 | { 628 | deserializer.deserialize_u64(HexColorNumberVisitor::new(NumberMode::U32)) 629 | } 630 | 631 | /// Serializes a [`HexColor`] as a `u32`. 632 | /// 633 | /// To serialize only the red, green, and blue components, use 634 | /// [`u24::serialize`] instead. 635 | /// 636 | /// [`u24::serialize`]: crate::u24::serialize 637 | /// 638 | /// # Examples 639 | /// 640 | /// ``` 641 | /// use hex_color::HexColor; 642 | /// use serde::Serialize; 643 | /// use serde_json::json; 644 | /// 645 | /// #[derive(Serialize)] 646 | /// struct Color { 647 | /// name: &'static str, 648 | /// #[serde(serialize_with = "hex_color::u32::serialize")] 649 | /// value: HexColor, 650 | /// } 651 | /// 652 | /// # fn main() -> Result<(), serde_json::Error> { 653 | /// let linen = Color { 654 | /// name: "Linen", 655 | /// value: HexColor::from_u32(0xFAF0_E6FF), 656 | /// }; 657 | /// assert_eq!( 658 | /// serde_json::to_value(linen)?, 659 | /// json!({ 660 | /// "name": "Linen", 661 | /// "value": 4_210_091_775_u32, 662 | /// }), 663 | /// ); 664 | /// # Ok(()) 665 | /// # } 666 | /// ``` 667 | #[inline] 668 | pub fn serialize(color: &HexColor, s: S) -> Result 669 | where 670 | S: Serializer, 671 | { 672 | s.serialize_u32(color.to_u32()) 673 | } 674 | } 675 | 676 | #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] 677 | impl<'de> Deserialize<'de> for HexColor { 678 | /// Deserializes a `HexColor` from a string using the same rules as 679 | /// [`HexColor::parse`]. 680 | /// 681 | /// To strictly deserialize either an RGB or RGBA value from a string, use 682 | /// [`rgb::deserialize`] or [`rgba::deserialize`] respectively. 683 | /// 684 | /// # Examples 685 | /// 686 | /// ``` 687 | /// use hex_color::HexColor; 688 | /// use serde::Deserialize; 689 | /// use serde_json::json; 690 | /// 691 | /// #[derive(Debug, PartialEq, Deserialize)] 692 | /// struct Color { 693 | /// name: String, 694 | /// value: HexColor, 695 | /// } 696 | /// 697 | /// # fn main() -> Result<(), serde_json::Error> { 698 | /// let saddle_brown = json!({ 699 | /// "name": "Saddle Brown", 700 | /// "value": "#8B4513", 701 | /// }); 702 | /// assert_eq!( 703 | /// serde_json::from_value::(saddle_brown)?, 704 | /// Color { 705 | /// name: String::from("Saddle Brown"), 706 | /// value: HexColor::rgb(139, 69, 19), 707 | /// }, 708 | /// ); 709 | /// 710 | /// let transparent_cyan = json!({ 711 | /// "name": "Transparent Cyan", 712 | /// "value": "#00FFFF80", 713 | /// }); 714 | /// assert_eq!( 715 | /// serde_json::from_value::(transparent_cyan)?, 716 | /// Color { 717 | /// name: String::from("Transparent Cyan"), 718 | /// value: HexColor::rgba(0, 255, 255, 128), 719 | /// }, 720 | /// ); 721 | /// # Ok(()) 722 | /// # } 723 | /// ``` 724 | fn deserialize(deserializer: D) -> Result 725 | where 726 | D: Deserializer<'de>, 727 | { 728 | deserializer.deserialize_str(HexColorStringVisitor::new(ParseMode::Any)) 729 | } 730 | } 731 | 732 | #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] 733 | impl Serialize for HexColor { 734 | /// Serializes the `HexColor` as a string. 735 | /// 736 | /// By default, `HexColor` values get serialized in the form of `#RRGGBB` 737 | /// strings when the alpha value is set to [`u8::MAX`], or completely 738 | /// opaque. However, if any transparency exists, they are serialized in 739 | /// the form of `#RRGGBBAA` strings. 740 | /// 741 | /// To strictly enforce getting serialized as an RGB or RGBA string, use 742 | /// [`rgb::serialize`] or [`rgba::serialize`] respectively. In fact, using 743 | /// either of these options is highly suggested for more normalized results. 744 | /// 745 | /// # Examples 746 | /// 747 | /// ``` 748 | /// use hex_color::HexColor; 749 | /// use serde::Serialize; 750 | /// use serde_json::json; 751 | /// 752 | /// #[derive(Serialize)] 753 | /// struct Color { 754 | /// name: &'static str, 755 | /// value: HexColor, 756 | /// } 757 | /// 758 | /// # fn main() -> Result<(), serde_json::Error> { 759 | /// let orange = Color { 760 | /// name: "Orange", 761 | /// value: HexColor::rgb(255, 165, 0), 762 | /// }; 763 | /// assert_eq!( 764 | /// serde_json::to_value(orange)?, 765 | /// json!({ 766 | /// "name": "Orange", 767 | /// "value": "#FFA500", 768 | /// }), 769 | /// ); 770 | /// 771 | /// let transparent_navy = Color { 772 | /// name: "Transparent Navy", 773 | /// value: HexColor::rgba(0, 0, 128, 128), 774 | /// }; 775 | /// assert_eq!( 776 | /// serde_json::to_value(transparent_navy)?, 777 | /// json!({ 778 | /// "name": "Transparent Navy", 779 | /// "value": "#00008080", 780 | /// }), 781 | /// ); 782 | /// # Ok(()) 783 | /// # } 784 | /// ``` 785 | fn serialize(&self, serializer: S) -> Result 786 | where 787 | S: Serializer, 788 | { 789 | if self.a == u8::MAX { 790 | serializer.serialize_str(&self.to_rgb_string()) 791 | } else { 792 | serializer.serialize_str(&self.to_rgba_string()) 793 | } 794 | } 795 | } 796 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A simple, lightweight library for working with RGB(A) hexadecimal colors. 2 | //! 3 | //! # Usage 4 | //! 5 | //! This crate is [on crates.io][crates] and can be used by adding `hex_color` 6 | //! to your dependencies in your project's `Cargo.toml`: 7 | //! 8 | //! ```toml 9 | //! [dependencies] 10 | //! hex_color = "3" 11 | //! ``` 12 | //! 13 | //! [crates]: https://crates.io/crates/hex_color 14 | //! 15 | //! # Examples 16 | //! 17 | //! Basic parsing: 18 | //! 19 | //! ``` 20 | //! use hex_color::HexColor; 21 | //! 22 | //! # fn main() -> Result<(), hex_color::ParseHexColorError> { 23 | //! let cyan = HexColor::parse("#0FF")?; 24 | //! assert_eq!(cyan, HexColor::CYAN); 25 | //! 26 | //! let transparent_plum = HexColor::parse("#DDA0DD80")?; 27 | //! assert_eq!(transparent_plum, HexColor::rgba(221, 160, 221, 128)); 28 | //! 29 | //! // Strictly enforce only an RGB color through parse_rgb: 30 | //! let pink = HexColor::parse_rgb("#FFC0CB")?; 31 | //! assert_eq!(pink, HexColor::rgb(255, 192, 203)); 32 | //! 33 | //! // Strictly enforce an alpha component through parse_rgba: 34 | //! let opaque_white = HexColor::parse_rgba("#FFFF")?; 35 | //! assert_eq!(opaque_white, HexColor::WHITE); 36 | //! # Ok(()) 37 | //! # } 38 | //! ``` 39 | //! 40 | //! Flexible constructors: 41 | //! 42 | //! ``` 43 | //! use hex_color::HexColor; 44 | //! 45 | //! let violet = HexColor::rgb(238, 130, 238); 46 | //! let transparent_maroon = HexColor::rgba(128, 0, 0, 128); 47 | //! let transparent_gray = HexColor::GRAY.with_a(128); 48 | //! let lavender = HexColor::from_u24(0x00E6_E6FA); 49 | //! let transparent_lavender = HexColor::from_u32(0xE6E6_FA80); 50 | //! let floral_white = HexColor::WHITE.with_g(250).with_b(240); 51 | //! ``` 52 | //! 53 | //! Comprehensive arithmetic: 54 | //! 55 | //! ``` 56 | //! use hex_color::HexColor; 57 | //! 58 | //! assert_eq!(HexColor::BLUE + HexColor::RED, HexColor::MAGENTA); 59 | //! assert_eq!( 60 | //! HexColor::CYAN.saturating_add(HexColor::GRAY), 61 | //! HexColor::rgb(128, 255, 255), 62 | //! ); 63 | //! assert_eq!( 64 | //! HexColor::BLACK.wrapping_sub(HexColor::achromatic(1)), 65 | //! HexColor::WHITE, 66 | //! ); 67 | //! ``` 68 | //! 69 | //! ## With [`rand`](::rand) 70 | //! 71 | //! Using `rand` + `std` features to generate random colors via [`rand`](::rand) 72 | //! out of the box: 73 | //! 74 | //! ``` 75 | //! use hex_color::HexColor; 76 | //! 77 | //! let random_rgb: HexColor = rand::random(); 78 | //! ``` 79 | //! 80 | //! To specify whether an RGB or RGBA color is randomly created, use 81 | //! [`HexColor::random_rgb`] or [`HexColor::random_rgba`] respectively: 82 | //! 83 | //! ``` 84 | //! use hex_color::HexColor; 85 | //! 86 | //! let random_rgb = HexColor::random_rgb(); 87 | //! let random_rgba = HexColor::random_rgba(); 88 | //! ``` 89 | //! 90 | //! ## With [`serde`](::serde) 91 | //! 92 | //! Use [`serde`](::serde) to serialize and deserialize colors in multiple 93 | //! formats: [`u24`], [`mod@u32`], [`rgb`], or [`rgba`]: 94 | //! 95 | //! ``` 96 | //! use hex_color::HexColor; 97 | //! use serde::{Deserialize, Serialize}; 98 | //! use serde_json::json; 99 | //! 100 | //! #[derive(Debug, PartialEq, Deserialize, Serialize)] 101 | //! struct Color { 102 | //! name: String, 103 | //! value: HexColor, 104 | //! } 105 | //! 106 | //! # fn main() -> serde_json::Result<()> { 107 | //! let json_input = json!({ 108 | //! "name": "Light Coral", 109 | //! "value": "#F08080", 110 | //! }); 111 | //! assert_eq!( 112 | //! serde_json::from_value::(json_input)?, 113 | //! Color { 114 | //! name: String::from("Light Coral"), 115 | //! value: HexColor::rgb(240, 128, 128), 116 | //! }, 117 | //! ); 118 | //! 119 | //! let my_color = Color { 120 | //! name: String::from("Dark Salmon"), 121 | //! value: HexColor::rgb(233, 150, 122), 122 | //! }; 123 | //! assert_eq!( 124 | //! serde_json::to_value(my_color)?, 125 | //! json!({ 126 | //! "name": "Dark Salmon", 127 | //! "value": "#E9967A", 128 | //! }), 129 | //! ); 130 | //! 131 | //! #[derive(Debug, PartialEq, Deserialize, Serialize)] 132 | //! struct NumericColor { 133 | //! name: String, 134 | //! #[serde(with = "hex_color::u24")] 135 | //! value: HexColor, 136 | //! } 137 | //! 138 | //! let json_input = json!({ 139 | //! "name": "Light Coral", 140 | //! "value": 0x00F0_8080_u32, 141 | //! }); 142 | //! assert_eq!( 143 | //! serde_json::from_value::(json_input)?, 144 | //! NumericColor { 145 | //! name: String::from("Light Coral"), 146 | //! value: HexColor::rgb(240, 128, 128), 147 | //! }, 148 | //! ); 149 | //! 150 | //! let my_color = NumericColor { 151 | //! name: String::from("Dark Salmon"), 152 | //! value: HexColor::rgb(233, 150, 122), 153 | //! }; 154 | //! assert_eq!( 155 | //! serde_json::to_value(my_color)?, 156 | //! json!({ 157 | //! "name": "Dark Salmon", 158 | //! "value": 0x00E9_967A_u32, 159 | //! }), 160 | //! ); 161 | //! # Ok(()) 162 | //! # } 163 | //! ``` 164 | //! 165 | //! # Features 166 | //! 167 | //! * `rand` enables out-of-the-box compatability with the [`rand`](::rand) 168 | //! crate. 169 | //! * `serde` enables serialization and deserialization with the 170 | //! [`serde`](::serde) crate. 171 | //! * `std` enables [`std::error::Error`] on [`ParseHexColorError`]. Otherwise, 172 | //! it's needed with `rand` for [`HexColor::random_rgb`], 173 | //! [`HexColor::random_rgba`], and, of course, 174 | //! [`rand::random`](::rand::random). 175 | //! 176 | //! *Note*: Only the `std` feature is enabled by default. 177 | 178 | #![cfg_attr(not(feature = "std"), no_std)] 179 | // hex_color types in rustdoc of other crates get linked to here 180 | #![doc(html_root_url = "https://docs.rs/hex_color/3.0.0")] 181 | #![cfg_attr(doc_cfg, feature(doc_cfg))] 182 | #![deny(missing_docs)] 183 | #![deny(clippy::pedantic)] 184 | // This is a necessary evil for "r", "g", "b", "a", and more: 185 | #![allow(clippy::many_single_char_names, clippy::similar_names)] 186 | 187 | #[cfg(feature = "rand")] 188 | mod rand; 189 | #[cfg(feature = "serde")] 190 | mod serde; 191 | 192 | use core::fmt; 193 | use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; 194 | use core::str::{Bytes, FromStr}; 195 | 196 | #[cfg(feature = "serde")] 197 | #[doc(inline)] 198 | pub use self::serde::{rgb, rgba, u24, u32}; 199 | 200 | /// An RGBA color. 201 | /// 202 | /// See the [module documentation](self) for details. 203 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 204 | pub struct HexColor { 205 | /// The red component of the color. 206 | pub r: u8, 207 | /// The green component of the color. 208 | pub g: u8, 209 | /// The blue component of the color. 210 | pub b: u8, 211 | /// The alpha component of the color (`0` is transparent, `255` is opaque). 212 | pub a: u8, 213 | } 214 | 215 | impl HexColor { 216 | /// Solid black. RGBA is `(0, 0, 0, 255)`. 217 | pub const BLACK: HexColor = HexColor::rgb(0, 0, 0); 218 | /// Solid blue. RGBA is `(0, 0, 255, 255)`. 219 | pub const BLUE: HexColor = HexColor::rgb(0, 0, 255); 220 | /// Completely transparent. RGBA is `(0, 0, 0, 0)`. 221 | pub const CLEAR: HexColor = HexColor::rgba(0, 0, 0, 0); 222 | /// Solid cyan. RGBA is `(0, 255, 255, 255)`. 223 | pub const CYAN: HexColor = HexColor::rgb(0, 255, 255); 224 | /// Solid gray; American spelling of grey. RGBA is `(128, 128, 128, 255)`. 225 | pub const GRAY: HexColor = HexColor::achromatic(128); 226 | /// Solid green. RGBA is `(0, 0, 255, 255)`. 227 | pub const GREEN: HexColor = HexColor::rgb(0, 255, 0); 228 | /// Solid grey; British spelling of gray. RGBA is `(128, 128, 128, 255)`. 229 | pub const GREY: HexColor = HexColor::achromatic(128); 230 | /// Solid magenta. RGBA is `(255, 0, 255, 255)`. 231 | pub const MAGENTA: HexColor = HexColor::rgb(255, 0, 255); 232 | /// The maximum possible value. RGBA is `(255, 255, 255, 255)`. 233 | pub const MAX: HexColor = HexColor::from_u32(0xFFFF_FFFF); 234 | /// The minimum possible value. RGBA is `(0, 0, 0, 0)`. 235 | pub const MIN: HexColor = HexColor::from_u32(0x0000_0000); 236 | /// Solid red. RGBA is `(255, 0, 0, 255)`. 237 | pub const RED: HexColor = HexColor::rgb(255, 0, 0); 238 | /// Solid white. RGBA is `(255, 255, 255, 255)`. 239 | pub const WHITE: HexColor = HexColor::rgb(255, 255, 255); 240 | /// Solid yellow. RGBA is `(255, 255, 0, 255)`. 241 | pub const YELLOW: HexColor = HexColor::rgb(255, 255, 0); 242 | 243 | //////////////////////////////////////////////////////////////////////////// 244 | // Constructors 245 | //////////////////////////////////////////////////////////////////////////// 246 | 247 | /// Constructs a new RGBA value. 248 | /// 249 | /// For creating just an RGB value instead, use [`HexColor::rgb`]. 250 | /// 251 | /// # Examples 252 | /// 253 | /// ``` 254 | /// use hex_color::HexColor; 255 | /// 256 | /// let red = HexColor::rgba(255, 0, 0, 255); 257 | /// let translucent_red = HexColor::rgba(255, 0, 0, 128); 258 | /// ``` 259 | #[must_use] 260 | #[inline] 261 | pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> HexColor { 262 | HexColor { r, g, b, a } 263 | } 264 | 265 | /// Constructs a new RGB value. (The alpha channel defaults to [`u8::MAX`].) 266 | /// 267 | /// For creating an RGBA value instead, use [`HexColor::rgba`]. 268 | /// 269 | /// # Examples 270 | /// 271 | /// ``` 272 | /// use hex_color::HexColor; 273 | /// 274 | /// let aqua = HexColor::rgb(0, 255, 255); 275 | /// ``` 276 | #[must_use] 277 | #[inline] 278 | pub const fn rgb(r: u8, g: u8, b: u8) -> HexColor { 279 | HexColor { r, g, b, a: 255 } 280 | } 281 | 282 | /// Constructs a new achromatic RGB value. (The alpha channel defaults to 283 | /// [`u8::MAX`].) 284 | /// 285 | /// # Examples 286 | /// 287 | /// ``` 288 | /// use hex_color::HexColor; 289 | /// 290 | /// assert_eq!(HexColor::achromatic(128), HexColor::rgb(128, 128, 128)); 291 | /// ``` 292 | /// 293 | /// *Note*: There is no "`achromatic_alpha`" constructor or similar method. 294 | /// Instead, it's advised to chain [`HexColor::achromatic`] with 295 | /// [`HexColor::with_a`]: 296 | /// 297 | /// ``` 298 | /// use hex_color::HexColor; 299 | /// 300 | /// let transparent_dark_gray = HexColor::achromatic(64).with_a(128); 301 | /// assert_eq!(transparent_dark_gray, HexColor::rgba(64, 64, 64, 128)); 302 | /// ``` 303 | #[must_use] 304 | #[inline] 305 | pub const fn achromatic(v: u8) -> HexColor { 306 | HexColor::rgb(v, v, v) 307 | } 308 | 309 | //////////////////////////////////////////////////////////////////////////// 310 | // Random color generation 311 | //////////////////////////////////////////////////////////////////////////// 312 | 313 | /// Constructs a new random RGB value through the [`rand`](::rand) crate. 314 | /// 315 | /// To generate a random RGBA value, use [`HexColor::random_rgba`] instead. 316 | /// 317 | /// # Examples 318 | /// 319 | /// ``` 320 | /// use hex_color::HexColor; 321 | /// 322 | /// println!("{}", HexColor::random_rgb().display_rgb()); 323 | /// ``` 324 | #[cfg(all(feature = "rand", feature = "std"))] 325 | #[cfg_attr(doc_cfg, doc(cfg(all(feature = "rand", feature = "std"))))] 326 | #[must_use] 327 | #[inline] 328 | pub fn random_rgb() -> Self { 329 | ::rand::random() 330 | } 331 | 332 | /// Constructs a new random RGBA value through the [`rand`](::rand) crate. 333 | /// 334 | /// To generate a random RGB value, use [`HexColor::random_rgb`] instead. 335 | /// 336 | /// # Examples 337 | /// 338 | /// ``` 339 | /// use hex_color::HexColor; 340 | /// 341 | /// println!("{}", HexColor::random_rgba().display_rgba()); 342 | /// ``` 343 | #[cfg(all(feature = "rand", feature = "std"))] 344 | #[cfg_attr(doc_cfg, doc(cfg(all(feature = "rand", feature = "std"))))] 345 | #[must_use] 346 | #[inline] 347 | pub fn random_rgba() -> Self { 348 | HexColor::random_rgb().with_a(::rand::random()) 349 | } 350 | 351 | //////////////////////////////////////////////////////////////////////////// 352 | // Parsing 353 | //////////////////////////////////////////////////////////////////////////// 354 | 355 | #[must_use] 356 | unsafe fn parse_shorthand(bytes: &mut Bytes, has_alpha: bool) -> Option { 357 | unsafe fn parse_single_hex_value(bytes: &mut Bytes) -> Option { 358 | match bytes.next().unwrap_unchecked() { 359 | b'0' => Some(0x00), 360 | b'1' => Some(0x11), 361 | b'2' => Some(0x22), 362 | b'3' => Some(0x33), 363 | b'4' => Some(0x44), 364 | b'5' => Some(0x55), 365 | b'6' => Some(0x66), 366 | b'7' => Some(0x77), 367 | b'8' => Some(0x88), 368 | b'9' => Some(0x99), 369 | b'a' | b'A' => Some(0xAA), 370 | b'b' | b'B' => Some(0xBB), 371 | b'c' | b'C' => Some(0xCC), 372 | b'd' | b'D' => Some(0xDD), 373 | b'e' | b'E' => Some(0xEE), 374 | b'f' | b'F' => Some(0xFF), 375 | _ => None, 376 | } 377 | } 378 | 379 | let r = parse_single_hex_value(bytes)?; 380 | let g = parse_single_hex_value(bytes)?; 381 | let b = parse_single_hex_value(bytes)?; 382 | let a = if has_alpha { 383 | parse_single_hex_value(bytes)? 384 | } else { 385 | u8::MAX 386 | }; 387 | Some(HexColor::rgba(r, g, b, a)) 388 | } 389 | 390 | #[must_use] 391 | unsafe fn parse_full(bytes: &mut Bytes, has_alpha: bool) -> Option { 392 | const HEX_RADIX: u32 = 16; 393 | 394 | unsafe fn parse_double_hex_value(bytes: &mut Bytes) -> Option { 395 | let buf = [ 396 | bytes.next().unwrap_unchecked(), 397 | bytes.next().unwrap_unchecked(), 398 | ]; 399 | let s = core::str::from_utf8_unchecked(&buf); 400 | u8::from_str_radix(s, HEX_RADIX).ok() 401 | } 402 | 403 | let r = parse_double_hex_value(bytes)?; 404 | let g = parse_double_hex_value(bytes)?; 405 | let b = parse_double_hex_value(bytes)?; 406 | let a = if has_alpha { 407 | parse_double_hex_value(bytes)? 408 | } else { 409 | u8::MAX 410 | }; 411 | Some(HexColor::rgba(r, g, b, a)) 412 | } 413 | 414 | fn parse_internals(s: &str, mode: ParseMode) -> Result { 415 | macro_rules! err { 416 | ($variant:ident) => {{ 417 | return Err(ParseHexColorError::$variant); 418 | }}; 419 | } 420 | 421 | let mut bytes = s.bytes(); 422 | match bytes.next() { 423 | Some(b'#') => {} 424 | Some(_) => err!(InvalidFormat), 425 | None => err!(Empty), 426 | } 427 | let has_alpha = matches!(s.len(), 5 | 9); 428 | let opt = match (s.len(), mode) { 429 | (4, ParseMode::Rgb | ParseMode::Any) | (5, ParseMode::Rgba | ParseMode::Any) => { 430 | // SAFETY: `bytes` will have either `3` or `4` bytes left and 431 | // `has_alpha` is synchronized. 432 | unsafe { HexColor::parse_shorthand(&mut bytes, has_alpha) } 433 | } 434 | (7, ParseMode::Rgb | ParseMode::Any) | (9, ParseMode::Rgba | ParseMode::Any) => { 435 | // SAFETY: `bytes` will have either `6` or `8` bytes left and 436 | // `has_alpha` is synchronized. 437 | unsafe { HexColor::parse_full(&mut bytes, has_alpha) } 438 | } 439 | _ => err!(InvalidFormat), 440 | }; 441 | opt.ok_or(ParseHexColorError::InvalidDigit) 442 | } 443 | 444 | /// Parses an RGB(A) hex code. 445 | /// 446 | /// **All parsing is case-insensitive**. There are currently four parseable 447 | /// formats: 448 | /// 449 | /// * `#RGB` 450 | /// * `#RRGGBB` 451 | /// * `#RGBA` 452 | /// * `#RRGGBBAA` 453 | /// 454 | /// To parse *only* a hexadecimal triplet, use [`parse_rgb`]. Otherwise, 455 | /// to parse *only* a hexadecimal quadruplet, use [`parse_rgba`]. 456 | /// 457 | /// [`parse_rgb`]: HexColor::parse_rgb 458 | /// [`parse_rgba`]: HexColor::parse_rgba 459 | /// 460 | /// # Errors 461 | /// 462 | /// - [`Empty`] when the input is empty. 463 | /// - [`InvalidFormat`] when the input is a malformed length or lacks a 464 | /// leading `#`. If you suspect there might be whitespace in the input, 465 | /// consider calling [`str::trim`] first. 466 | /// - [`InvalidDigit`] when the format seems correct but one of the 467 | /// characters is an invalid hexadecimal digit. 468 | /// 469 | /// [`Empty`]: ParseHexColorError::Empty 470 | /// [`InvalidFormat`]: ParseHexColorError::InvalidFormat 471 | /// [`InvalidDigit`]: ParseHexColorError::InvalidDigit 472 | /// 473 | /// # Examples 474 | /// 475 | /// ``` 476 | /// use hex_color::HexColor; 477 | /// 478 | /// # fn main() -> Result<(), hex_color::ParseHexColorError> { 479 | /// let red = HexColor::parse("#F00")?; 480 | /// assert_eq!(red, HexColor::rgb(0xFF, 0x00, 0x00)); 481 | /// 482 | /// let plum = HexColor::parse("#DDA0DD")?; 483 | /// assert_eq!(plum, HexColor::rgb(0xDD, 0xA0, 0xDD)); 484 | /// 485 | /// let opaque_cyan = HexColor::parse("#0FFF")?; 486 | /// assert_eq!(opaque_cyan, HexColor::rgba(0x00, 0xFF, 0xFF, 0xFF)); 487 | /// 488 | /// let translucent_azure = HexColor::parse("#F0FFFF80")?; 489 | /// assert_eq!(translucent_azure, HexColor::rgba(0xF0, 0xFF, 0xFF, 0x80)); 490 | /// # Ok(()) 491 | /// # } 492 | /// ``` 493 | #[inline] 494 | pub fn parse(s: &str) -> Result { 495 | HexColor::parse_internals(s, ParseMode::Any) 496 | } 497 | 498 | /// Parses an RGB hex code. 499 | /// 500 | /// **All parsing is case-insensitive**. There are currently two parseable 501 | /// formats: 502 | /// 503 | /// * `#RGB` 504 | /// * `#RRGGBB` 505 | /// 506 | /// To parse *only* a hexadecimal quadruplet, use [`parse_rgba`]. Otherwise, 507 | /// to parse *both* hexadecimal triplets and quadruplets, use [`parse`]. 508 | /// 509 | /// [`parse_rgba`]: HexColor::parse_rgba 510 | /// [`parse`]: HexColor::parse 511 | /// 512 | /// # Errors 513 | /// 514 | /// - [`Empty`] when the input is empty. 515 | /// - [`InvalidFormat`] when the input is a malformed length or lacks a 516 | /// leading `#`. If you suspect there might be whitespace in the input, 517 | /// consider calling [`str::trim`] first. 518 | /// - [`InvalidDigit`] when the format seems correct but one of the 519 | /// characters is an invalid hexadecimal digit. 520 | /// 521 | /// *Note*: a valid RGBA input will return an [`InvalidFormat`]. Use 522 | /// [`parse_rgba`] or [`parse`] instead if that behavior is not desired. 523 | /// 524 | /// [`Empty`]: ParseHexColorError::Empty 525 | /// [`InvalidFormat`]: ParseHexColorError::InvalidFormat 526 | /// [`InvalidDigit`]: ParseHexColorError::InvalidDigit 527 | /// 528 | /// # Examples 529 | /// 530 | /// ``` 531 | /// use hex_color::HexColor; 532 | /// 533 | /// # fn main() -> Result<(), hex_color::ParseHexColorError> { 534 | /// let yellow = HexColor::parse_rgb("#FF0")?; 535 | /// assert_eq!(yellow, HexColor::rgb(0xFF, 0xFF, 0x00)); 536 | /// 537 | /// let hot_pink = HexColor::parse_rgb("#FF69B4")?; 538 | /// assert_eq!(hot_pink, HexColor::rgb(0xFF, 0x69, 0xB4)); 539 | /// # Ok(()) 540 | /// # } 541 | /// ``` 542 | #[inline] 543 | pub fn parse_rgb(s: &str) -> Result { 544 | HexColor::parse_internals(s, ParseMode::Rgb) 545 | } 546 | 547 | /// Parses an RGBA hex code. 548 | /// 549 | /// **All parsing is case-insensitive**. There are currently two parseable 550 | /// formats: 551 | /// 552 | /// * `#RGBA` 553 | /// * `#RRGGBBAA` 554 | /// 555 | /// To parse *only* a hexadecimal triplet, use [`parse_rgb`]. Otherwise, 556 | /// to parse *both* hexadecimal triplets and quadruplets, use [`parse`]. 557 | /// 558 | /// [`parse_rgb`]: HexColor::parse_rgb 559 | /// [`parse`]: HexColor::parse 560 | /// 561 | /// # Errors 562 | /// 563 | /// - [`Empty`] when the input is empty. 564 | /// - [`InvalidFormat`] when the input is a malformed length or lacks a 565 | /// leading `#`. If you suspect there might be whitespace in the input, 566 | /// consider calling [`str::trim`] first. 567 | /// - [`InvalidDigit`] when the format seems correct but one of the 568 | /// characters is an invalid hexadecimal digit. 569 | /// 570 | /// **Note**: a valid RGB input (without an alpha value) will return an 571 | /// [`InvalidFormat`]. Use [`parse_rgb`] or [`parse`] instead if that 572 | /// behavior is not desired. 573 | /// 574 | /// [`Empty`]: ParseHexColorError::Empty 575 | /// [`InvalidFormat`]: ParseHexColorError::InvalidFormat 576 | /// [`InvalidDigit`]: ParseHexColorError::InvalidDigit 577 | /// 578 | /// # Examples 579 | /// 580 | /// ``` 581 | /// use hex_color::HexColor; 582 | /// 583 | /// # fn main() -> Result<(), hex_color::ParseHexColorError> { 584 | /// let transparent = HexColor::parse_rgba("#FFFF")?; 585 | /// assert_eq!(transparent, HexColor::rgba(0xFF, 0xFF, 0xFF, 0xFF)); 586 | /// 587 | /// let translucent_gold = HexColor::parse_rgba("#FFD70080")?; 588 | /// assert_eq!(translucent_gold, HexColor::rgba(0xFF, 0xD7, 0x00, 0x80)); 589 | /// # Ok(()) 590 | /// # } 591 | /// ``` 592 | #[inline] 593 | pub fn parse_rgba(s: &str) -> Result { 594 | HexColor::parse_internals(s, ParseMode::Rgba) 595 | } 596 | 597 | //////////////////////////////////////////////////////////////////////////// 598 | // Other Conversions 599 | //////////////////////////////////////////////////////////////////////////// 600 | 601 | /// Converts any `u32` in the range `0x0000_0000..=0x00FF_FFFF` to an RGB 602 | /// value. 603 | /// 604 | /// To convert any `u32` to an RGBA value, use [`from_u32`] instead. 605 | /// 606 | /// [`from_u32`]: HexColor::from_u32 607 | /// 608 | /// # Panics 609 | /// 610 | /// Panics if `debug_assertions` are enabled and the given value exceeds 611 | /// `0x00FF_FFFF`. 612 | /// 613 | /// # Examples 614 | /// 615 | /// ``` 616 | /// use hex_color::HexColor; 617 | /// 618 | /// let pale_green = HexColor::from_u24(0x98FB98); 619 | /// assert_eq!(pale_green, HexColor::rgb(0x98, 0xFB, 0x98)); 620 | /// ``` 621 | #[must_use] 622 | #[inline] 623 | #[track_caller] 624 | pub const fn from_u24(n: u32) -> HexColor { 625 | debug_assert!(n <= 0x00FF_FFFF); 626 | 627 | let [_, r, g, b] = n.to_be_bytes(); 628 | HexColor::rgb(r, g, b) 629 | } 630 | 631 | /// Converts any `u32` to an RGBA value. 632 | /// 633 | /// For converting a `u32` in the range of `0x0000_0000..=0x00FF_FFFF` to 634 | /// an RGB value, use [`from_u24`] instead. 635 | /// 636 | /// [`from_u24`]: HexColor::from_u24 637 | /// 638 | /// # Examples 639 | /// 640 | /// ``` 641 | /// use hex_color::HexColor; 642 | /// 643 | /// let navajo_white = HexColor::from_u32(0xFFDEADFF); 644 | /// assert_eq!(navajo_white, HexColor::rgba(0xFF, 0xDE, 0xAD, 0xFF)); 645 | /// 646 | /// let translucent_violet = HexColor::from_u32(0xEE82EE80); 647 | /// assert_eq!(translucent_violet, HexColor::rgba(0xEE, 0x82, 0xEE, 0x80)); 648 | /// ``` 649 | #[must_use] 650 | #[inline] 651 | pub const fn from_u32(v: u32) -> HexColor { 652 | let [r, g, b, a] = v.to_be_bytes(); 653 | HexColor::rgba(r, g, b, a) 654 | } 655 | 656 | /// Converts a `HexColor` into a `u32` in the range `0x000000..=0xFFFFFF`, 657 | /// discarding any possibly significant alpha value. 658 | /// 659 | /// To convert this `HexColor` into a `u32` containing the alpha value as 660 | /// well, use [`to_u32`]. 661 | /// 662 | /// [`to_u32`]: HexColor::to_u32 663 | /// 664 | /// # Examples 665 | /// 666 | /// ``` 667 | /// use hex_color::HexColor; 668 | /// 669 | /// let misty_rose = HexColor::rgb(0xFF, 0xE4, 0xE1); 670 | /// assert_eq!(misty_rose.to_u24(), 0xFFE4E1); 671 | /// 672 | /// // Again, note that the alpha value is lost in this conversion: 673 | /// let translucent_navy = HexColor::rgba(0x00, 0x00, 0x80, 0x80); 674 | /// assert_eq!(translucent_navy.to_u24(), 0x000080); 675 | /// ``` 676 | #[inline] 677 | #[must_use] 678 | pub const fn to_u24(self) -> u32 { 679 | let (r, g, b) = self.split_rgb(); 680 | u32::from_be_bytes([0x00, r, g, b]) 681 | } 682 | 683 | /// Converts a `HexColor` into a `u32`. 684 | /// 685 | /// To convert the `HexColor` into a `u32` with only the red, green, and 686 | /// blue channels packed, use [`to_u24`]. 687 | /// 688 | /// [`to_u24`]: HexColor::to_u24 689 | /// 690 | /// # Examples 691 | /// 692 | /// ``` 693 | /// use hex_color::HexColor; 694 | /// 695 | /// let sea_shell = HexColor::rgb(0xFF, 0xF5, 0xEE); 696 | /// assert_eq!(sea_shell.to_u32(), 0xFFF5EEFF); 697 | /// 698 | /// // Unlike `to_u24` the alpha value is preserved 699 | /// let translucent_navy = HexColor::rgba(0x00, 0x00, 0x80, 0x80); 700 | /// assert_eq!(translucent_navy.to_u32(), 0x00008080); 701 | /// ``` 702 | #[must_use] 703 | #[inline] 704 | pub const fn to_u32(self) -> u32 { 705 | let (r, g, b, a) = self.split_rgba(); 706 | u32::from_be_bytes([r, g, b, a]) 707 | } 708 | 709 | /// Converts a `HexColor` into `[r, g, b, a]`. 710 | /// 711 | /// # Examples 712 | /// 713 | /// ``` 714 | /// use hex_color::HexColor; 715 | /// 716 | /// let color = HexColor::from_u32(0x89AB_CDEF); 717 | /// assert_eq!(color.to_be_bytes(), [0x89, 0xAB, 0xCD, 0xEF]); 718 | /// ``` 719 | #[must_use] 720 | #[inline] 721 | pub const fn to_be_bytes(self) -> [u8; 4] { 722 | [self.r, self.g, self.b, self.a] 723 | } 724 | 725 | /// Converts a `HexColor` into `[a, b, g, r]`. 726 | /// 727 | /// # Examples 728 | /// 729 | /// ``` 730 | /// use hex_color::HexColor; 731 | /// 732 | /// let color = HexColor::from_u32(0x89AB_CDEF); 733 | /// assert_eq!(color.to_le_bytes(), [0xEF, 0xCD, 0xAB, 0x89]); 734 | /// ``` 735 | #[must_use] 736 | #[inline] 737 | pub const fn to_le_bytes(self) -> [u8; 4] { 738 | [self.a, self.b, self.g, self.r] 739 | } 740 | 741 | //////////////////////////////////////////////////////////////////////////// 742 | // Utility methods 743 | //////////////////////////////////////////////////////////////////////////// 744 | 745 | /// Constructs a new `HexColor` derived from `self` with the red component 746 | /// of `r`. 747 | /// 748 | /// # Examples 749 | /// 750 | /// ``` 751 | /// use hex_color::HexColor; 752 | /// 753 | /// assert_eq!(HexColor::MIN.with_r(255), HexColor::rgba(255, 0, 0, 0)); 754 | /// ``` 755 | #[must_use] 756 | #[inline] 757 | pub const fn with_r(self, r: u8) -> HexColor { 758 | let (_, g, b, a) = self.split_rgba(); 759 | HexColor::rgba(r, g, b, a) 760 | } 761 | 762 | /// Constructs a new `HexColor` derived from `self` with the green component 763 | /// of `g`. 764 | /// 765 | /// # Examples 766 | /// 767 | /// ``` 768 | /// use hex_color::HexColor; 769 | /// 770 | /// assert_eq!(HexColor::MIN.with_g(255), HexColor::rgba(0, 255, 0, 0)); 771 | /// ``` 772 | #[must_use] 773 | #[inline] 774 | pub const fn with_g(self, g: u8) -> HexColor { 775 | let (r, _, b, a) = self.split_rgba(); 776 | HexColor::rgba(r, g, b, a) 777 | } 778 | 779 | /// Constructs a new `HexColor` derived from `self` with the blue component 780 | /// of `b`. 781 | /// 782 | /// # Examples 783 | /// 784 | /// ``` 785 | /// use hex_color::HexColor; 786 | /// 787 | /// assert_eq!(HexColor::MIN.with_b(255), HexColor::rgba(0, 0, 255, 0)); 788 | /// ``` 789 | #[must_use] 790 | #[inline] 791 | pub const fn with_b(self, b: u8) -> HexColor { 792 | let (r, g, _, a) = self.split_rgba(); 793 | HexColor::rgba(r, g, b, a) 794 | } 795 | 796 | /// Constructs a new `HexColor` derived from `self` with the alpha component 797 | /// of `a`. 798 | /// 799 | /// # Examples 800 | /// 801 | /// ``` 802 | /// use hex_color::HexColor; 803 | /// 804 | /// assert_eq!(HexColor::MIN.with_a(255), HexColor::rgba(0, 0, 0, 255)); 805 | /// ``` 806 | #[must_use] 807 | #[inline] 808 | pub const fn with_a(self, a: u8) -> HexColor { 809 | let (r, g, b) = self.split_rgb(); 810 | HexColor::rgba(r, g, b, a) 811 | } 812 | 813 | /// Deconstructs a `HexColor` into a tuple of its components: `(r, g, b, 814 | /// a)`. 815 | /// 816 | /// This primarily helps in cleaner deconstruction of `HexColor` instances, 817 | /// especially if the variable bindings aren't the same as the `struct`'s 818 | /// fields. 819 | /// 820 | /// # Examples 821 | /// 822 | /// Basic usage: 823 | /// 824 | /// ``` 825 | /// use hex_color::HexColor; 826 | /// 827 | /// let slate_blue = HexColor::from_u24(0x6A5ACD); 828 | /// let (red, green, blue, alpha) = slate_blue.split_rgba(); 829 | /// ``` 830 | /// 831 | /// For contrast, here's what it would look like otherwise; it's not 832 | /// terrible, but en masse, it's subjectively annoying: 833 | /// 834 | /// ``` 835 | /// use hex_color::HexColor; 836 | /// 837 | /// let slate_blue = HexColor::from_u24(0x6A5ACD); 838 | /// let HexColor { 839 | /// r: red, 840 | /// g: green, 841 | /// b: blue, 842 | /// a: alpha, 843 | /// } = slate_blue; 844 | /// ``` 845 | #[must_use] 846 | #[inline] 847 | pub const fn split_rgba(self) -> (u8, u8, u8, u8) { 848 | let HexColor { r, g, b, a } = self; 849 | (r, g, b, a) 850 | } 851 | 852 | /// Deconstructs a `HexColor` into a tuple of its components: `(r, g, b)`. 853 | /// 854 | /// This primarily helps in cleaner deconstruction of `HexColor` instances, 855 | /// especially if the variable bindings aren't the same as the `struct`'s 856 | /// fields. 857 | /// 858 | /// # Examples 859 | /// 860 | /// Basic usage: 861 | /// 862 | /// ``` 863 | /// use hex_color::HexColor; 864 | /// 865 | /// let powder_blue = HexColor::from_u24(0xB6D0E2); 866 | /// let (red, green, blue) = powder_blue.split_rgb(); 867 | /// ``` 868 | /// 869 | /// For contrast, here's what it would look like otherwise; it's not 870 | /// terrible, but en masse, it's subjectively annoying: 871 | /// 872 | /// ``` 873 | /// use hex_color::HexColor; 874 | /// 875 | /// let powder_blue = HexColor::from_u24(0xB6D0E2); 876 | /// let HexColor { 877 | /// r: red, 878 | /// g: green, 879 | /// b: blue, 880 | /// .. 881 | /// } = powder_blue; 882 | /// ``` 883 | #[must_use] 884 | #[inline] 885 | pub const fn split_rgb(self) -> (u8, u8, u8) { 886 | let HexColor { r, g, b, .. } = self; 887 | (r, g, b) 888 | } 889 | 890 | //////////////////////////////////////////////////////////////////////////// 891 | // Display 892 | //////////////////////////////////////////////////////////////////////////// 893 | 894 | /// Returns an object that implements [`fmt::Display`] for `HexColor`. By 895 | /// default, the alpha channel is hidden and the letters are uppercase. 896 | /// 897 | /// # Examples 898 | /// 899 | /// ``` 900 | /// use hex_color::HexColor; 901 | /// 902 | /// let display_red = HexColor::RED.display_rgb(); 903 | /// assert_eq!(format!("{display_red}"), "#FF0000"); 904 | /// ``` 905 | /// 906 | /// For more control over the display, see [`Display::with_alpha`] and 907 | /// [`Display::with_case`]. 908 | #[must_use] 909 | #[inline] 910 | pub const fn display_rgb(self) -> Display { 911 | Display::new(self).with_alpha(Alpha::Hidden) 912 | } 913 | 914 | /// Returns an object that implements [`fmt::Display`] for `HexColor`. By 915 | /// default, the alpha channel is visible and the letters are uppercase. 916 | /// 917 | /// # Examples 918 | /// 919 | /// ``` 920 | /// use hex_color::HexColor; 921 | /// 922 | /// let display_red = HexColor::RED.display_rgba(); 923 | /// assert_eq!(format!("{display_red}"), "#FF0000FF"); 924 | /// ``` 925 | /// 926 | /// For more control over the display, see [`Display::with_alpha`] and 927 | /// [`Display::with_case`]. 928 | #[must_use] 929 | #[inline] 930 | pub const fn display_rgba(self) -> Display { 931 | Display::new(self).with_alpha(Alpha::Visible) 932 | } 933 | 934 | //////////////////////////////////////////////////////////////////////////// 935 | // Arithmetic operations 936 | //////////////////////////////////////////////////////////////////////////// 937 | 938 | /// Adds two colors together. 939 | /// 940 | /// Each component is added separately. The alpha component is ignored 941 | /// entirely, always returning an RGB color (where alpha is the default 942 | /// [`u8::MAX`]). 943 | /// 944 | /// # Panics 945 | /// 946 | /// Panics if any overflow occurs. 947 | /// 948 | /// # Examples 949 | /// 950 | /// ``` 951 | /// use hex_color::HexColor; 952 | /// 953 | /// assert_eq!(HexColor::BLUE.add(HexColor::GREEN), HexColor::CYAN); 954 | /// assert_eq!(HexColor::RED.add(HexColor::BLUE), HexColor::MAGENTA); 955 | /// assert_eq!(HexColor::GREEN.add(HexColor::RED), HexColor::YELLOW); 956 | /// ``` 957 | #[inline] 958 | #[must_use] 959 | #[track_caller] 960 | pub const fn add(self, rhs: HexColor) -> HexColor { 961 | let (r1, g1, b1) = self.split_rgb(); 962 | let (r2, g2, b2) = rhs.split_rgb(); 963 | HexColor::rgb(r1 + r2, g1 + g2, b1 + b2) 964 | } 965 | 966 | /// Checked color addition. Computes `self + rhs`, returning [`None`] if 967 | /// overflow occurred. 968 | /// 969 | /// # Examples 970 | /// 971 | /// ``` 972 | /// use hex_color::HexColor; 973 | /// 974 | /// let almost_white = HexColor::achromatic(254); 975 | /// let one = HexColor::achromatic(1); 976 | /// 977 | /// assert_eq!(almost_white.checked_add(one), Some(HexColor::WHITE)); 978 | /// assert_eq!(HexColor::WHITE.checked_add(one), None); 979 | /// ``` 980 | #[inline] 981 | #[must_use] 982 | pub const fn checked_add(self, rhs: HexColor) -> Option { 983 | let (res, flag) = self.overflowing_add(rhs); 984 | // TODO: Use `unlikely!` or some equivalent hint when stable. 985 | if flag { 986 | None 987 | } else { 988 | Some(res) 989 | } 990 | } 991 | 992 | /// Calculates `self + rhs`. 993 | /// 994 | /// Returns a tuple of the addition along with a boolean indicating whether 995 | /// any arithmetic overflow would occur. If an overflow would have occurred, 996 | /// then the wrapped value is returned. 997 | /// 998 | /// # Examples 999 | /// 1000 | /// ``` 1001 | /// use hex_color::HexColor; 1002 | /// 1003 | /// let almost_white = HexColor::achromatic(254); 1004 | /// let one = HexColor::achromatic(1); 1005 | /// 1006 | /// assert_eq!(almost_white.overflowing_add(one), (HexColor::WHITE, false),); 1007 | /// assert_eq!( 1008 | /// HexColor::WHITE.overflowing_add(one), 1009 | /// (HexColor::BLACK, true), 1010 | /// ); 1011 | /// ``` 1012 | #[inline] 1013 | #[must_use] 1014 | pub const fn overflowing_add(self, rhs: HexColor) -> (HexColor, bool) { 1015 | let (r1, g1, b1) = self.split_rgb(); 1016 | let (r2, g2, b2) = rhs.split_rgb(); 1017 | 1018 | let (r, r_flag) = r1.overflowing_add(r2); 1019 | let (g, g_flag) = g1.overflowing_add(g2); 1020 | let (b, b_flag) = b1.overflowing_add(b2); 1021 | 1022 | (HexColor::rgb(r, g, b), r_flag || g_flag || b_flag) 1023 | } 1024 | 1025 | /// Saturating color addition. Computes `self + rhs`, saturating at the 1026 | /// numeric bounds instead of overflowing. 1027 | /// 1028 | /// # Examples 1029 | /// 1030 | /// ``` 1031 | /// use hex_color::HexColor; 1032 | /// 1033 | /// // Even though the green component should exceed 255, it saturates at 1034 | /// // 255 instead: 1035 | /// assert_eq!( 1036 | /// HexColor::YELLOW.saturating_add(HexColor::CYAN), 1037 | /// HexColor::WHITE, 1038 | /// ); 1039 | /// ``` 1040 | #[inline] 1041 | #[must_use] 1042 | pub const fn saturating_add(self, rhs: HexColor) -> HexColor { 1043 | let (r1, g1, b1) = self.split_rgb(); 1044 | let (r2, g2, b2) = rhs.split_rgb(); 1045 | HexColor::rgb( 1046 | r1.saturating_add(r2), 1047 | g1.saturating_add(g2), 1048 | b1.saturating_add(b2), 1049 | ) 1050 | } 1051 | 1052 | /// Wrapping (modular) addition. Computes `self + rhs`, wrapping around the 1053 | /// boundary of [`u8`]. 1054 | /// 1055 | /// # Examples 1056 | /// 1057 | /// ``` 1058 | /// use hex_color::HexColor; 1059 | /// 1060 | /// let almost_white = HexColor::achromatic(254); 1061 | /// let one = HexColor::achromatic(1); 1062 | /// 1063 | /// assert_eq!(almost_white.wrapping_add(one), HexColor::WHITE); 1064 | /// assert_eq!(HexColor::WHITE.wrapping_add(one), HexColor::BLACK); 1065 | /// ``` 1066 | #[inline] 1067 | #[must_use] 1068 | pub const fn wrapping_add(self, rhs: HexColor) -> HexColor { 1069 | let (r1, g1, b1) = self.split_rgb(); 1070 | let (r2, g2, b2) = rhs.split_rgb(); 1071 | HexColor::rgb( 1072 | r1.wrapping_add(r2), 1073 | g1.wrapping_add(g2), 1074 | b1.wrapping_add(b2), 1075 | ) 1076 | } 1077 | 1078 | /// Subtracts one color from another. 1079 | /// 1080 | /// Each component is subtracted separately. The alpha component is ignored 1081 | /// entirely, always returning an RGB color (where alpha is the default 1082 | /// [`u8::MAX`]). 1083 | /// 1084 | /// # Panics 1085 | /// 1086 | /// Panics if any overflow occurs. 1087 | /// 1088 | /// # Examples 1089 | /// 1090 | /// ``` 1091 | /// use hex_color::HexColor; 1092 | /// 1093 | /// assert_eq!(HexColor::MAGENTA.sub(HexColor::BLUE), HexColor::RED); 1094 | /// assert_eq!(HexColor::YELLOW.sub(HexColor::RED), HexColor::GREEN); 1095 | /// assert_eq!(HexColor::CYAN.sub(HexColor::GREEN), HexColor::BLUE); 1096 | /// ``` 1097 | #[inline] 1098 | #[must_use] 1099 | #[track_caller] 1100 | pub const fn sub(self, rhs: HexColor) -> HexColor { 1101 | let (r1, g1, b1) = self.split_rgb(); 1102 | let (r2, g2, b2) = rhs.split_rgb(); 1103 | HexColor::rgb(r1 - r2, g1 - g2, b1 - b2) 1104 | } 1105 | 1106 | /// Subtracts `n` from the `HexColor`'s red, green, and blue components. 1107 | /// 1108 | /// # Panics 1109 | /// 1110 | /// Panics if overflow occurs. 1111 | /// 1112 | /// # Examples 1113 | /// 1114 | /// ``` 1115 | /// use hex_color::HexColor; 1116 | /// 1117 | /// assert_eq!(HexColor::WHITE.sub_scalar(255), HexColor::BLACK); 1118 | /// ``` 1119 | #[inline] 1120 | #[must_use] 1121 | #[track_caller] 1122 | pub const fn sub_scalar(self, n: u8) -> HexColor { 1123 | self.sub(HexColor::achromatic(n)) 1124 | } 1125 | 1126 | /// Checked color subtraction. Computes `self - rhs`, returning [`None`] if 1127 | /// overflow occurred. 1128 | /// 1129 | /// # Examples 1130 | /// 1131 | /// ``` 1132 | /// use hex_color::HexColor; 1133 | /// 1134 | /// let almost_black = HexColor::achromatic(1); 1135 | /// let one = HexColor::achromatic(1); 1136 | /// 1137 | /// assert_eq!(almost_black.checked_sub(one), Some(HexColor::BLACK)); 1138 | /// assert_eq!(HexColor::BLACK.checked_sub(one), None); 1139 | /// ``` 1140 | #[inline] 1141 | #[must_use] 1142 | pub const fn checked_sub(self, rhs: HexColor) -> Option { 1143 | let (res, flag) = self.overflowing_sub(rhs); 1144 | // TODO: Use `unlikely!` or some equivalent hint when stable. 1145 | if flag { 1146 | None 1147 | } else { 1148 | Some(res) 1149 | } 1150 | } 1151 | 1152 | /// Calculates `self - rhs`. 1153 | /// 1154 | /// Returns a tuple of the subtraction along with a boolean indicating 1155 | /// whether any arithmetic overflow would occur. If an overflow would have 1156 | /// occurred, then the wrapped value is returned. 1157 | /// 1158 | /// # Examples 1159 | /// 1160 | /// ``` 1161 | /// use hex_color::HexColor; 1162 | /// 1163 | /// let almost_black = HexColor::achromatic(1); 1164 | /// let one = HexColor::achromatic(1); 1165 | /// 1166 | /// assert_eq!(almost_black.overflowing_sub(one), (HexColor::BLACK, false),); 1167 | /// assert_eq!( 1168 | /// HexColor::BLACK.overflowing_sub(one), 1169 | /// (HexColor::WHITE, true), 1170 | /// ); 1171 | /// ``` 1172 | #[inline] 1173 | #[must_use] 1174 | pub const fn overflowing_sub(self, rhs: HexColor) -> (HexColor, bool) { 1175 | let (r1, g1, b1) = self.split_rgb(); 1176 | let (r2, g2, b2) = rhs.split_rgb(); 1177 | 1178 | let (r, r_flag) = r1.overflowing_sub(r2); 1179 | let (g, g_flag) = g1.overflowing_sub(g2); 1180 | let (b, b_flag) = b1.overflowing_sub(b2); 1181 | 1182 | (HexColor::rgb(r, g, b), r_flag || g_flag || b_flag) 1183 | } 1184 | 1185 | /// Saturating color subtraction. Computes `self - rhs`, saturating at the 1186 | /// numeric bounds instead of overflowing. 1187 | /// 1188 | /// # Examples 1189 | /// 1190 | /// ``` 1191 | /// use hex_color::HexColor; 1192 | /// 1193 | /// // Even though the red component should overflow, it saturates at 0 1194 | /// // instead: 1195 | /// assert_eq!( 1196 | /// HexColor::CYAN.saturating_sub(HexColor::YELLOW), 1197 | /// HexColor::BLUE, 1198 | /// ); 1199 | /// ``` 1200 | #[inline] 1201 | #[must_use] 1202 | pub const fn saturating_sub(self, rhs: HexColor) -> HexColor { 1203 | let (r1, g1, b1) = self.split_rgb(); 1204 | let (r2, g2, b2) = rhs.split_rgb(); 1205 | HexColor::rgb( 1206 | r1.saturating_sub(r2), 1207 | g1.saturating_sub(g2), 1208 | b1.saturating_sub(b2), 1209 | ) 1210 | } 1211 | 1212 | /// Wrapping (modular) subtraction. Computes `self - rhs`, wrapping around 1213 | /// the boundary of [`u8`]. 1214 | /// 1215 | /// # Examples 1216 | /// 1217 | /// ``` 1218 | /// use hex_color::HexColor; 1219 | /// 1220 | /// let almost_black = HexColor::achromatic(1); 1221 | /// let one = HexColor::achromatic(1); 1222 | /// 1223 | /// assert_eq!(almost_black.wrapping_sub(one), HexColor::BLACK); 1224 | /// assert_eq!(HexColor::BLACK.wrapping_sub(one), HexColor::WHITE); 1225 | /// ``` 1226 | #[inline] 1227 | #[must_use] 1228 | pub const fn wrapping_sub(self, rhs: HexColor) -> HexColor { 1229 | let (r1, g1, b1) = self.split_rgb(); 1230 | let (r2, g2, b2) = rhs.split_rgb(); 1231 | HexColor::rgb( 1232 | r1.wrapping_sub(r2), 1233 | g1.wrapping_sub(g2), 1234 | b1.wrapping_sub(b2), 1235 | ) 1236 | } 1237 | 1238 | /// Multiplies two colors together. 1239 | /// 1240 | /// Each component is multiplied separately. The alpha component is ignored 1241 | /// entirely, always returning an RGB color (where alpha is the default 1242 | /// [`u8::MAX`]). 1243 | /// 1244 | /// # Panics 1245 | /// 1246 | /// Panics if any overflow occurs. 1247 | /// 1248 | /// # Examples 1249 | /// 1250 | /// ``` 1251 | /// use hex_color::HexColor; 1252 | /// 1253 | /// let a = HexColor::rgb(1, 2, 3); 1254 | /// let b = HexColor::rgb(4, 5, 6); 1255 | /// 1256 | /// assert_eq!(a.mul(b), HexColor::rgb(4, 10, 18)); 1257 | /// ``` 1258 | #[inline] 1259 | #[must_use] 1260 | #[track_caller] 1261 | pub const fn mul(self, rhs: HexColor) -> HexColor { 1262 | let (r1, g1, b1) = self.split_rgb(); 1263 | let (r2, g2, b2) = rhs.split_rgb(); 1264 | HexColor::rgb(r1 * r2, g1 * g2, b1 * b2) 1265 | } 1266 | 1267 | /// Checked color multiplication. Computes `self * rhs`, returning [`None`] 1268 | /// if overflow occurred. 1269 | /// 1270 | /// # Examples 1271 | /// 1272 | /// ``` 1273 | /// use hex_color::HexColor; 1274 | /// 1275 | /// assert_eq!( 1276 | /// HexColor::achromatic(5).checked_mul(HexColor::achromatic(2)), 1277 | /// Some(HexColor::achromatic(10)), 1278 | /// ); 1279 | /// assert_eq!(HexColor::MAX.checked_mul(HexColor::achromatic(2)), None,); 1280 | /// ``` 1281 | #[inline] 1282 | #[must_use] 1283 | pub const fn checked_mul(self, rhs: HexColor) -> Option { 1284 | let (res, flag) = self.overflowing_mul(rhs); 1285 | // TODO: Use `unlikely!` or some equivalent hint when stable. 1286 | if flag { 1287 | None 1288 | } else { 1289 | Some(res) 1290 | } 1291 | } 1292 | 1293 | /// Calculates `self * rhs`. 1294 | /// 1295 | /// Returns a tuple of the multiplication along with a boolean indicating 1296 | /// whether any arithmetic overflow would occur. If an overflow would have 1297 | /// occurred, then the wrapped value is returned. 1298 | /// 1299 | /// # Examples 1300 | /// 1301 | /// ``` 1302 | /// use hex_color::HexColor; 1303 | /// 1304 | /// assert_eq!( 1305 | /// HexColor::achromatic(5).overflowing_mul(HexColor::achromatic(2)), 1306 | /// (HexColor::achromatic(10), false), 1307 | /// ); 1308 | /// assert_eq!( 1309 | /// HexColor::achromatic(200).overflowing_mul(HexColor::achromatic(2)), 1310 | /// (HexColor::achromatic(144), true), 1311 | /// ); 1312 | /// ``` 1313 | #[inline] 1314 | #[must_use] 1315 | pub const fn overflowing_mul(self, rhs: HexColor) -> (HexColor, bool) { 1316 | let (r1, g1, b1) = self.split_rgb(); 1317 | let (r2, g2, b2) = rhs.split_rgb(); 1318 | 1319 | let (r, r_flag) = r1.overflowing_mul(r2); 1320 | let (g, g_flag) = g1.overflowing_mul(g2); 1321 | let (b, b_flag) = b1.overflowing_mul(b2); 1322 | 1323 | (HexColor::rgb(r, g, b), r_flag || g_flag || b_flag) 1324 | } 1325 | 1326 | /// Saturating color multiplication. Computes `self * rhs`, saturating at 1327 | /// the numeric bounds instead of overflowing. 1328 | /// 1329 | /// # Examples 1330 | /// 1331 | /// ``` 1332 | /// use hex_color::HexColor; 1333 | /// 1334 | /// assert_eq!( 1335 | /// HexColor::achromatic(5).saturating_mul(HexColor::achromatic(2)), 1336 | /// HexColor::achromatic(10), 1337 | /// ); 1338 | /// assert_eq!( 1339 | /// HexColor::achromatic(200).saturating_mul(HexColor::achromatic(2)), 1340 | /// HexColor::achromatic(255), 1341 | /// ); 1342 | /// ``` 1343 | #[inline] 1344 | #[must_use] 1345 | pub const fn saturating_mul(self, rhs: HexColor) -> HexColor { 1346 | let (r1, g1, b1) = self.split_rgb(); 1347 | let (r2, g2, b2) = rhs.split_rgb(); 1348 | HexColor::rgb( 1349 | r1.saturating_mul(r2), 1350 | g1.saturating_mul(g2), 1351 | b1.saturating_mul(b2), 1352 | ) 1353 | } 1354 | 1355 | /// Wrapping (modular) multiplication. Computes `self * rhs`, wrapping 1356 | /// around at the boundary of the type. 1357 | /// 1358 | /// # Examples 1359 | /// 1360 | /// ``` 1361 | /// use hex_color::HexColor; 1362 | /// 1363 | /// assert_eq!( 1364 | /// HexColor::achromatic(5).wrapping_mul(HexColor::achromatic(2)), 1365 | /// HexColor::achromatic(10), 1366 | /// ); 1367 | /// assert_eq!( 1368 | /// HexColor::achromatic(200).wrapping_mul(HexColor::achromatic(2)), 1369 | /// HexColor::achromatic(144), 1370 | /// ); 1371 | /// ``` 1372 | #[inline] 1373 | #[must_use] 1374 | pub const fn wrapping_mul(self, rhs: HexColor) -> HexColor { 1375 | let (r1, g1, b1) = self.split_rgb(); 1376 | let (r2, g2, b2) = rhs.split_rgb(); 1377 | HexColor::rgb( 1378 | r1.wrapping_mul(r2), 1379 | g1.wrapping_mul(g2), 1380 | b1.wrapping_mul(b2), 1381 | ) 1382 | } 1383 | 1384 | /// Divides one color with another. 1385 | /// 1386 | /// Each component is divided separately. The alpha component is ignored 1387 | /// entirely, always returning an RGB color (where alpha is the default 1388 | /// [`u8::MAX`]). 1389 | /// 1390 | /// # Panics 1391 | /// 1392 | /// Panics if any component is divided by zero. 1393 | /// 1394 | /// # Examples 1395 | /// 1396 | /// ``` 1397 | /// use hex_color::HexColor; 1398 | /// 1399 | /// let a = HexColor::rgb(128, 64, 32); 1400 | /// let b = HexColor::rgb(2, 4, 8); 1401 | /// 1402 | /// assert_eq!(a.div(b), HexColor::rgb(64, 16, 4)); 1403 | /// ``` 1404 | #[inline] 1405 | #[must_use] 1406 | #[track_caller] 1407 | pub const fn div(self, rhs: HexColor) -> HexColor { 1408 | let (r1, g1, b1) = self.split_rgb(); 1409 | let (r2, g2, b2) = rhs.split_rgb(); 1410 | HexColor::rgb(r1 / r2, g1 / g2, b1 / b2) 1411 | } 1412 | 1413 | /// Checked color division. Computes `self / rhs`, returning [`None`] if 1414 | /// any component of `rhs` is zero. 1415 | /// 1416 | /// # Examples 1417 | /// 1418 | /// ``` 1419 | /// use hex_color::HexColor; 1420 | /// 1421 | /// assert_eq!( 1422 | /// HexColor::achromatic(128).checked_div(HexColor::achromatic(2)), 1423 | /// Some(HexColor::achromatic(64)), 1424 | /// ); 1425 | /// assert_eq!(HexColor::WHITE.checked_div(HexColor::achromatic(0)), None,); 1426 | /// ``` 1427 | #[inline] 1428 | #[must_use] 1429 | pub const fn checked_div(self, rhs: HexColor) -> Option { 1430 | let (r1, g1, b1) = self.split_rgb(); 1431 | let (r2, g2, b2) = rhs.split_rgb(); 1432 | // TODO: Use `unlikely!` or some equivalent hint when stable. 1433 | if r2 == 0 || g2 == 0 || b2 == 0 { 1434 | None 1435 | } else { 1436 | Some(HexColor::rgb(r1 / r2, g1 / g2, b1 / b2)) 1437 | } 1438 | } 1439 | 1440 | //////////////////////////////////////////////////////////////////////////// 1441 | // "Complex" operations 1442 | //////////////////////////////////////////////////////////////////////////// 1443 | 1444 | /// Scales the `r`, `g`, and `b` components of the [`HexColor`] by `f`. 1445 | /// 1446 | /// The alpha component is ignore entirely, but is preserved in the 1447 | /// resulting color. 1448 | /// 1449 | /// # Examples 1450 | /// 1451 | /// ``` 1452 | /// use hex_color::HexColor; 1453 | /// 1454 | /// let amber = HexColor::from_u24(0xFFBF00); 1455 | /// 1456 | /// let lighter_amber = amber.scale(1.1); // 10% lighter (rounded) 1457 | /// assert_eq!(lighter_amber, HexColor::from_u24(0xFFD200)); 1458 | /// 1459 | /// let darker_amber = amber.scale(0.9); // 10% darker (rounded) 1460 | /// assert_eq!(darker_amber, HexColor::from_u24(0xE6AC00)); 1461 | /// ``` 1462 | #[inline] 1463 | #[must_use] 1464 | #[track_caller] 1465 | #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] 1466 | pub fn scale(self, f: f32) -> Self { 1467 | let (r, g, b, a) = self.split_rgba(); 1468 | let r = (f32::from(r) * f).min(255.0).round() as u8; 1469 | let g = (f32::from(g) * f).min(255.0).round() as u8; 1470 | let b = (f32::from(b) * f).min(255.0).round() as u8; 1471 | HexColor::rgba(r, g, b, a) 1472 | } 1473 | 1474 | /// Linearly inverts the [`HexColor`]. 1475 | /// 1476 | /// # Examples 1477 | /// 1478 | /// ``` 1479 | /// use hex_color::HexColor; 1480 | /// 1481 | /// assert_eq!(HexColor::RED.invert(), HexColor::CYAN); 1482 | /// ``` 1483 | #[inline] 1484 | #[must_use] 1485 | pub const fn invert(self) -> HexColor { 1486 | let (r, g, b, a) = self.split_rgba(); 1487 | HexColor::rgba(0xFF - r, 0xFF - g, 0xFF - b, a) 1488 | } 1489 | } 1490 | 1491 | //////////////////////////////////////////////////////////////////////////////// 1492 | // Arithmetic traits 1493 | //////////////////////////////////////////////////////////////////////////////// 1494 | 1495 | impl Add for HexColor { 1496 | type Output = HexColor; 1497 | 1498 | #[inline] 1499 | #[track_caller] 1500 | fn add(self, rhs: Self) -> Self::Output { 1501 | self.add(rhs) 1502 | } 1503 | } 1504 | 1505 | impl AddAssign for HexColor { 1506 | #[inline] 1507 | #[track_caller] 1508 | fn add_assign(&mut self, rhs: Self) { 1509 | *self = *self + rhs; 1510 | } 1511 | } 1512 | 1513 | impl Sub for HexColor { 1514 | type Output = HexColor; 1515 | 1516 | #[inline] 1517 | #[track_caller] 1518 | fn sub(self, rhs: Self) -> Self::Output { 1519 | self.sub(rhs) 1520 | } 1521 | } 1522 | 1523 | impl SubAssign for HexColor { 1524 | #[inline] 1525 | #[track_caller] 1526 | fn sub_assign(&mut self, rhs: Self) { 1527 | *self = *self - rhs; 1528 | } 1529 | } 1530 | 1531 | impl Mul for HexColor { 1532 | type Output = HexColor; 1533 | 1534 | #[inline] 1535 | #[track_caller] 1536 | fn mul(self, rhs: Self) -> Self::Output { 1537 | self.mul(rhs) 1538 | } 1539 | } 1540 | 1541 | impl MulAssign for HexColor { 1542 | #[inline] 1543 | #[track_caller] 1544 | fn mul_assign(&mut self, rhs: Self) { 1545 | *self = *self * rhs; 1546 | } 1547 | } 1548 | 1549 | impl Mul for HexColor { 1550 | type Output = HexColor; 1551 | 1552 | #[inline] 1553 | #[track_caller] 1554 | fn mul(self, rhs: f32) -> Self::Output { 1555 | self.scale(rhs) 1556 | } 1557 | } 1558 | 1559 | impl Mul for f32 { 1560 | type Output = HexColor; 1561 | 1562 | #[inline] 1563 | #[track_caller] 1564 | fn mul(self, rhs: HexColor) -> Self::Output { 1565 | rhs.scale(self) 1566 | } 1567 | } 1568 | 1569 | impl MulAssign for HexColor { 1570 | #[inline] 1571 | #[track_caller] 1572 | fn mul_assign(&mut self, rhs: f32) { 1573 | *self = *self * rhs; 1574 | } 1575 | } 1576 | 1577 | impl Mul for HexColor { 1578 | type Output = HexColor; 1579 | 1580 | #[inline] 1581 | #[track_caller] 1582 | #[allow(clippy::cast_possible_truncation)] 1583 | fn mul(self, rhs: f64) -> Self::Output { 1584 | self.scale(rhs as f32) 1585 | } 1586 | } 1587 | 1588 | impl Mul for f64 { 1589 | type Output = HexColor; 1590 | 1591 | #[inline] 1592 | #[track_caller] 1593 | fn mul(self, rhs: HexColor) -> Self::Output { 1594 | rhs * self 1595 | } 1596 | } 1597 | 1598 | impl MulAssign for HexColor { 1599 | #[inline] 1600 | #[track_caller] 1601 | fn mul_assign(&mut self, rhs: f64) { 1602 | *self = *self * rhs; 1603 | } 1604 | } 1605 | 1606 | impl Div for HexColor { 1607 | type Output = HexColor; 1608 | 1609 | #[inline] 1610 | #[track_caller] 1611 | fn div(self, rhs: Self) -> Self::Output { 1612 | self.div(rhs) 1613 | } 1614 | } 1615 | 1616 | impl DivAssign for HexColor { 1617 | #[inline] 1618 | #[track_caller] 1619 | fn div_assign(&mut self, rhs: Self) { 1620 | *self = *self / rhs; 1621 | } 1622 | } 1623 | 1624 | //////////////////////////////////////////////////////////////////////////////// 1625 | // Conversion traits 1626 | //////////////////////////////////////////////////////////////////////////////// 1627 | 1628 | impl From<(u8, u8, u8, u8)> for HexColor { 1629 | /// Constructs a new `HexColor` from a tuple of `(r, g, b, a)`. 1630 | /// 1631 | /// # Examples 1632 | /// 1633 | /// ``` 1634 | /// use hex_color::HexColor; 1635 | /// 1636 | /// let brass = (0xE1, 0xC1, 0x6E, 0xFF); 1637 | /// let color = HexColor::from(brass); 1638 | /// 1639 | /// assert_eq!(color.r, 0xE1); 1640 | /// assert_eq!(color.g, 0xC1); 1641 | /// assert_eq!(color.b, 0x6E); 1642 | /// assert_eq!(color.a, 0xFF); 1643 | /// ``` 1644 | #[inline] 1645 | fn from((r, g, b, a): (u8, u8, u8, u8)) -> Self { 1646 | HexColor::rgba(r, g, b, a) 1647 | } 1648 | } 1649 | 1650 | impl From for (u8, u8, u8, u8) { 1651 | /// Deconstructs a `HexColor` into a tuple of its components: `(r, g, b, 1652 | /// a)`. 1653 | /// 1654 | /// # Examples 1655 | /// 1656 | /// ``` 1657 | /// use hex_color::HexColor; 1658 | /// 1659 | /// let brass = HexColor::from_u24(0xE1C16E); 1660 | /// let (red, green, blue, alpha) = brass.into(); 1661 | /// 1662 | /// assert_eq!(red, 0xE1); 1663 | /// assert_eq!(green, 0xC1); 1664 | /// assert_eq!(blue, 0x6E); 1665 | /// assert_eq!(alpha, 0xFF); 1666 | fn from(hex_color: HexColor) -> Self { 1667 | hex_color.split_rgba() 1668 | } 1669 | } 1670 | 1671 | impl From<(u8, u8, u8)> for HexColor { 1672 | /// Constructs a new `HexColor` from a tuple of `(r, g, b)`. 1673 | /// 1674 | /// # Examples 1675 | /// 1676 | /// ``` 1677 | /// use hex_color::HexColor; 1678 | /// 1679 | /// let jade = (0x00, 0xA3, 0x6C); 1680 | /// let color = HexColor::from(jade); 1681 | /// 1682 | /// assert_eq!(color.r, 0x00); 1683 | /// assert_eq!(color.g, 0xA3); 1684 | /// assert_eq!(color.b, 0x6C); 1685 | /// ``` 1686 | #[inline] 1687 | fn from((r, g, b): (u8, u8, u8)) -> Self { 1688 | HexColor::rgb(r, g, b) 1689 | } 1690 | } 1691 | 1692 | impl From for (u8, u8, u8) { 1693 | /// Deconstructs a `HexColor` into a tuple of its components: `(r, g, b)`. 1694 | /// 1695 | /// # Examples 1696 | /// 1697 | /// ``` 1698 | /// use hex_color::HexColor; 1699 | /// 1700 | /// let jade = HexColor::from_u24(0x00A36C); 1701 | /// let (red, green, blue) = jade.into(); 1702 | /// 1703 | /// assert_eq!(red, 0x00); 1704 | /// assert_eq!(green, 0xA3); 1705 | /// assert_eq!(blue, 0x6C); 1706 | /// ``` 1707 | #[inline] 1708 | fn from(hex_color: HexColor) -> Self { 1709 | hex_color.split_rgb() 1710 | } 1711 | } 1712 | 1713 | impl From for HexColor { 1714 | /// Constructs a new `HexColor` from a `u32` via [`HexColor::from_u32`]. 1715 | #[inline] 1716 | fn from(n: u32) -> Self { 1717 | HexColor::from_u32(n) 1718 | } 1719 | } 1720 | 1721 | impl From for u32 { 1722 | /// Constructs a new `u32` from a `HexColor` via [`HexColor::to_u32`]. 1723 | #[inline] 1724 | fn from(hex_color: HexColor) -> Self { 1725 | hex_color.to_u32() 1726 | } 1727 | } 1728 | 1729 | //////////////////////////////////////////////////////////////////////////////// 1730 | // Parsing Details 1731 | //////////////////////////////////////////////////////////////////////////////// 1732 | 1733 | impl FromStr for HexColor { 1734 | type Err = ParseHexColorError; 1735 | 1736 | /// Semantically identical to [`HexColor::parse`]. For more information, 1737 | /// refer to that function's documentation. 1738 | #[inline] 1739 | fn from_str(s: &str) -> Result { 1740 | HexColor::parse_internals(s, ParseMode::Any) 1741 | } 1742 | } 1743 | 1744 | #[derive(Debug, Copy, Clone)] 1745 | enum ParseMode { 1746 | Any, 1747 | Rgb, 1748 | Rgba, 1749 | } 1750 | 1751 | //////////////////////////////////////////////////////////////////////////////// 1752 | // Display 1753 | //////////////////////////////////////////////////////////////////////////////// 1754 | 1755 | /// Helper struct for printing [`HexColor`] objects with [`format!`] and `{}`. 1756 | /// 1757 | /// [`format!`]: https://doc.rust-lang.org/alloc/macro.format.html 1758 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 1759 | pub struct Display { 1760 | color: HexColor, 1761 | alpha: Alpha, 1762 | case: Case, 1763 | } 1764 | 1765 | impl Display { 1766 | /// Constructs a new `Display` from a [`HexColor`]. By default, the alpha 1767 | /// channel is hidden and the letters are uppercase. 1768 | /// 1769 | /// # Examples 1770 | /// 1771 | /// ``` 1772 | /// use hex_color::{Display, HexColor}; 1773 | /// 1774 | /// let gainsboro = HexColor::from_u24(0xDCDCDC); 1775 | /// let display = Display::new(gainsboro); 1776 | /// 1777 | /// assert_eq!(format!("{display}"), "#DCDCDC"); 1778 | /// ``` 1779 | /// 1780 | /// Consider using [`HexColor::display_rgb`] or [`HexColor::display_rgba`] 1781 | /// if it's more convenient. 1782 | #[must_use] 1783 | #[inline] 1784 | pub const fn new(color: HexColor) -> Self { 1785 | Self { 1786 | color, 1787 | alpha: Alpha::Hidden, 1788 | case: Case::Upper, 1789 | } 1790 | } 1791 | 1792 | /// Creates a new `Display` with the given [`Alpha`] display option. 1793 | /// 1794 | /// # Examples 1795 | /// 1796 | /// ``` 1797 | /// use hex_color::{Alpha, Display, HexColor}; 1798 | /// 1799 | /// let transparent_gainsboro = HexColor::from_u32(0x7080907F); 1800 | /// let display = Display::new(transparent_gainsboro).with_alpha(Alpha::Visible); 1801 | /// 1802 | /// assert_eq!(format!("{display}"), "#7080907F"); 1803 | /// ``` 1804 | #[must_use] 1805 | #[inline] 1806 | pub const fn with_alpha(mut self, alpha: Alpha) -> Self { 1807 | self.alpha = alpha; 1808 | self 1809 | } 1810 | 1811 | /// Creates a new `Display` with the given [`Case`] display option. 1812 | /// 1813 | /// # Examples 1814 | /// 1815 | /// ``` 1816 | /// use hex_color::{HexColor, Display, Case}; 1817 | /// 1818 | /// let gainsboro = HexColor::from_u24(0xDCDCDC); 1819 | /// let display = Display::new(gainsboro).with_case(Case::Lower); 1820 | /// 1821 | /// assert_eq!(format!("{display}"), "#dcdcdc"); 1822 | #[must_use] 1823 | #[inline] 1824 | pub const fn with_case(mut self, case: Case) -> Self { 1825 | self.case = case; 1826 | self 1827 | } 1828 | 1829 | /// Returns the [`HexColor`] that this `Display` was created from. 1830 | /// 1831 | /// # Examples 1832 | /// 1833 | /// ``` 1834 | /// use hex_color::HexColor; 1835 | /// 1836 | /// let transparent_lavender_blush = HexColor::from_u32(0xFFF0F57F); 1837 | /// let display = transparent_lavender_blush.display_rgb(); 1838 | /// 1839 | /// assert_eq!(display.color(), transparent_lavender_blush); 1840 | /// ``` 1841 | #[must_use] 1842 | #[inline] 1843 | pub const fn color(self) -> HexColor { 1844 | self.color 1845 | } 1846 | } 1847 | 1848 | impl fmt::Display for Display { 1849 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1850 | let (r, g, b) = self.color.split_rgb(); 1851 | 1852 | match self.case { 1853 | Case::Lower => write!(f, "#{r:02x}{g:02x}{b:02x}")?, 1854 | Case::Upper => write!(f, "#{r:02X}{g:02X}{b:02X}")?, 1855 | } 1856 | 1857 | if self.alpha == Alpha::Visible { 1858 | let a = self.color.a; 1859 | match self.case { 1860 | Case::Lower => write!(f, "{a:02x}")?, 1861 | Case::Upper => write!(f, "{a:02X}")?, 1862 | } 1863 | } 1864 | 1865 | Ok(()) 1866 | } 1867 | } 1868 | 1869 | /// An option for whether or not to display the alpha channel when displaying a 1870 | /// hexadecimal color. 1871 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] 1872 | pub enum Alpha { 1873 | /// When displaying hexadecimal colors, do not display the alpha channel. 1874 | #[default] 1875 | Hidden, 1876 | /// When displaying hexadecimal colors, display the alpha channel. 1877 | Visible, 1878 | } 1879 | 1880 | /// The case of the letters to use when displaying a hexadecimal color. 1881 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] 1882 | pub enum Case { 1883 | /// When displaying hexadecimal colors, use lowercase letters. 1884 | Lower, 1885 | /// When displaying hexadecimal colors, use uppercase letters. 1886 | #[default] 1887 | Upper, 1888 | } 1889 | 1890 | //////////////////////////////////////////////////////////////////////////////// 1891 | // Errors 1892 | //////////////////////////////////////////////////////////////////////////////// 1893 | 1894 | /// An error which can be returned when parsing a hex color. 1895 | /// 1896 | /// # Potential causes 1897 | /// 1898 | /// Among other causes, `ParseHexColorError` can be thrown because of leading 1899 | /// or trailing whitespace in the string e.g., when it is obtained from user 1900 | /// input. Using the [`str::trim()`] method ensures that no whitespace remains 1901 | /// before parsing. 1902 | #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] 1903 | #[non_exhaustive] 1904 | pub enum ParseHexColorError { 1905 | /// The input was empty. 1906 | Empty, 1907 | /// The string is of a malformed length or does not start with `#`. 1908 | InvalidFormat, 1909 | /// The format was presumably correct, but one of the digits wasn't 1910 | /// hexadecimal. 1911 | InvalidDigit, 1912 | } 1913 | 1914 | impl fmt::Display for ParseHexColorError { 1915 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1916 | let data = match self { 1917 | ParseHexColorError::Empty => "cannot parse hex color from empty string", 1918 | ParseHexColorError::InvalidFormat => "invalid hexadecimal color format", 1919 | ParseHexColorError::InvalidDigit => "invalid hexadecimal digit", 1920 | }; 1921 | f.write_str(data) 1922 | } 1923 | } 1924 | 1925 | #[cfg(feature = "std")] 1926 | #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] 1927 | impl std::error::Error for ParseHexColorError {} 1928 | --------------------------------------------------------------------------------