├── src ├── options │ ├── mod.rs │ ├── luminosity.rs │ ├── gamut.rs │ └── seed.rs ├── color_dictionary.rs └── lib.rs ├── .gitignore ├── .github └── workflows │ └── publish.yml ├── Cargo.toml ├── LICENSE └── README.md /src/options/mod.rs: -------------------------------------------------------------------------------- 1 | mod gamut; 2 | mod luminosity; 3 | mod seed; 4 | 5 | pub use self::gamut::Gamut; 6 | pub use self::luminosity::Luminosity; 7 | pub use self::seed::Seed; 8 | -------------------------------------------------------------------------------- /src/options/luminosity.rs: -------------------------------------------------------------------------------- 1 | /// The luminosity of the color. 2 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] 3 | pub enum Luminosity { 4 | #[default] 5 | Random, 6 | Bright, 7 | Light, 8 | Dark, 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /src/options/gamut.rs: -------------------------------------------------------------------------------- 1 | /// The gamut (hue) of the color. 2 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] 3 | pub enum Gamut { 4 | #[default] 5 | Monochrome, 6 | Red, 7 | Orange, 8 | Yellow, 9 | Green, 10 | Blue, 11 | Purple, 12 | Pink, 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build-and-publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | - uses: katyo/publish-crates@v2 20 | with: 21 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "random_color" 3 | version = "1.1.0" 4 | edition = "2021" 5 | authors = ["Lucas Maximiliano Marino "] 6 | license = "MIT" 7 | readme = "README.md" 8 | keywords = ["random", "color", "rgb", "rgba", "hsl"] 9 | repository = "https://github.com/elementh/random_color.git" 10 | homepage = "https://github.com/elementh/random_color" 11 | description = "Rust crate for generating random attractive colors" 12 | 13 | [features] 14 | rgb_support = ["dep:rgb"] 15 | palette_support = ["dep:palette"] 16 | ecolor_support = ["dep:ecolor"] 17 | 18 | [dependencies] 19 | rand = { version = "0.9", features = ["small_rng"] } 20 | rgb = { version = "0.8.50", optional = true } 21 | palette = { version = "0.7.6", optional = true } 22 | ecolor = { version = "0.31", optional = true } 23 | 24 | [package.metadata.docs.rs] 25 | all-features = true 26 | rustdoc-args = ["--cfg", "docsrs"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Lucas Maximiliano Marino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/options/seed.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::DefaultHasher; 2 | use std::hash::{Hash, Hasher}; 3 | 4 | /// A trait for types that can be used as seeds for the RandomColor struct. 5 | pub trait Seed { 6 | fn to_value(self) -> u64; 7 | } 8 | 9 | impl Seed for i64 { 10 | fn to_value(self) -> u64 { 11 | self as u64 12 | } 13 | } 14 | 15 | impl Seed for i32 { 16 | fn to_value(self) -> u64 { 17 | self as u64 18 | } 19 | } 20 | 21 | impl Seed for u64 { 22 | fn to_value(self) -> u64 { 23 | self 24 | } 25 | } 26 | 27 | impl Seed for u32 { 28 | fn to_value(self) -> u64 { 29 | self as u64 30 | } 31 | } 32 | 33 | impl Seed for String { 34 | fn to_value(self) -> u64 { 35 | let mut hasher = DefaultHasher::new(); 36 | self.hash(&mut hasher); 37 | hasher.finish() 38 | } 39 | } 40 | 41 | impl Seed for &String { 42 | fn to_value(self) -> u64 { 43 | let mut hasher = DefaultHasher::new(); 44 | self.hash(&mut hasher); 45 | hasher.finish() 46 | } 47 | } 48 | 49 | impl Seed for &str { 50 | fn to_value(self) -> u64 { 51 | let mut hasher = DefaultHasher::new(); 52 | self.hash(&mut hasher); 53 | hasher.finish() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # random_color [![crate badge](https://img.shields.io/crates/v/random_color.svg)](https://crates.io/crates/random_color) 2 | 3 | Rust crate for generating attractive random colors. Check it out on [crates.io](https://crates.io/crates/random_color). 4 | 5 | Inspired by [RandomColor](https://github.com/davidmerfield/randomColor) by [davidmerfield](https://github.com/davidmerfield). 6 | 7 | ## Using the library 8 | 9 | Check the online [documentation](https://docs.rs/random_color/latest/random_color/). 10 | 11 | ## License 12 | 13 | The MIT License (MIT) 14 | 15 | Copyright (c) 2017-2024 [Lucas Maximiliano Marino](https://lucasmarino.me) 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | -------------------------------------------------------------------------------- /src/color_dictionary.rs: -------------------------------------------------------------------------------- 1 | use crate::options::Gamut; 2 | 3 | /// Color information for a given hue. 4 | #[derive(Debug, Clone, PartialEq, Eq)] 5 | pub struct ColorInformation { 6 | /// The range of the hue. 7 | pub range: [i64; 2], 8 | /// The lower bounds of the hue. 9 | pub lower_bounds: Vec<[i64; 2]>, 10 | /// The saturation range of the hue. 11 | pub saturation_range: [i64; 2], 12 | /// The value range of the hue. 13 | pub value_range: [i64; 2], 14 | } 15 | 16 | impl ColorInformation { 17 | /// Create a new `ColorInformation` instance. 18 | pub fn new(range: [i64; 2], lower_bounds: Vec<[i64; 2]>) -> Self { 19 | let saturation_range_min = lower_bounds[0][0]; 20 | let saturation_range_max = lower_bounds[lower_bounds.len() - 1][0]; 21 | 22 | let saturation_range = [saturation_range_min, saturation_range_max]; 23 | 24 | let value_range_min = lower_bounds[lower_bounds.len() - 1][1]; 25 | let value_range_max = lower_bounds[0][1]; 26 | 27 | let value_range = [value_range_min, value_range_max]; 28 | 29 | Self { 30 | range, 31 | lower_bounds, 32 | saturation_range, 33 | value_range, 34 | } 35 | } 36 | 37 | /// Check if the given hue is within the range. 38 | pub fn has_between_range(&self, hue: &i64) -> bool { 39 | hue >= &self.range[0] && hue <= &self.range[1] 40 | } 41 | } 42 | 43 | /// The color dictionary contains all the color information for the different gamuts. 44 | #[derive(Debug, Clone, PartialEq, Eq)] 45 | pub struct ColorDictionary { 46 | /// The color information for the monochrome gamut. 47 | pub monochrome: ColorInformation, 48 | /// The color information for the red gamut. 49 | pub red: ColorInformation, 50 | /// The color information for the orange gamut. 51 | pub orange: ColorInformation, 52 | /// The color information for the yellow gamut. 53 | pub yellow: ColorInformation, 54 | /// The color information for the green gamut. 55 | pub green: ColorInformation, 56 | /// The color information for the blue gamut. 57 | pub blue: ColorInformation, 58 | /// The color information for the purple gamut. 59 | pub purple: ColorInformation, 60 | /// The color information for the pink gamut. 61 | pub pink: ColorInformation, 62 | } 63 | 64 | impl ColorDictionary { 65 | /// Creates a new `ColorDictionary` instance. 66 | pub fn new() -> ColorDictionary { 67 | ColorDictionary { 68 | monochrome: ColorInformation::new([0, 0], vec![[0, 0], [100, 0]]), 69 | red: ColorInformation::new( 70 | [-26, 18], 71 | vec![ 72 | [20, 100], 73 | [30, 92], 74 | [40, 89], 75 | [50, 85], 76 | [60, 78], 77 | [70, 70], 78 | [80, 60], 79 | [90, 55], 80 | [100, 50], 81 | ], 82 | ), 83 | orange: ColorInformation::new( 84 | [19, 46], 85 | vec![ 86 | [20, 100], 87 | [30, 93], 88 | [40, 88], 89 | [50, 86], 90 | [60, 85], 91 | [70, 70], 92 | [100, 70], 93 | ], 94 | ), 95 | yellow: ColorInformation::new( 96 | [47, 62], 97 | vec![ 98 | [25, 100], 99 | [40, 94], 100 | [50, 89], 101 | [60, 86], 102 | [70, 84], 103 | [80, 82], 104 | [90, 80], 105 | [100, 75], 106 | ], 107 | ), 108 | green: ColorInformation::new( 109 | [63, 178], 110 | vec![ 111 | [30, 100], 112 | [40, 90], 113 | [50, 85], 114 | [60, 81], 115 | [70, 74], 116 | [80, 64], 117 | [90, 50], 118 | [100, 40], 119 | ], 120 | ), 121 | blue: ColorInformation::new( 122 | [179, 257], 123 | vec![ 124 | [20, 100], 125 | [30, 86], 126 | [40, 80], 127 | [50, 74], 128 | [60, 60], 129 | [70, 52], 130 | [80, 44], 131 | [90, 39], 132 | [100, 35], 133 | ], 134 | ), 135 | purple: ColorInformation::new( 136 | [258, 282], 137 | vec![ 138 | [20, 100], 139 | [30, 87], 140 | [40, 79], 141 | [50, 70], 142 | [60, 65], 143 | [70, 59], 144 | [80, 52], 145 | [90, 45], 146 | [100, 42], 147 | ], 148 | ), 149 | pink: ColorInformation::new( 150 | [283, 334], 151 | vec![ 152 | [20, 100], 153 | [30, 90], 154 | [40, 86], 155 | [60, 84], 156 | [80, 80], 157 | [90, 75], 158 | [100, 73], 159 | ], 160 | ), 161 | } 162 | } 163 | 164 | /// Get the saturation range for the given hue. 165 | /// 166 | /// Parameters: 167 | /// * `hue`: The hue to get the saturation range for. 168 | pub fn get_saturation_range(&self, hue: &i64) -> (i64, i64) { 169 | let color = &self.get_color_from_hue(hue); 170 | (color.saturation_range[0], color.saturation_range[1]) 171 | } 172 | 173 | /// Get the minimum value for the given hue and saturation. 174 | /// 175 | /// Parameters: 176 | /// * `hue`: The hue to get the minimum value for. 177 | /// * `saturation`: The saturation to get the minimum value for. 178 | pub fn get_minimum_value(&self, hue: &i64, saturation: &i64) -> i64 { 179 | let mut minimum_value = 0; 180 | let lower_bounds = &self.get_color_from_hue(hue).lower_bounds; 181 | for i in 0..lower_bounds.len() - 1 { 182 | let s1 = lower_bounds[i][0]; 183 | let v1 = lower_bounds[i][1]; 184 | 185 | let s2 = lower_bounds[i + 1][0]; 186 | let v2 = lower_bounds[i + 1][1]; 187 | 188 | if saturation >= &s1 && saturation <= &s2 { 189 | let m = (v2 - v1) / (s2 - s1); 190 | let b = v1 - m * s1; 191 | 192 | minimum_value = m * saturation + b; 193 | } 194 | } 195 | 196 | minimum_value 197 | } 198 | 199 | /// Get the color information for the given gamut. 200 | /// 201 | /// Parameters: 202 | /// * `gamut`: The gamut to get the color information for. 203 | pub fn get_color_from_gamut(&self, gamut: &Gamut) -> &ColorInformation { 204 | match gamut { 205 | Gamut::Monochrome => &self.monochrome, 206 | Gamut::Red => &self.red, 207 | Gamut::Orange => &self.orange, 208 | Gamut::Yellow => &self.yellow, 209 | Gamut::Green => &self.green, 210 | Gamut::Blue => &self.blue, 211 | Gamut::Purple => &self.purple, 212 | Gamut::Pink => &self.pink, 213 | } 214 | } 215 | 216 | /// Get the color information for the given hue. 217 | /// 218 | /// Parameters: 219 | /// * `hue`: The hue to get the color information for. 220 | fn get_color_from_hue(&self, hue: &i64) -> &ColorInformation { 221 | if self.monochrome.has_between_range(hue) { 222 | &self.monochrome 223 | } else if self.red.has_between_range(hue) { 224 | &self.red 225 | } else if self.orange.has_between_range(hue) { 226 | &self.orange 227 | } else if self.yellow.has_between_range(hue) { 228 | &self.yellow 229 | } else if self.green.has_between_range(hue) { 230 | &self.green 231 | } else if self.blue.has_between_range(hue) { 232 | &self.blue 233 | } else if self.purple.has_between_range(hue) { 234 | &self.purple 235 | } else { 236 | &self.pink 237 | } 238 | } 239 | } 240 | 241 | impl Default for ColorDictionary { 242 | fn default() -> Self { 243 | ColorDictionary::new() 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library for generating attractive random colors with a variety of options. 2 | //! Inspired by [randomColor](https://github.com/davidmerfield/randomColor). 3 | //! 4 | //! # Examples 5 | //! 6 | //! ```rust 7 | //! use random_color::RandomColor; 8 | //! 9 | //! let mut random_color = RandomColor::new(); 10 | //! 11 | //! let color = random_color.to_hex(); 12 | //! println!("{}", color); 13 | //! ``` 14 | //! 15 | //! ```rust 16 | //! use random_color::RandomColor; 17 | //! use random_color::options::{Gamut, Luminosity}; 18 | //! 19 | //! let mut random_color = RandomColor{ 20 | //! hue: Some(Gamut::Blue), 21 | //! luminosity: Some(Luminosity::Dark), 22 | //! ..Default::default() 23 | //! }; 24 | //! 25 | //! let color = random_color.to_hsl_string(); 26 | //! println!("{}", color); 27 | //! ``` 28 | //! 29 | //! ```rust 30 | //! use random_color::RandomColor; 31 | //! 32 | //! let mut random_color = RandomColor::new(); 33 | //! 34 | //! random_color.seed("A random seed"); 35 | //! 36 | //! let color = random_color.to_rgb_string(); 37 | //! println!("{}", color); 38 | //! ``` 39 | #[cfg(feature = "ecolor_support")] 40 | extern crate ecolor; 41 | #[cfg(feature = "palette_support")] 42 | extern crate palette; 43 | extern crate rand; 44 | #[cfg(feature = "rgb_support")] 45 | extern crate rgb; 46 | 47 | pub mod color_dictionary; 48 | pub mod options; 49 | 50 | use color_dictionary::ColorDictionary; 51 | #[cfg(feature = "ecolor_support")] 52 | use ecolor::{Color32, Rgba}; 53 | use options::{Gamut, Luminosity, Seed}; 54 | #[cfg(feature = "palette_support")] 55 | use palette::{Srgb, Srgba}; 56 | use rand::rngs::SmallRng; 57 | use rand::{Rng, SeedableRng}; 58 | #[cfg(feature = "rgb_support")] 59 | use rgb::Rgb; 60 | 61 | /// A structure for generating random colors with a variety of options. 62 | /// 63 | /// The available options are: 64 | /// * `hue`: Specify a specific hue, or a range of hues. You can use the 65 | /// `Gamut` enum to select a hue. 66 | /// * `luminosity`: Specify a specific luminosity, or a range of luminosities. 67 | /// You can use the `Luminosity` enum to select a luminosity. 68 | /// * `seed`: Specify a seed for the random number generator. If you don't 69 | /// specify a seed, one will be generated randomly. 70 | /// * `alpha`: Specify an alpha value for the generated color. If you don't 71 | /// specify an alpha value, 1.0 will be used. 72 | /// * `color_dictionary`: Specify a custom color dictionary. If you don't 73 | /// specify a color dictionary, the default one will be used. 74 | #[derive(Debug, PartialEq, Clone)] 75 | pub struct RandomColor { 76 | /// The hue of the color to generate. 77 | pub hue: Option, 78 | /// The luminosity of the color to generate. 79 | pub luminosity: Option, 80 | /// The seed for the random number generator. 81 | pub seed: SmallRng, 82 | /// The alpha value of the color to generate. 83 | pub alpha: Option, 84 | /// The color dictionary to use. 85 | pub color_dictionary: ColorDictionary, 86 | } 87 | 88 | impl RandomColor { 89 | /// Creates a new `RandomColor` instance. 90 | pub fn new() -> Self { 91 | RandomColor { 92 | hue: None, 93 | luminosity: None, 94 | seed: SmallRng::from_os_rng(), 95 | alpha: Some(1.0), 96 | color_dictionary: ColorDictionary::new(), 97 | } 98 | } 99 | 100 | /// Sets the hue setting. 101 | pub fn hue(&mut self, hue: Gamut) -> &mut RandomColor { 102 | self.hue = Some(hue); 103 | 104 | self 105 | } 106 | 107 | /// Removes the luminosity setting. 108 | pub fn luminosity(&mut self, luminosity: Luminosity) -> &mut RandomColor { 109 | self.luminosity = Some(luminosity); 110 | self 111 | } 112 | 113 | /// Sets the seed. 114 | pub fn seed(&mut self, seed: T) -> &mut RandomColor { 115 | self.seed = SmallRng::seed_from_u64(seed.to_value()); 116 | 117 | self 118 | } 119 | 120 | /// Sets the alpha setting. 121 | pub fn alpha(&mut self, alpha: f32) -> &mut RandomColor { 122 | if alpha < 1.0 { 123 | self.alpha = Some(alpha); 124 | } 125 | 126 | self 127 | } 128 | 129 | /// Removes the alpha setting. 130 | pub fn random_alpha(&mut self) -> &mut RandomColor { 131 | self.alpha = None; 132 | 133 | self 134 | } 135 | 136 | /// Sets the ColorDictionary. 137 | pub fn dictionary(&mut self, dictionary: ColorDictionary) -> &mut RandomColor { 138 | self.color_dictionary = dictionary; 139 | 140 | self 141 | } 142 | 143 | /// Generates a random color and returns it as an HSV array. 144 | pub fn to_hsv_array(&mut self) -> [u32; 3] { 145 | let (h, s, b) = self.generate_color(); 146 | 147 | [h as u32, s as u32, b as u32] 148 | } 149 | 150 | /// Generates a random color and returns it as an RGB string. 151 | pub fn to_rgb_string(&mut self) -> String { 152 | let (h, s, b) = self.generate_color(); 153 | let rgb = self.hsv_to_rgb(h, s, b); 154 | 155 | format!("rgb({}, {}, {})", rgb[0], rgb[1], rgb[2]) 156 | } 157 | 158 | /// Generates a random color and returns it as an RGBA string. 159 | pub fn to_rgba_string(&mut self) -> String { 160 | let (h, s, b) = self.generate_color(); 161 | let rgb = self.hsv_to_rgb(h, s, b); 162 | let a: f32 = match self.alpha { 163 | Some(alpha) => alpha, 164 | None => self.seed.random_range(0.0..1.0), 165 | }; 166 | 167 | format!("rgba({}, {}, {}, {})", rgb[0], rgb[1], rgb[2], a) 168 | } 169 | 170 | /// Generates a random color and returns it as an RGB array. 171 | pub fn to_rgb_array(&mut self) -> [u8; 3] { 172 | let (h, s, b) = self.generate_color(); 173 | 174 | self.hsv_to_rgb(h, s, b) 175 | } 176 | 177 | /// Generates a random color and returns it as an RGB array. 178 | pub fn to_rgba_array(&mut self) -> [u8; 4] { 179 | let (h, s, b) = self.generate_color(); 180 | let rgb: [u8; 3] = self.hsv_to_rgb(h, s, b); 181 | 182 | let alpha = match self.alpha { 183 | Some(alpha) => (alpha * 255.0) as u8, 184 | None => self.random_within(0, 255) as u8, 185 | }; 186 | 187 | [rgb[0], rgb[1], rgb[2], alpha] 188 | } 189 | 190 | /// Generates a random color and returns it as a `f32` RGB array. 191 | pub fn to_f32_rgb_array(&mut self) -> [f32; 3] { 192 | let (h, s, b) = self.generate_color(); 193 | let rgb: [u8; 3] = self.hsv_to_rgb(h, s, b); 194 | 195 | [ 196 | rgb[0] as f32 / 255.0, 197 | rgb[1] as f32 / 255.0, 198 | rgb[2] as f32 / 255.0, 199 | ] 200 | } 201 | 202 | /// Generates a random color and returns it as an `f32` RGBA array. 203 | pub fn to_f32_rgba_array(&mut self) -> [f32; 4] { 204 | let (h, s, b) = self.generate_color(); 205 | let rgb: [u8; 3] = self.hsv_to_rgb(h, s, b); 206 | 207 | let alpha: f32 = match self.alpha { 208 | Some(alpha) => alpha, 209 | None => self.seed.random_range(0.0..1.0), 210 | }; 211 | 212 | [ 213 | rgb[0] as f32 / 255.0, 214 | rgb[1] as f32 / 255.0, 215 | rgb[2] as f32 / 255.0, 216 | alpha, 217 | ] 218 | } 219 | 220 | /// Generates a random color and returns it as an HSL string. 221 | pub fn to_hsl_string(&mut self) -> String { 222 | let (h, s, b) = self.generate_color(); 223 | let hsv = self.hsv_to_hsl(h, s, b); 224 | 225 | format!("hsl({}, {}%, {}%)", hsv[0], hsv[1], hsv[2]) 226 | } 227 | 228 | /// Generates a random color and returns it as an HSLA string. 229 | pub fn to_hsla_string(&mut self) -> String { 230 | let (h, s, b) = self.generate_color(); 231 | let hsv = self.hsv_to_hsl(h, s, b); 232 | let a: f32 = match self.alpha { 233 | Some(alpha) => alpha, 234 | None => rand::random(), 235 | }; 236 | 237 | format!("hsl({}, {}%, {}%, {})", hsv[0], hsv[1], hsv[2], a) 238 | } 239 | 240 | /// Generates a random color and returns it as an HSL array. 241 | pub fn to_hsl_array(&mut self) -> [u32; 3] { 242 | let (h, s, b) = self.generate_color(); 243 | 244 | self.hsv_to_hsl(h, s, b) 245 | } 246 | 247 | /// Generates a random color and returns it as a hex string. 248 | pub fn to_hex(&mut self) -> String { 249 | let (h, s, b) = self.generate_color(); 250 | let [r, g, b] = self.hsv_to_rgb(h, s, b); 251 | 252 | format!("#{:02x}{:02x}{:02x}", r, g, b) 253 | } 254 | 255 | /// Transforms the `RandomColor` into a `u8` array with the color's RGB values. 256 | pub fn into_rgb_array(self) -> [u8; 3] { 257 | self.clone().to_rgb_array() 258 | } 259 | 260 | /// Transforms the `RandomColor` into a `u8` array with the color's RGBA values. 261 | pub fn into_rgba_array(self) -> [u8; 4] { 262 | self.clone().to_rgba_array() 263 | } 264 | 265 | /// Transforms the `RandomColor` into a `f32` array with the color's RGB values. 266 | pub fn into_f32_rgb_array(self) -> [f32; 3] { 267 | self.clone().to_f32_rgb_array() 268 | } 269 | 270 | /// Transforms the `RandomColor` into a `f32` array with the color's RGBA values. 271 | pub fn into_f32_rgba_array(self) -> [f32; 4] { 272 | self.clone().to_f32_rgba_array() 273 | } 274 | 275 | /// Generates a random color based on the settings. 276 | fn generate_color(&mut self) -> (i64, i64, i64) { 277 | let h = self.pick_hue(); 278 | let s = self.pick_saturation(&h); 279 | let b = self.pick_brightness(&h, &s); 280 | 281 | (h, s, b) 282 | } 283 | 284 | /// Picks a random hue based on the hue setting. 285 | fn pick_hue(&mut self) -> i64 { 286 | match self.hue { 287 | None => self.random_within(0, 361), 288 | Some(ref gamut) => { 289 | let color = self.color_dictionary.get_color_from_gamut(gamut); 290 | self.random_within(color.range[0], color.range[1]) 291 | } 292 | } 293 | } 294 | 295 | /// Picks a random saturation value based on the hue and luminosity setting. 296 | /// 297 | /// Parameters: 298 | /// * `hue`: The hue of the color. 299 | fn pick_saturation(&mut self, hue: &i64) -> i64 { 300 | let s_range: (i64, i64) = self.color_dictionary.get_saturation_range(hue); 301 | 302 | let s_min = s_range.0; 303 | let s_max = s_range.1; 304 | 305 | match self.luminosity { 306 | Some(Luminosity::Random) => self.random_within(0, 100), 307 | Some(Luminosity::Bright) => self.random_within(55, s_max), 308 | Some(Luminosity::Dark) => self.random_within(s_max - 10, s_max), 309 | Some(Luminosity::Light) => self.random_within(s_min, 55), 310 | _ => self.random_within(s_min, s_max), 311 | } 312 | } 313 | 314 | /// Picks a random brightness value based on the hue and saturation, as well 315 | /// as the luminosity setting. 316 | /// 317 | /// Parameters: 318 | /// * `hue`: The hue of the color. 319 | /// * `saturation`: The saturation of the color. 320 | fn pick_brightness(&mut self, hue: &i64, saturation: &i64) -> i64 { 321 | let b_min = self.color_dictionary.get_minimum_value(hue, saturation); 322 | let b_max = 100; 323 | 324 | match self.luminosity { 325 | Some(Luminosity::Random) => self.random_within(0, 100), 326 | Some(Luminosity::Light) => self.random_within((b_max + b_min) / 2, b_max), 327 | Some(Luminosity::Dark) => self.random_within(b_min, b_min + 20), 328 | _ => self.random_within(b_min, b_max), 329 | } 330 | } 331 | 332 | /// Generates a random i64 within the given range. 333 | /// 334 | /// This function first ensures that `min` is less than or equal to `max`. 335 | /// If `min` is equal to `max`, it increments `max` by 1 to ensure that the 336 | /// range is not empty. It uses the `SmallRng` in the seed property to 337 | /// generate the random number. 338 | /// 339 | /// Parameters: 340 | /// * `min`: The minimum value of the range. 341 | /// * `max`: The maximum value of the range. 342 | fn random_within(&mut self, mut min: i64, mut max: i64) -> i64 { 343 | if min > max { 344 | std::mem::swap(&mut min, &mut max); 345 | } 346 | 347 | if min == max { 348 | max += 1; 349 | } 350 | 351 | self.seed.random_range(min..max) 352 | } 353 | 354 | /// Convert a color from HSV to RGB. 355 | /// 356 | /// Parameters: 357 | /// * `hue`: The hue of the color in the range [0, 360). 358 | /// * `saturation`: The saturation of the color in the range [0, 100]. 359 | /// * `brightness`: The brightness of the color in the range [0, 100]. 360 | fn hsv_to_rgb(&self, mut hue: i64, saturation: i64, brightness: i64) -> [u8; 3] { 361 | if hue == 0 { 362 | hue = 1; 363 | } 364 | 365 | if hue == 360 { 366 | hue = 359; 367 | } 368 | 369 | let h: f32 = hue as f32 / 360.0; 370 | let s: f32 = saturation as f32 / 100.0; 371 | let v: f32 = brightness as f32 / 100.0; 372 | 373 | let h_i = (h * 6.0).floor(); 374 | let f = h * 6.0 - h_i; 375 | let p = v * (1.0 - s); 376 | let q = v * (1.0 - f * s); 377 | let t = v * (1.0 - (1.0 - f) * s); 378 | 379 | let (r, g, b) = match h_i as i64 { 380 | 0 => (v, t, p), 381 | 1 => (q, v, p), 382 | 2 => (p, v, t), 383 | 3 => (p, q, v), 384 | 4 => (t, p, v), 385 | _ => (v, p, q), 386 | }; 387 | 388 | [ 389 | (r * 255.0).floor() as u8, 390 | (g * 255.0).floor() as u8, 391 | (b * 255.0).floor() as u8, 392 | ] 393 | } 394 | 395 | /// Convert a color from HSV to HSL. 396 | /// 397 | /// Parameters: 398 | /// * `hue`: The hue of the color in the range [0, 360). 399 | /// * `saturation`: The saturation of the color in the range [0, 100]. 400 | /// * `brightness`: The brightness of the color in the range [0, 100]. 401 | fn hsv_to_hsl(&self, hue: i64, saturation: i64, brightness: i64) -> [u32; 3] { 402 | let h = hue; 403 | let s = saturation as f32 / 100.0; 404 | let v = brightness as f32 / 100.0; 405 | let mut k = (2.0 - s) * v; 406 | 407 | if k > 1.0 { 408 | k = 2.0 - k; 409 | } 410 | 411 | [ 412 | h as u32, 413 | ((s * v / k * 10000.0) / 100.0) as u32, 414 | (k / 2.0 * 100.0) as u32, 415 | ] 416 | } 417 | 418 | /* Optional Features */ 419 | 420 | /* `rgb` crate support */ 421 | 422 | /// Generates a random color and returns it as an `Rgb` struct from the `rgb` crate. 423 | #[cfg(feature = "rgb_support")] 424 | pub fn to_rgb(&mut self) -> Rgb { 425 | Rgb::from(self) 426 | } 427 | 428 | /// Generates a random color and returns it as an `Rgba` struct from the `rgb` crate. 429 | #[cfg(feature = "rgb_support")] 430 | pub fn to_rgba(&mut self) -> rgb::Rgba { 431 | rgb::Rgba::from(self) 432 | } 433 | 434 | /* `palette` crate support */ 435 | 436 | /// Generates a random color and returns it as an `Srgb` struct from the `palette` crate. 437 | #[cfg(feature = "palette_support")] 438 | pub fn to_srgb(&mut self) -> Srgb { 439 | Srgb::from(self) 440 | } 441 | 442 | /// Generates a random color and returns it as an `Srgba` struct from the `palette` crate. 443 | #[cfg(feature = "palette_support")] 444 | pub fn to_srgba(&mut self) -> Srgba { 445 | Srgba::from(self) 446 | } 447 | 448 | /* `ecolor` crate support` */ 449 | 450 | /// Generates a random color and returns it as an `Color32` struct from the `ecolor` crate. 451 | #[cfg(feature = "ecolor_support")] 452 | pub fn to_color32(&mut self) -> Color32 { 453 | Color32::from(self) 454 | } 455 | } 456 | 457 | impl Default for RandomColor { 458 | fn default() -> Self { 459 | RandomColor::new() 460 | } 461 | } 462 | 463 | #[cfg(feature = "rgb_support")] 464 | impl From for Rgb { 465 | fn from(value: RandomColor) -> Self { 466 | let rgb = value.into_rgb_array(); 467 | 468 | Rgb { 469 | r: rgb[0], 470 | g: rgb[1], 471 | b: rgb[2], 472 | } 473 | } 474 | } 475 | 476 | #[cfg(feature = "rgb_support")] 477 | impl From<&mut RandomColor> for Rgb { 478 | fn from(value: &mut RandomColor) -> Self { 479 | let rgb = value.to_rgb_array(); 480 | 481 | Rgb { 482 | r: rgb[0], 483 | g: rgb[1], 484 | b: rgb[2], 485 | } 486 | } 487 | } 488 | 489 | #[cfg(feature = "rgb_support")] 490 | impl From for rgb::Rgba { 491 | fn from(value: RandomColor) -> Self { 492 | let rgba = value.into_rgba_array(); 493 | 494 | rgb::Rgba { 495 | r: rgba[0], 496 | g: rgba[1], 497 | b: rgba[2], 498 | a: rgba[3], 499 | } 500 | } 501 | } 502 | 503 | #[cfg(feature = "rgb_support")] 504 | impl From<&mut RandomColor> for rgb::Rgba { 505 | fn from(value: &mut RandomColor) -> Self { 506 | let rgba = value.to_rgba_array(); 507 | 508 | rgb::Rgba { 509 | r: rgba[0], 510 | g: rgba[1], 511 | b: rgba[2], 512 | a: rgba[3], 513 | } 514 | } 515 | } 516 | 517 | #[cfg(feature = "palette_support")] 518 | impl From for Srgb { 519 | fn from(value: RandomColor) -> Self { 520 | let rgb = value.into_f32_rgb_array(); 521 | 522 | Srgb::new(rgb[0], rgb[1], rgb[2]) 523 | } 524 | } 525 | 526 | #[cfg(feature = "palette_support")] 527 | impl From<&mut RandomColor> for Srgb { 528 | fn from(value: &mut RandomColor) -> Self { 529 | let rgb = value.to_f32_rgb_array(); 530 | 531 | Srgb::new(rgb[0], rgb[1], rgb[2]) 532 | } 533 | } 534 | 535 | #[cfg(feature = "palette_support")] 536 | impl From for Srgba { 537 | fn from(value: RandomColor) -> Self { 538 | let rgba = value.into_f32_rgba_array(); 539 | 540 | Srgba::new(rgba[0], rgba[1], rgba[2], rgba[3]) 541 | } 542 | } 543 | 544 | #[cfg(feature = "palette_support")] 545 | impl From<&mut RandomColor> for Srgba { 546 | fn from(value: &mut RandomColor) -> Self { 547 | let rgba = value.to_f32_rgba_array(); 548 | 549 | Srgba::new(rgba[0], rgba[1], rgba[2], rgba[3]) 550 | } 551 | } 552 | 553 | #[cfg(feature = "ecolor_support")] 554 | impl From for Color32 { 555 | fn from(value: RandomColor) -> Self { 556 | let rgba = value.into_rgba_array(); 557 | Color32::from_rgba_unmultiplied(rgba[0], rgba[1], rgba[2], rgba[3]) 558 | } 559 | } 560 | 561 | #[cfg(feature = "ecolor_support")] 562 | impl From<&mut RandomColor> for Color32 { 563 | fn from(value: &mut RandomColor) -> Self { 564 | let rgba = value.to_rgba_array(); 565 | Color32::from_rgba_unmultiplied(rgba[0], rgba[1], rgba[2], rgba[3]) 566 | } 567 | } 568 | 569 | #[cfg(feature = "ecolor_support")] 570 | impl From for Rgba { 571 | fn from(value: RandomColor) -> Self { 572 | let rgba = value.into_f32_rgba_array(); 573 | Rgba::from_rgba_unmultiplied(rgba[0], rgba[1], rgba[2], rgba[3]) 574 | } 575 | } 576 | 577 | #[cfg(feature = "ecolor_support")] 578 | impl From<&mut RandomColor> for Rgba { 579 | fn from(value: &mut RandomColor) -> Self { 580 | let rgba = value.to_f32_rgba_array(); 581 | Rgba::from_rgba_unmultiplied(rgba[0], rgba[1], rgba[2], rgba[3]) 582 | } 583 | } 584 | 585 | #[cfg(test)] 586 | mod tests { 587 | use super::*; 588 | 589 | #[test] 590 | fn generates_different_colors_using_the_same_instance() { 591 | let mut rc = RandomColor::new(); 592 | 593 | assert_ne!(rc.to_rgb_string(), rc.to_rgb_string()); 594 | } 595 | 596 | #[test] 597 | fn generates_color_as_hsv_array() { 598 | let test_case = RandomColor::new() 599 | .hue(Gamut::Blue) 600 | .luminosity(Luminosity::Light) 601 | .seed(42) 602 | .alpha(1.0) 603 | .to_hsv_array(); 604 | 605 | assert_eq!(test_case, [242, 31, 99]); 606 | } 607 | 608 | #[test] 609 | fn generates_color_as_rgb_string() { 610 | let test_case = RandomColor::new() 611 | .hue(Gamut::Blue) 612 | .luminosity(Luminosity::Light) 613 | .seed(42) 614 | .alpha(1.0) 615 | .to_rgb_string(); 616 | 617 | assert_eq!(test_case, "rgb(176, 174, 252)"); 618 | } 619 | 620 | #[test] 621 | fn generates_color_as_rgba_string() { 622 | let test_case = RandomColor::new() 623 | .hue(Gamut::Blue) 624 | .luminosity(Luminosity::Light) 625 | .seed(42) 626 | .alpha(1.0) 627 | .to_rgba_string(); 628 | 629 | assert_eq!(test_case, "rgba(176, 174, 252, 1)"); 630 | } 631 | 632 | #[test] 633 | fn generates_color_as_rgb_array() { 634 | let test_case = RandomColor::new() 635 | .hue(Gamut::Blue) 636 | .luminosity(Luminosity::Light) 637 | .seed(42) 638 | .alpha(1.0) 639 | .to_rgb_array(); 640 | 641 | assert_eq!(test_case, [176, 174, 252]); 642 | } 643 | 644 | #[test] 645 | fn generates_color_as_f32_rgb_array() { 646 | let test_case = RandomColor::new() 647 | .hue(Gamut::Blue) 648 | .luminosity(Luminosity::Light) 649 | .seed(42) 650 | .alpha(1.0) 651 | .to_f32_rgb_array(); 652 | 653 | assert_eq!(test_case, [0.6901961, 0.68235296, 0.9882353]); 654 | } 655 | 656 | #[test] 657 | fn generates_color_as_f32_rgba_array() { 658 | let test_case = RandomColor::new() 659 | .hue(Gamut::Blue) 660 | .luminosity(Luminosity::Light) 661 | .seed(42) 662 | .alpha(1.0) 663 | .to_f32_rgba_array(); 664 | 665 | assert_eq!(test_case, [0.6901961, 0.68235296, 0.9882353, 1.0]); 666 | } 667 | 668 | #[test] 669 | fn generates_color_as_hsl_string() { 670 | let test_case = RandomColor::new() 671 | .hue(Gamut::Blue) 672 | .luminosity(Luminosity::Light) 673 | .seed(42) 674 | .alpha(1.0) 675 | .to_hsl_string(); 676 | 677 | assert_eq!(test_case, "hsl(242, 93%, 16%)"); 678 | } 679 | 680 | #[test] 681 | fn generates_color_as_hsla_string() { 682 | let test_case = RandomColor::new() 683 | .hue(Gamut::Blue) 684 | .luminosity(Luminosity::Light) 685 | .seed(42) 686 | .alpha(1.0) 687 | .to_hsla_string(); 688 | 689 | assert_eq!(test_case, "hsl(242, 93%, 16%, 1)"); 690 | } 691 | 692 | #[test] 693 | fn generates_color_as_hsl_array() { 694 | let test_case = RandomColor::new() 695 | .hue(Gamut::Blue) 696 | .luminosity(Luminosity::Light) 697 | .seed(42) 698 | .alpha(1.0) 699 | .to_hsl_array(); 700 | 701 | assert_eq!(test_case, [242, 93, 16]); 702 | } 703 | 704 | #[test] 705 | fn generates_color_as_hex() { 706 | let test_case = RandomColor::new() 707 | .hue(Gamut::Blue) 708 | .luminosity(Luminosity::Light) 709 | .seed(42) 710 | .alpha(1.0) 711 | .to_hex(); 712 | 713 | assert_eq!(test_case, "#b0aefc"); 714 | } 715 | 716 | #[test] 717 | fn to_hex_is_rrggbb() { 718 | let test_case = RandomColor::new() 719 | .hue(Gamut::Blue) 720 | .luminosity(Luminosity::Light) 721 | .seed(42) 722 | .alpha(1.0) 723 | .to_hex(); 724 | let [r, g, b] = RandomColor::new() 725 | .hue(Gamut::Blue) 726 | .luminosity(Luminosity::Light) 727 | .seed(42) 728 | .alpha(1.0) 729 | .to_rgb_array(); 730 | 731 | assert_eq!(test_case, format!("#{:02x}{:02x}{:02x}", r, g, b).as_str()); 732 | } 733 | 734 | #[test] 735 | fn single_digit_hex_are_padded_by_to_two_chars() { 736 | let test_case = RandomColor::new() 737 | .luminosity(Luminosity::Dark) 738 | .seed(5) 739 | .to_hex(); 740 | 741 | assert_eq!(test_case, "#207204"); 742 | } 743 | 744 | /* Optional Feature Tests */ 745 | 746 | #[test] 747 | #[cfg(feature = "rgb_support")] 748 | fn generates_color_as_rgb_from_rgb_crate() { 749 | let test_case = RandomColor::new() 750 | .hue(Gamut::Blue) 751 | .luminosity(Luminosity::Light) 752 | .seed(42) 753 | .alpha(1.0) 754 | .to_rgb(); 755 | 756 | assert_eq!(test_case, Rgb::new(176, 174, 252)); 757 | } 758 | 759 | #[test] 760 | #[cfg(feature = "rgb_support")] 761 | fn generates_color_as_rgba_from_rgb_crate() { 762 | let test_case = RandomColor::new() 763 | .hue(Gamut::Blue) 764 | .luminosity(Luminosity::Light) 765 | .seed(42) 766 | .alpha(0.69) 767 | .to_rgba(); 768 | 769 | assert_eq!(test_case, rgb::Rgba::new(176, 174, 252, 175)); 770 | } 771 | 772 | #[test] 773 | #[cfg(feature = "palette_support")] 774 | fn can_be_transformed_into_srgba_from_palette_crate() { 775 | let mut rc = RandomColor::new(); 776 | 777 | let test_case = rc 778 | .hue(Gamut::Blue) 779 | .luminosity(Luminosity::Light) 780 | .seed(42) 781 | .alpha(1.0); 782 | 783 | let converted = Srgba::from(test_case); 784 | 785 | assert_eq!( 786 | converted.into_components(), 787 | (0.6901961, 0.68235296, 0.9882353, 1.0) 788 | ); 789 | } 790 | 791 | #[test] 792 | #[cfg(feature = "palette_support")] 793 | fn can_be_transformed_into_srgb_from_palette_crate() { 794 | let mut rc = RandomColor::new(); 795 | 796 | let test_case = rc 797 | .hue(Gamut::Blue) 798 | .luminosity(Luminosity::Light) 799 | .seed(42) 800 | .alpha(1.0); 801 | 802 | let converted = Srgb::from(test_case); 803 | 804 | assert_eq!( 805 | converted.into_components(), 806 | (0.6901961, 0.68235296, 0.9882353) 807 | ); 808 | } 809 | 810 | #[test] 811 | #[cfg(feature = "ecolor_support")] 812 | fn can_be_transformed_into_color32_from_ecolor_crate() { 813 | let mut rc = RandomColor::new(); 814 | 815 | let test_case = rc 816 | .hue(Gamut::Blue) 817 | .luminosity(Luminosity::Light) 818 | .seed(42) 819 | .alpha(1.0); 820 | 821 | let converted = Color32::from(test_case); 822 | 823 | assert_eq!(converted.to_array(), [176, 174, 252, 255]); 824 | } 825 | 826 | #[test] 827 | #[cfg(feature = "ecolor_support")] 828 | fn can_be_transformed_into_rgba_from_ecolor_crate() { 829 | let mut rc = RandomColor::new(); 830 | 831 | let test_case = rc 832 | .hue(Gamut::Blue) 833 | .luminosity(Luminosity::Light) 834 | .seed(42) 835 | .alpha(1.0); 836 | 837 | let converted = Rgba::from(test_case); 838 | 839 | assert_eq!( 840 | converted.to_array(), 841 | [0.6901961, 0.68235296, 0.9882353, 1.0] 842 | ); 843 | } 844 | } 845 | --------------------------------------------------------------------------------