Re-initialize app using vite.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -0,0 +1,54 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
extends: [
|
||||
// Remove ...tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default tseslint.config({
|
||||
plugins: {
|
||||
// Add the react-x and react-dom plugins
|
||||
'react-x': reactX,
|
||||
'react-dom': reactDom,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended typescript rules
|
||||
...reactX.configs['recommended-typescript'].rules,
|
||||
...reactDom.configs.recommended.rules,
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
target
|
||||
pkg
|
||||
Generated
+140
@@ -0,0 +1,140 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "colorlib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "colorlib"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4.3"
|
||||
wasm-bindgen = "0.2.92"
|
||||
@@ -0,0 +1,2 @@
|
||||
max_width = 79
|
||||
wrap_comments = true
|
||||
@@ -0,0 +1,379 @@
|
||||
//! Color Library
|
||||
//!
|
||||
//! This module contains structures representing Hex, RGB, HSV, and HCL color
|
||||
//! spaces and methods for converting among them. The `Color` struct is a
|
||||
//! container for a color represented in all 4 color spaces.
|
||||
//!
|
||||
//! Example:
|
||||
//!
|
||||
//! ```
|
||||
//! use color::Color;
|
||||
//!
|
||||
//! let color = Color::from_hex("F00");
|
||||
//! ```
|
||||
|
||||
pub mod calc;
|
||||
pub mod hcl;
|
||||
pub mod hex;
|
||||
pub mod hsv;
|
||||
pub mod rgb;
|
||||
pub mod util;
|
||||
|
||||
pub use hcl::HCL;
|
||||
pub use hex::Hex;
|
||||
pub use hsv::HSV;
|
||||
pub use rgb::RGB;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// -----
|
||||
// Enums
|
||||
// -----
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Component {
|
||||
RGB_R,
|
||||
RGB_G,
|
||||
RGB_B,
|
||||
HSV_H,
|
||||
HSV_S,
|
||||
HSV_V,
|
||||
HCL_H,
|
||||
HCL_C,
|
||||
HCL_L,
|
||||
}
|
||||
|
||||
// ----------------
|
||||
// Color Structures
|
||||
// ----------------
|
||||
//
|
||||
/// Represents a color in various spaces.
|
||||
///
|
||||
/// `Color` holds a single color in four color spaces: Hex, RGB, HSV, and
|
||||
/// HCL. It can be constructed from any of those four components.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::Color;
|
||||
///
|
||||
/// let red = Color::from_hex("F00");
|
||||
/// ```
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Color {
|
||||
pub hex: Hex,
|
||||
pub rgb: RGB,
|
||||
pub hsv: HSV,
|
||||
pub hcl: HCL,
|
||||
}
|
||||
|
||||
// Color Methods
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Color {
|
||||
/// Creates a new Color struct from a hex code string.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::Color;
|
||||
///
|
||||
/// let red = Color::from_hex("FF0000");
|
||||
/// ```
|
||||
pub fn from_hex(code: &str) -> Color {
|
||||
let hex = Hex::from_code(&code);
|
||||
let rgb = RGB::from_hex(&code);
|
||||
let hsv = HSV::from_hex(&code);
|
||||
let hcl = HCL::from_hex(&code);
|
||||
Color { hex, rgb, hsv, hcl }
|
||||
}
|
||||
|
||||
/// Creates a new Color struct from floating point RGB values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::Color;
|
||||
///
|
||||
/// let red = Color::from_rgb(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
pub fn from_rgb(r: f64, g: f64, b: f64) -> Color {
|
||||
let hex = Hex::from_rgb(r, g, b);
|
||||
let rgb = RGB::new(r, g, b);
|
||||
let hsv = HSV::from_rgb(r, g, b);
|
||||
let hcl = HCL::from_rgb(r, g, b);
|
||||
Color { hex, rgb, hsv, hcl }
|
||||
}
|
||||
|
||||
/// Creates a new Color struct from floating point HSV values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::Color;
|
||||
///
|
||||
/// let red = Color::from_hsv(0.0, 1.0, 1.0);
|
||||
/// ```
|
||||
pub fn from_hsv(h: f64, s: f64, v: f64) -> Color {
|
||||
let hex = Hex::from_hsv(h, s, v);
|
||||
let rgb = RGB::from_hsv(h, s, v);
|
||||
let hsv = HSV::new(h, s, v);
|
||||
let hcl = HCL::from_hsv(h, s, v);
|
||||
Color { hex, rgb, hsv, hcl }
|
||||
}
|
||||
|
||||
/// Creates a new Color struct from floating point HCL values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::Color;
|
||||
///
|
||||
/// let red = Color::from_hcl(0.0, 1.0, 0.55);
|
||||
/// ```
|
||||
pub fn from_hcl(h: f64, c: f64, l: f64) -> Color {
|
||||
let hex = Hex::from_hcl(h, c, l);
|
||||
let rgb = RGB::from_hcl(h, c, l);
|
||||
let hsv = HSV::from_hcl(h, c, l);
|
||||
let hcl = HCL::new(h, c, l);
|
||||
Color { hex, rgb, hsv, hcl }
|
||||
}
|
||||
|
||||
pub fn get(&self, component: Component) -> f64 {
|
||||
match component {
|
||||
Component::RGB_R => self.rgb.r,
|
||||
Component::RGB_G => self.rgb.g,
|
||||
Component::RGB_B => self.rgb.b,
|
||||
Component::HSV_H => self.hsv.h,
|
||||
Component::HSV_S => self.hsv.s,
|
||||
Component::HSV_V => self.hsv.v,
|
||||
Component::HCL_H => self.hcl.h,
|
||||
Component::HCL_C => self.hcl.c,
|
||||
Component::HCL_L => self.hcl.l,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self, component: Component, value: f64) -> Color {
|
||||
match component {
|
||||
Component::RGB_R => Color::from_rgb(value, self.rgb.g, self.rgb.b),
|
||||
Component::RGB_G => Color::from_rgb(self.rgb.r, value, self.rgb.b),
|
||||
Component::RGB_B => Color::from_rgb(self.rgb.r, self.rgb.g, value),
|
||||
Component::HSV_H => Color::from_hsv(value, self.hsv.s, self.hsv.v),
|
||||
Component::HSV_S => Color::from_hsv(self.hsv.h, value, self.hsv.v),
|
||||
Component::HSV_V => Color::from_hsv(self.hsv.h, self.hsv.s, value),
|
||||
Component::HCL_H => Color::from_hcl(value, self.hcl.c, self.hcl.l),
|
||||
Component::HCL_C => Color::from_hcl(self.hcl.h, value, self.hcl.l),
|
||||
Component::HCL_L => Color::from_hcl(self.hcl.h, self.hcl.c, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod test_color {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn color_from_hex() {
|
||||
let hex_code = "4C964B";
|
||||
let (hr, hg, hb) = (76u8, 150u8, 75u8);
|
||||
let (r, g, b) = (76.0, 150.0, 75.0);
|
||||
let (h1, s, v) = (119.2, 0.5, 0.5882352941176471);
|
||||
let (h2, c, l) = (119.2, 0.5, 0.4894232967469906);
|
||||
|
||||
let color = Color::from_hex(&hex_code);
|
||||
assert_eq!(color.hex.to_code(), hex_code.to_string());
|
||||
assert_eq!(color.hex.r, hr);
|
||||
assert_eq!(color.hex.g, hg);
|
||||
assert_eq!(color.hex.b, hb);
|
||||
assert_eq!(color.rgb.r, r);
|
||||
assert_eq!(color.rgb.g, g);
|
||||
assert_eq!(color.rgb.b, b);
|
||||
assert_eq!(color.hsv.h, h1);
|
||||
assert_eq!(color.hsv.s, s);
|
||||
assert_eq!(color.hsv.v, v);
|
||||
assert_eq!(color.hcl.h, h2);
|
||||
assert_eq!(color.hcl.c, c);
|
||||
assert_eq!(color.hcl.l, l);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_from_rgb() {
|
||||
let hex_code = "4C964B";
|
||||
let (hr, hg, hb) = (76u8, 150u8, 75u8);
|
||||
let (r, g, b) = (76.0, 150.0, 75.0);
|
||||
let (h1, s, v) = (119.2, 0.5, 0.5882352941176471);
|
||||
let (h2, c, l) = (119.2, 0.5, 0.4894232967469906);
|
||||
|
||||
let color = Color::from_rgb(r, g, b);
|
||||
assert_eq!(color.hex.to_code(), hex_code.to_string());
|
||||
assert_eq!(color.hex.r, hr);
|
||||
assert_eq!(color.hex.g, hg);
|
||||
assert_eq!(color.hex.b, hb);
|
||||
assert_eq!(color.rgb.r, r);
|
||||
assert_eq!(color.rgb.g, g);
|
||||
assert_eq!(color.rgb.b, b);
|
||||
assert_eq!(color.hsv.h, h1);
|
||||
assert_eq!(color.hsv.s, s);
|
||||
assert_eq!(color.hsv.v, v);
|
||||
assert_eq!(color.hcl.h, h2);
|
||||
assert_eq!(color.hcl.c, c);
|
||||
assert_eq!(color.hcl.l, l);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_from_hsv() {
|
||||
let hex_code = "4B964B";
|
||||
let (hr, hg, hb) = (75u8, 150u8, 75u8);
|
||||
let (r, g, b) = (75.225, 150.45, 75.225);
|
||||
let (h1, s, v) = (120.0, 0.5, 0.59);
|
||||
let (h2, c, l) = (120.0, 0.5, 0.4901795844381934);
|
||||
|
||||
let color = Color::from_hsv(h1, s, v);
|
||||
assert_eq!(color.hex.to_code(), hex_code.to_string());
|
||||
assert_eq!(color.hex.r, hr);
|
||||
assert_eq!(color.hex.g, hg);
|
||||
assert_eq!(color.hex.b, hb);
|
||||
assert_eq!(color.rgb.r, r);
|
||||
assert_eq!(color.rgb.g, g);
|
||||
assert_eq!(color.rgb.b, b);
|
||||
assert_eq!(color.hsv.h, h1);
|
||||
assert_eq!(color.hsv.s, s);
|
||||
assert_eq!(color.hsv.v, v);
|
||||
assert_eq!(color.hcl.h, h2);
|
||||
assert_eq!(color.hcl.c, c);
|
||||
assert_eq!(color.hcl.l, l);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_from_hcl() {
|
||||
let hex_code = "4B964B";
|
||||
let (hr, hg, hb) = (75u8, 150u8, 75u8);
|
||||
let (r, g, b) = (75.19744022437494, 150.3948804487499, 75.19744022437494);
|
||||
let (h1, s, v) = (120.0, 0.5, 0.5897838448970584);
|
||||
let (h2, c, l) = (120.0, 0.5, 0.49);
|
||||
|
||||
let color = Color::from_hcl(h2, c, l);
|
||||
assert_eq!(color.hex.to_code(), hex_code.to_string());
|
||||
assert_eq!(color.hex.r, hr);
|
||||
assert_eq!(color.hex.g, hg);
|
||||
assert_eq!(color.hex.b, hb);
|
||||
assert_eq!(color.rgb.r, r);
|
||||
assert_eq!(color.rgb.g, g);
|
||||
assert_eq!(color.rgb.b, b);
|
||||
assert_eq!(color.hsv.h, h1);
|
||||
assert_eq!(color.hsv.s, s);
|
||||
assert_eq!(color.hsv.v, v);
|
||||
assert_eq!(color.hcl.h, h2);
|
||||
assert_eq!(color.hcl.c, c);
|
||||
assert_eq!(color.hcl.l, l);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_get {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_rgb() {
|
||||
let color = Color::from_rgb(1.0, 2.0, 3.0);
|
||||
assert_eq!(color.get(Component::RGB_R), 1.0);
|
||||
assert_eq!(color.get(Component::RGB_G), 2.0);
|
||||
assert_eq!(color.get(Component::RGB_B), 3.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_hsv() {
|
||||
let color = Color::from_hsv(120.0, 0.5, 1.0);
|
||||
assert_eq!(color.get(Component::HSV_H), 120.0);
|
||||
assert_eq!(color.get(Component::HSV_S), 0.5);
|
||||
assert_eq!(color.get(Component::HSV_V), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_hcl() {
|
||||
let color = Color::from_hcl(120.0, 0.5, 1.0);
|
||||
assert_eq!(color.get(Component::HCL_H), 120.0);
|
||||
assert_eq!(color.get(Component::HCL_C), 0.5);
|
||||
assert_eq!(color.get(Component::HCL_L), 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_update {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rgb_r() {
|
||||
let color = Color::from_rgb(0.0, 0.0, 0.0);
|
||||
let color = color.update(Component::RGB_R, 255.0);
|
||||
|
||||
assert_eq!(color, Color::from_rgb(255.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgb_g() {
|
||||
let color = Color::from_rgb(0.0, 0.0, 0.0);
|
||||
let color = color.update(Component::RGB_G, 255.0);
|
||||
|
||||
assert_eq!(color, Color::from_rgb(0.0, 255.0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgb_b() {
|
||||
let color = Color::from_rgb(0.0, 0.0, 0.0);
|
||||
let color = color.update(Component::RGB_B, 255.0);
|
||||
|
||||
assert_eq!(color, Color::from_rgb(0.0, 0.0, 255.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hsv_h() {
|
||||
let color = Color::from_hsv(120.0, 1.0, 1.0);
|
||||
let color = color.update(Component::HSV_H, 240.0);
|
||||
|
||||
assert_eq!(color, Color::from_hsv(240.0, 1.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hsv_s() {
|
||||
let color = Color::from_hsv(120.0, 1.0, 1.0);
|
||||
let color = color.update(Component::HSV_S, 0.5);
|
||||
|
||||
assert_eq!(color, Color::from_hsv(120.0, 0.5, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hsv_v() {
|
||||
let color = Color::from_hsv(120.0, 1.0, 1.0);
|
||||
let color = color.update(Component::HSV_V, 0.5);
|
||||
|
||||
assert_eq!(color, Color::from_hsv(120.0, 1.0, 0.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hcl_h() {
|
||||
let color = Color::from_hcl(120.0, 1.0, 1.0);
|
||||
let color = color.update(Component::HCL_H, 240.0);
|
||||
|
||||
assert_eq!(color, Color::from_hcl(240.0, 1.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hcl_s() {
|
||||
let color = Color::from_hcl(120.0, 1.0, 1.0);
|
||||
let color = color.update(Component::HCL_C, 0.5);
|
||||
|
||||
assert_eq!(color, Color::from_hcl(120.0, 0.5, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hcl_v() {
|
||||
let color = Color::from_hcl(120.0, 1.0, 1.0);
|
||||
let color = color.update(Component::HCL_L, 0.5);
|
||||
|
||||
assert_eq!(color, Color::from_hcl(120.0, 1.0, 0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
//! Calculation Functions
|
||||
|
||||
/// Perceptual Brightness Correction Factor - Red
|
||||
const C_R: f64 = 0.299;
|
||||
///
|
||||
/// Perceptual Brightness Correction Factor - Green
|
||||
const C_G: f64 = 0.587;
|
||||
///
|
||||
/// Perceptual Brightness Correction Factor - Blue
|
||||
const C_B: f64 = 0.114;
|
||||
|
||||
/// Calculates the Luminance of an HSV color
|
||||
pub fn luminance_from_hsv(h: f64, s: f64, v: f64) -> f64 {
|
||||
let [a, b, c] = luminance_constants(h);
|
||||
let h_prime = h_prime(h);
|
||||
let v_squared = v.powi(2);
|
||||
|
||||
return (a * v_squared
|
||||
+ b * v_squared * (s * (h_prime - 1.0) + 1.0).powi(2)
|
||||
+ c * v_squared * (1.0 - s).powi(2))
|
||||
.sqrt();
|
||||
}
|
||||
|
||||
/// Calculates the Value of an HSV color from its Luminance
|
||||
pub fn value(h: f64, s: f64, l: f64) -> f64 {
|
||||
let l_boundary = luminance_from_hsv(h, s, 1.0);
|
||||
l / l_boundary
|
||||
}
|
||||
|
||||
/// Calculates the Chroma of an HSV color from its Luminance
|
||||
pub fn chroma(h: f64, s: f64, l: f64) -> f64 {
|
||||
let cutoff = luminance_cutoff(h);
|
||||
if l <= cutoff {
|
||||
s
|
||||
} else {
|
||||
let boundary = chroma_boundary(h, l);
|
||||
if boundary.abs() < 1e-10 {
|
||||
0.0
|
||||
} else {
|
||||
s / boundary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the Saturation of an HCL color
|
||||
pub fn saturation(h: f64, c: f64, l: f64) -> f64 {
|
||||
let cutoff = luminance_cutoff(h);
|
||||
if l <= cutoff {
|
||||
c
|
||||
} else {
|
||||
let boundary = chroma_boundary(h, l);
|
||||
c * boundary
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the highest Luminance value for a given Hue
|
||||
pub fn luminance_cutoff(h: f64) -> f64 {
|
||||
luminance_from_hsv(h, 1.0, 1.0)
|
||||
}
|
||||
|
||||
/// Calculates the Chroma boundary of an HCL color
|
||||
pub fn chroma_boundary(h: f64, l: f64) -> f64 {
|
||||
let [a, b, c] = luminance_constants(h);
|
||||
let h_prime = h_prime(h);
|
||||
|
||||
let hm1_2 = (h_prime - 1.0).powi(2);
|
||||
let l_2 = l.powi(2);
|
||||
let h_2 = h_prime.powi(2);
|
||||
|
||||
let t1 = -h_prime * b + b + c;
|
||||
let t2 = c * l_2;
|
||||
let t3 = -a * (b * hm1_2 + c);
|
||||
let t4 = b * (hm1_2 * l_2 - c * h_2);
|
||||
let t5 = b * hm1_2 + c;
|
||||
|
||||
let radicand = t2 + t3 + t4;
|
||||
|
||||
if radicand.abs() < 1e-10 {
|
||||
t1 / t5
|
||||
} else {
|
||||
(t1 - radicand.sqrt()) / t5
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates H' for a given Hue
|
||||
pub fn h_prime(h: f64) -> f64 {
|
||||
1.0 - (((h / 60.0) % 2.0) - 1.0).abs()
|
||||
}
|
||||
|
||||
/// Calculates the domain for a given Hue
|
||||
pub fn h_domain(h: f64) -> u8 {
|
||||
((h / 60.0) % 6.0) as u8
|
||||
}
|
||||
|
||||
/// Calculates RGB'
|
||||
pub fn rgb_prime(h: f64, c: f64, x: f64) -> [f64; 3] {
|
||||
match h_domain(h) {
|
||||
0 => [c, x, 0.0],
|
||||
1 => [x, c, 0.0],
|
||||
2 => [0.0, c, x],
|
||||
3 => [0.0, x, c],
|
||||
4 => [x, 0.0, c],
|
||||
5 => [c, 0.0, x],
|
||||
_ => [c, x, 0.0],
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the Luminance constants for a given Hue
|
||||
pub fn luminance_constants(h: f64) -> [f64; 3] {
|
||||
let (r, g, b) = (C_R, C_G, C_B);
|
||||
|
||||
match h_domain(h) {
|
||||
0 => [r, g, b],
|
||||
1 => [g, r, b],
|
||||
2 => [g, b, r],
|
||||
3 => [b, g, r],
|
||||
4 => [b, r, g],
|
||||
5 => [r, b, g],
|
||||
_ => [r, g, b],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::color::calc;
|
||||
use crate::color::util::assert_close;
|
||||
|
||||
mod luminance {
|
||||
use super::*;
|
||||
|
||||
macro_rules! calculate_hsv {
|
||||
($name:ident, $h:expr, $s:expr, $v:expr, $l:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let lum = calc::luminance_from_hsv($h, $s, $v);
|
||||
assert_close(lum, $l);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
calculate_hsv!(calculate_hsv_black, 0.0, 0.0, 0.0, 0.0);
|
||||
calculate_hsv!(calculate_hsv_grey, 0.0, 0.0, 0.498039, 0.498039);
|
||||
calculate_hsv!(calculate_hsv_white, 0.0, 0.0, 1.0, 1.0);
|
||||
calculate_hsv!(calculate_hsv_red, 0.0, 0.9, 1.0, 0.553182);
|
||||
calculate_hsv!(calculate_hsv_yellow, 60.0, 0.9, 1.0, 0.941881);
|
||||
calculate_hsv!(calculate_hsv_green, 120.0, 0.9, 1.0, 0.76885);
|
||||
calculate_hsv!(calculate_hsv_cyan, 180.0, 0.9, 1.0, 0.839041);
|
||||
calculate_hsv!(calculate_hsv_blue, 240.0, 0.9, 1.0, 0.350514);
|
||||
calculate_hsv!(calculate_hsv_magenta, 300.0, 0.9, 1.0, 0.647202);
|
||||
calculate_hsv!(calculate_hsv_red_360, 360.0, 0.9, 1.0, 0.553182);
|
||||
}
|
||||
|
||||
mod luminance_cutoff {
|
||||
use super::*;
|
||||
|
||||
macro_rules! calculate {
|
||||
($name:ident, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l_0 = calc::luminance_cutoff($h);
|
||||
assert_close(l_0, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
calculate!(calculate_red, 0.0, 0.546809);
|
||||
calculate!(calculate_yellow, 60.0, 0.941276);
|
||||
calculate!(calculate_green, 120.0, 0.766159);
|
||||
calculate!(calculate_cyan, 180.0, 0.837259);
|
||||
calculate!(calculate_blue, 240.0, 0.337639);
|
||||
calculate!(calculate_magenta, 300.0, 0.642651);
|
||||
}
|
||||
|
||||
mod chroma_boundary {
|
||||
use super::*;
|
||||
|
||||
macro_rules! at_cutoff {
|
||||
($name:ident, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l = calc::luminance_cutoff($h);
|
||||
let c = calc::chroma_boundary($h, l);
|
||||
assert_close(c, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! at_midpoint {
|
||||
($name:ident, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l = 1.0 - (1.0 - calc::luminance_cutoff($h)) / 2.0;
|
||||
let c = calc::chroma_boundary($h, l);
|
||||
assert_close(c, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! at_upper {
|
||||
($name:ident, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let c = calc::chroma_boundary($h, 1.0);
|
||||
assert_close(c, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
at_cutoff!(at_cutoff_red, 0.0, 1.0);
|
||||
at_cutoff!(at_cutoff_yellow, 60.0, 1.0);
|
||||
at_cutoff!(at_cutoff_green, 120.0, 1.0);
|
||||
at_cutoff!(at_cutoff_cyan, 180.0, 1.0);
|
||||
at_cutoff!(at_cutoff_blue, 240.0, 1.0);
|
||||
at_cutoff!(at_cutoff_magenta, 300.0, 1.0);
|
||||
|
||||
at_midpoint!(at_midpoint_red, 0.0, 0.346737);
|
||||
at_midpoint!(at_midpoint_yellow, 60.0, 0.298272);
|
||||
at_midpoint!(at_midpoint_green, 120.0, 0.316701);
|
||||
at_midpoint!(at_midpoint_cyan, 180.0, 0.308732);
|
||||
at_midpoint!(at_midpoint_blue, 240.0, 0.386643);
|
||||
at_midpoint!(at_midpoint_magenta, 300.0, 0.332458);
|
||||
|
||||
at_upper!(at_upper_red, 0.0, 0.0);
|
||||
at_upper!(at_upper_yellow, 60.0, 0.0);
|
||||
at_upper!(at_upper_green, 120.0, 0.0);
|
||||
at_upper!(at_upper_cyan, 180.0, 0.0);
|
||||
at_upper!(at_upper_blue, 240.0, 0.0);
|
||||
at_upper!(at_upper_magenta, 300.0, 0.0);
|
||||
}
|
||||
|
||||
mod chroma {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn red_below_cutoff() {
|
||||
let c = calc::chroma(0.0, 0.5, 0.1);
|
||||
assert_eq!(c, 0.5);
|
||||
}
|
||||
|
||||
macro_rules! at_cutoff {
|
||||
($name:ident, $s:expr, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l = calc::luminance_cutoff($h);
|
||||
let c = calc::chroma($h, $s, l);
|
||||
assert_close(c, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! at_midpoint {
|
||||
($name:ident, $s:expr, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l = 1.0 - (1.0 - calc::luminance_cutoff($h)) / 2.0;
|
||||
let c = calc::chroma($h, $s, l);
|
||||
assert_close(c, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! at_upper {
|
||||
($name:ident, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let c = calc::chroma($h, 0.0, 1.0);
|
||||
assert_close(c, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
at_cutoff!(at_cutoff_red_1, 0.0, 0.0, 0.0);
|
||||
at_cutoff!(at_cutoff_red_2, 0.5, 0.0, 0.5);
|
||||
at_cutoff!(at_cutoff_red_3, 1.0, 0.0, 1.0);
|
||||
|
||||
at_midpoint!(at_midpoint_red_1, 0.0, 0.0, 0.0);
|
||||
at_midpoint!(at_midpoint_red_2, 0.1733685, 0.0, 0.5);
|
||||
at_midpoint!(at_midpoint_red_3, 0.346737, 0.0, 1.0);
|
||||
|
||||
at_upper!(at_upper_red, 0.0, 0.0);
|
||||
at_upper!(at_upper_yellow, 60.0, 0.0);
|
||||
at_upper!(at_upper_green, 120.0, 0.0);
|
||||
at_upper!(at_upper_cyan, 180.0, 0.0);
|
||||
at_upper!(at_upper_blue, 240.0, 0.0);
|
||||
at_upper!(at_upper_magenta, 300.0, 0.0);
|
||||
}
|
||||
|
||||
mod value {
|
||||
use super::*;
|
||||
|
||||
macro_rules! calculate {
|
||||
($name:ident, $h:expr, $s:expr, $v:expr, $l:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let val = calc::value($h, $s, $l);
|
||||
assert_close(val, $v)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
calculate!(calculate_black, 0.0, 0.0, 0.0, 0.0);
|
||||
calculate!(calculate_grey, 0.0, 0.0, 0.498039, 0.498039);
|
||||
calculate!(calculate_white, 0.0, 0.0, 1.0, 1.0);
|
||||
calculate!(calculate_red, 0.0, 0.9, 1.0, 0.553182);
|
||||
calculate!(calculate_yellow, 60.0, 0.9, 1.0, 0.941881);
|
||||
calculate!(calculate_green, 120.0, 0.9, 1.0, 0.76885);
|
||||
calculate!(calculate_cyan, 180.0, 0.9, 1.0, 0.839041);
|
||||
calculate!(calculate_blue, 240.0, 0.9, 1.0, 0.350514);
|
||||
calculate!(calculate_magenta, 300.0, 0.9, 1.0, 0.647202);
|
||||
calculate!(calculate_red_360, 360.0, 0.9, 1.0, 0.553182);
|
||||
}
|
||||
|
||||
mod saturation {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn red_below_cutoff() {
|
||||
let s = calc::saturation(0.0, 0.5, 0.1);
|
||||
assert_eq!(s, 0.5);
|
||||
}
|
||||
|
||||
macro_rules! at_cutoff {
|
||||
($name:ident, $c:expr, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l = calc::luminance_cutoff($h);
|
||||
let s = calc::saturation($h, $c, l);
|
||||
assert_close(s, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! at_midpoint {
|
||||
($name:ident, $c:expr, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l = 1.0 - (1.0 - calc::luminance_cutoff($h)) / 2.0;
|
||||
let s = calc::saturation($h, $c, l);
|
||||
assert_close(s, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! at_upper {
|
||||
($name:ident, $c:expr, $h:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let l = 1.0;
|
||||
let s = calc::saturation($h, $c, l);
|
||||
assert_close(s, $expected)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
at_cutoff!(at_cutoff_1, 0.0, 0.0, 0.0);
|
||||
at_cutoff!(at_cutoff_2, 0.5, 0.0, 0.5);
|
||||
at_cutoff!(at_cutoff_3, 1.0, 0.0, 1.0);
|
||||
|
||||
at_midpoint!(at_midpoint_1, 0.0, 0.0, 0.0);
|
||||
at_midpoint!(at_midpoint_2, 0.5, 0.0, 0.173367);
|
||||
at_midpoint!(at_midpoint_3, 1.0, 0.0, 0.346735);
|
||||
|
||||
at_upper!(at_upper_1, 0.0, 0.0, 0.0);
|
||||
at_upper!(at_upper_2, 0.5, 0.0, 0.0);
|
||||
at_upper!(at_upper_3, 1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
use crate::color::calc;
|
||||
use crate::color::hsv::HSV;
|
||||
use crate::color::util;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Represents a color in HCL space.
|
||||
///
|
||||
/// Holds three color components:
|
||||
/// - H: Hue
|
||||
/// - C: Chroma
|
||||
/// - L: Luminance
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hcl::HCL;
|
||||
///
|
||||
/// let red = HCL::new(0.0, 1.0, 0.55);
|
||||
/// ```
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct HCL {
|
||||
pub h: f64,
|
||||
pub c: f64,
|
||||
pub l: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl HCL {
|
||||
/// Creates a new HCL struct from floating point HCL values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hcl::HCL;
|
||||
///
|
||||
/// let red = HCL::new(0.0, 1.0, 0.55);
|
||||
/// ```
|
||||
pub fn new(h: f64, c: f64, l: f64) -> HCL {
|
||||
let h = util::minmax(util::map_degrees(h), 0.0, 360.0);
|
||||
let c = util::minmax(c, 0.0, 1.0);
|
||||
let l = util::minmax(l, 0.0, 1.0);
|
||||
HCL { h, c, l }
|
||||
}
|
||||
|
||||
/// Creates a new HCL struct from a hex code string.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hcl::HCL;
|
||||
///
|
||||
/// let red = HCL::from_hex("FF0000");
|
||||
/// ```
|
||||
pub fn from_hex(code: &str) -> HCL {
|
||||
let hsv = HSV::from_hex(&code);
|
||||
HCL::from_hsv(hsv.h, hsv.s, hsv.v)
|
||||
}
|
||||
|
||||
/// Creates a new HCL struct from floating point RGB values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hcl::HCL;
|
||||
///
|
||||
/// let red = HCL::from_rgb(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
pub fn from_rgb(r: f64, g: f64, b: f64) -> HCL {
|
||||
let hsv = HSV::from_rgb(r, g, b);
|
||||
HCL::from_hsv(hsv.h, hsv.s, hsv.v)
|
||||
}
|
||||
|
||||
/// Creates a new HCL struct from floating point HSV values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hcl::HCL;
|
||||
///
|
||||
/// let red = HCL::from_hsv(0.0, 1.0, 0.55);
|
||||
/// ```
|
||||
pub fn from_hsv(h: f64, s: f64, v: f64) -> HCL {
|
||||
let l = calc::luminance_from_hsv(h, s, v);
|
||||
let c = calc::chroma(h, s, l);
|
||||
HCL::new(h, c, l)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::color::util::assert_close;
|
||||
|
||||
mod struct_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_new() {
|
||||
let hcl = HCL::new(0.0, 0.5, 1.0);
|
||||
assert_eq!(hcl.h, 0.0);
|
||||
assert_eq!(hcl.c, 0.5);
|
||||
assert_eq!(hcl.l, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_min() {
|
||||
let hcl = HCL::new(-60.0, -1.0, -1.0);
|
||||
assert_eq!(hcl.h, 300.0);
|
||||
assert_eq!(hcl.c, 0.0);
|
||||
assert_eq!(hcl.l, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_max() {
|
||||
let hcl = HCL::new(1000.0, 1000.0, 1000.0);
|
||||
assert_eq!(hcl.h, 280.0);
|
||||
assert_eq!(hcl.c, 1.0);
|
||||
assert_eq!(hcl.l, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
mod conversion_tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! from_hsv {
|
||||
($name:ident, $h:expr, $s:expr, $v:expr, $h2:expr, $c:expr, $l:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let hcl = HCL::from_hsv($h, $s, $v);
|
||||
assert_eq!(hcl.h, $h2);
|
||||
assert_close(hcl.c, $c);
|
||||
assert_close(hcl.l, $l);
|
||||
|
||||
let hsv = HSV::from_hcl(hcl.h, hcl.c, hcl.l);
|
||||
assert_eq!(hsv.h, $h);
|
||||
assert_close(hsv.s, $s);
|
||||
assert_close(hsv.v, $v);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
from_hsv!(from_hsv_black, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
from_hsv!(from_hsv_grey, 0.0, 0.0, 0.5, 0.0, 0.0, 0.5);
|
||||
from_hsv!(from_hsv_white, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0);
|
||||
from_hsv!(from_hsv_dark_red, 0.0, 0.9, 0.5, 0.0, 0.9, 0.276591);
|
||||
from_hsv!(from_hsv_light_red, 0.0, 0.5, 0.9, 0.0, 0.767469, 0.619792);
|
||||
from_hsv!(from_hsv_dark_yellow, 60.0, 0.9, 0.5, 60.0, 0.9, 0.470941);
|
||||
from_hsv!(
|
||||
from_hsv_light_yellow,
|
||||
60.0,
|
||||
0.5,
|
||||
0.99,
|
||||
60.0,
|
||||
0.714905,
|
||||
0.946732
|
||||
);
|
||||
from_hsv!(from_hsv_dark_green, 120.0, 0.9, 0.5, 120.0, 0.9, 0.384425);
|
||||
from_hsv!(
|
||||
from_hsv_light_green,
|
||||
120.0,
|
||||
0.5,
|
||||
0.95,
|
||||
120.0,
|
||||
0.709257,
|
||||
0.789272
|
||||
);
|
||||
from_hsv!(from_hsv_dark_cyan, 180.0, 0.9, 0.5, 180.0, 0.9, 0.419521);
|
||||
from_hsv!(
|
||||
from_hsv_light_cyan,
|
||||
180.0,
|
||||
0.5,
|
||||
0.99,
|
||||
180.0,
|
||||
0.901529,
|
||||
0.871959
|
||||
);
|
||||
from_hsv!(from_hsv_dark_blue, 240.0, 0.9, 0.5, 240.0, 0.9, 0.175257);
|
||||
from_hsv!(
|
||||
from_hsv_light_blue,
|
||||
240.0,
|
||||
0.5,
|
||||
0.9,
|
||||
240.0,
|
||||
0.864996,
|
||||
0.521301
|
||||
);
|
||||
from_hsv!(from_hsv_dark_magenta, 300.0, 0.9, 0.5, 300.0, 0.9, 0.323601);
|
||||
from_hsv!(
|
||||
from_hsv_light_magenta,
|
||||
300.0,
|
||||
0.5,
|
||||
0.9,
|
||||
300.0,
|
||||
0.677815,
|
||||
0.673348
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
use crate::color::rgb::RGB;
|
||||
use crate::color::util;
|
||||
use hex;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Represents a hexadecimal color in RGB space
|
||||
///
|
||||
/// Holds three color components in 8-bit integer form.
|
||||
/// - R: Red
|
||||
/// - G: Green
|
||||
/// - B: Blue
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::new(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Hex {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Hex {
|
||||
/// Creates a new Hex struct from floating point RGB values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::new(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
pub fn new(r: f64, g: f64, b: f64) -> Hex {
|
||||
let (r, g, b) = (r as u8, g as u8, b as u8);
|
||||
Hex { r, g, b }
|
||||
}
|
||||
|
||||
/// Creates a new Hex struct from integer RGB values
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::new_int(255, 0, 0);
|
||||
/// ```
|
||||
pub fn new_int(r: u8, g: u8, b: u8) -> Hex {
|
||||
Hex { r, g, b }
|
||||
}
|
||||
|
||||
/// Creates a new Hex struct from RGB values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::from_rgb(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
pub fn from_rgb(r: f64, g: f64, b: f64) -> Hex {
|
||||
Hex::new(r, g, b)
|
||||
}
|
||||
|
||||
/// Creates a new Hex struct from HSV values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::from_hsv(0.0, 1.0, 1.0);
|
||||
/// ```
|
||||
pub fn from_hsv(h: f64, s: f64, v: f64) -> Hex {
|
||||
let rgb = RGB::from_hsv(h, s, v);
|
||||
Hex::new(rgb.r, rgb.g, rgb.b)
|
||||
}
|
||||
|
||||
/// Creates a new Hex struct from HCL values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::from_hcl(0.0, 1.0, 0.55);
|
||||
/// ```
|
||||
pub fn from_hcl(h: f64, c: f64, l: f64) -> Hex {
|
||||
let rgb = RGB::from_hcl(h, c, l);
|
||||
Hex::new(rgb.r, rgb.g, rgb.b)
|
||||
}
|
||||
|
||||
/// Converts the Hex color to a hex code string.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::new_int(255, 0, 0);
|
||||
/// assert_eq!(red.to_code(), String::from("FF0000"));
|
||||
/// ```
|
||||
pub fn to_code(&self) -> String {
|
||||
hex::encode_upper([self.r, self.g, self.b])
|
||||
}
|
||||
|
||||
/// Creates a new Hex struct from a hex code string.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hex::Hex;
|
||||
///
|
||||
/// let red = Hex::from_code("FF0000");
|
||||
/// let green = Hex::from_code("0F0");
|
||||
/// ```
|
||||
pub fn from_code(code: &str) -> Hex {
|
||||
let code = code.to_string();
|
||||
let code = if code.len() == 3 {
|
||||
util::convert_short_code(&code)
|
||||
} else {
|
||||
code.clone()
|
||||
};
|
||||
|
||||
let decoded = match hex::decode(code) {
|
||||
Ok(decoded) => decoded,
|
||||
Err(_) => vec![0, 0, 0],
|
||||
};
|
||||
|
||||
Hex {
|
||||
r: decoded[0],
|
||||
g: decoded[1],
|
||||
b: decoded[2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod struct_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_new() {
|
||||
let hex = Hex::new(0.0, 1.0, 2.0);
|
||||
assert_eq!(hex.r, 0);
|
||||
assert_eq!(hex.g, 1);
|
||||
assert_eq!(hex.b, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_min() {
|
||||
let hex = Hex::new(-1.0, -1.0, -1.0);
|
||||
assert_eq!(hex.r, 0);
|
||||
assert_eq!(hex.g, 0);
|
||||
assert_eq!(hex.b, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_max() {
|
||||
let hex = Hex::new(1000.0, 1000.0, 1000.0);
|
||||
assert_eq!(hex.r, 255);
|
||||
assert_eq!(hex.g, 255);
|
||||
assert_eq!(hex.b, 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_code() {
|
||||
let hex = Hex::new(255.0, 0.0, 0.0);
|
||||
let hex_code = hex.to_code();
|
||||
assert_eq!(hex_code, "FF0000")
|
||||
}
|
||||
}
|
||||
|
||||
mod conversion_tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! to_code {
|
||||
($name:ident, $r:expr, $g:expr, $b:expr, $code:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let hex = Hex::new_int($r, $g, $b);
|
||||
let code = hex.to_code();
|
||||
assert_eq!(code, $code);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
to_code!(to_code_black, 0, 0, 0, "000000");
|
||||
to_code!(to_code_red, 255, 0, 0, "FF0000");
|
||||
to_code!(to_code_grey, 127, 127, 127, "7F7F7F");
|
||||
to_code!(to_code_white, 255, 255, 255, "FFFFFF");
|
||||
|
||||
macro_rules! from_code {
|
||||
($name:ident, $code:expr, $r:expr, $g:expr, $b:expr, $reverse:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let hex = Hex::from_code($code);
|
||||
assert_eq!(hex.r, $r);
|
||||
assert_eq!(hex.g, $g);
|
||||
assert_eq!(hex.b, $b);
|
||||
|
||||
let hex = Hex::new_int($r, $g, $b);
|
||||
let code = hex.to_code();
|
||||
assert_eq!(code, $reverse);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
from_code!(from_code_black, "000000", 0, 0, 0, "000000");
|
||||
from_code!(from_code_red, "FF0000", 255, 0, 0, "FF0000");
|
||||
from_code!(from_code_grey, "7F7F7F", 127, 127, 127, "7F7F7F");
|
||||
from_code!(from_code_shorthand_hex, "012", 0, 17, 34, "001122");
|
||||
from_code!(from_code_lowercase_hex, "7f7f7f", 127, 127, 127, "7F7F7F");
|
||||
|
||||
#[test]
|
||||
fn from_short_code() {
|
||||
let hex = Hex::from_code("00F");
|
||||
assert_eq!(hex.r, 0);
|
||||
assert_eq!(hex.g, 0);
|
||||
assert_eq!(hex.b, 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_code_bad_input() {
|
||||
let hex = Hex::from_code("xyz");
|
||||
assert_eq!(hex.r, 0);
|
||||
assert_eq!(hex.g, 0);
|
||||
assert_eq!(hex.b, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
use crate::color::calc;
|
||||
use crate::color::rgb::RGB;
|
||||
use crate::color::util;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Represents a color in HSV space
|
||||
///
|
||||
/// Holds three color components:
|
||||
/// - H: Hue
|
||||
/// - S: Saturation
|
||||
/// - V: Value
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hsv::HSV;
|
||||
///
|
||||
/// let red = HSV::new(0.0, 1.0, 1.0);
|
||||
/// ```
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct HSV {
|
||||
pub h: f64,
|
||||
pub s: f64,
|
||||
pub v: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl HSV {
|
||||
/// Creates a new HSV struct from floating point HSV values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hsv::HSV;
|
||||
///
|
||||
/// let red = HSV::new(0.0, 1.0, 1.0);
|
||||
/// ```
|
||||
pub fn new(h: f64, s: f64, v: f64) -> HSV {
|
||||
let h = util::minmax(util::map_degrees(h), 0.0, 360.0);
|
||||
let s = util::minmax(s, 0.0, 1.0);
|
||||
let v = util::minmax(v, 0.0, 1.0);
|
||||
HSV { h, s, v }
|
||||
}
|
||||
|
||||
/// Creates a new HSV struct from a hex code string.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hsv::HSV;
|
||||
///
|
||||
/// let red = HSV::from_hex("FF0000");
|
||||
/// ```
|
||||
pub fn from_hex(code: &str) -> HSV {
|
||||
let rgb = RGB::from_hex(&code);
|
||||
HSV::from_rgb(rgb.r, rgb.g, rgb.b)
|
||||
}
|
||||
|
||||
/// Creates a new HSV struct from floating point RGB values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hsv::HSV;
|
||||
///
|
||||
/// let red = HSV::from_rgb(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
pub fn from_rgb(r: f64, g: f64, b: f64) -> HSV {
|
||||
// Calculate constants
|
||||
let r_prime = r / 255.0;
|
||||
let g_prime = g / 255.0;
|
||||
let b_prime = b / 255.0;
|
||||
|
||||
let c_max = r_prime.max(g_prime).max(b_prime);
|
||||
let c_min = r_prime.min(g_prime).min(b_prime);
|
||||
let delta = c_max - c_min;
|
||||
|
||||
// Calculate H
|
||||
let h = if delta == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
if c_max == r_prime {
|
||||
60.0 * (((g_prime - b_prime) / delta) % 6.0)
|
||||
} else if c_max == g_prime {
|
||||
60.0 * ((b_prime - r_prime) / delta + 2.0)
|
||||
} else if c_max == b_prime {
|
||||
60.0 * ((r_prime - g_prime) / delta + 4.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate S
|
||||
let s = if c_max == 0.0 { 0.0 } else { delta / c_max };
|
||||
|
||||
// Calculate V
|
||||
let v = c_max;
|
||||
|
||||
HSV::new(h, s, v)
|
||||
}
|
||||
|
||||
/// Creates a new HSV struct from floating point HCL values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::hsv::HSV;
|
||||
///
|
||||
/// let red = HSV::from_hcl(0.0, 1.0, 0.55);
|
||||
/// ```
|
||||
pub fn from_hcl(h: f64, c: f64, l: f64) -> HSV {
|
||||
let s = calc::saturation(h, c, l);
|
||||
let v = calc::value(h, s, l);
|
||||
HSV::new(h, s, v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::color::util::assert_close;
|
||||
|
||||
mod struct_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_new() {
|
||||
let hsv = HSV::new(0.0, 0.5, 1.0);
|
||||
assert_eq!(hsv.h, 0.0);
|
||||
assert_eq!(hsv.s, 0.5);
|
||||
assert_eq!(hsv.v, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_min() {
|
||||
let hsv = HSV::new(-60.0, -1.0, -1.0);
|
||||
assert_eq!(hsv.h, 300.0);
|
||||
assert_eq!(hsv.s, 0.0);
|
||||
assert_eq!(hsv.v, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_max() {
|
||||
let hsv = HSV::new(1000.0, 1000.0, 1000.0);
|
||||
assert_eq!(hsv.h, 280.0);
|
||||
assert_eq!(hsv.s, 1.0);
|
||||
assert_eq!(hsv.v, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
mod conversion_tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! from_rgb {
|
||||
($name:ident, $r:expr, $g:expr, $b:expr, $h:expr, $s:expr, $v:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let hsv = HSV::from_rgb($r, $g, $b);
|
||||
assert_close(hsv.h, $h);
|
||||
assert_close(hsv.s, $s);
|
||||
assert_close(hsv.v, $v);
|
||||
|
||||
let rgb = RGB::from_hsv(hsv.h, hsv.s, hsv.v);
|
||||
assert_close(rgb.r, $r);
|
||||
assert_close(rgb.g, $g);
|
||||
assert_close(rgb.b, $b);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
from_rgb!(from_rgb_black, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
from_rgb!(from_rgb_grey, 127.0, 127.0, 127.0, 0.0, 0.0, 0.498039);
|
||||
from_rgb!(from_rgb_white, 255.0, 255.0, 255.0, 0.0, 0.0, 1.0);
|
||||
from_rgb!(from_rgb_red, 255.0, 127.0, 127.0, 0.0, 0.501961, 1.0);
|
||||
from_rgb!(from_rgb_green, 127.0, 255.0, 127.0, 120.0, 0.501961, 1.0);
|
||||
from_rgb!(from_rgb_blue, 127.0, 127.0, 255.0, 240.0, 0.501961, 1.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
use crate::color::calc;
|
||||
use crate::color::hex::Hex;
|
||||
use crate::color::hsv::HSV;
|
||||
use crate::color::util;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Represents a color in RGB space
|
||||
///
|
||||
/// Holds three color components:
|
||||
/// - R: Red
|
||||
/// - G: Green
|
||||
/// - B: Blue
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::rgb::RGB;
|
||||
///
|
||||
/// let red = RGB::new(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct RGB {
|
||||
pub r: f64,
|
||||
pub g: f64,
|
||||
pub b: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RGB {
|
||||
/// Creates a new RGB struct from floating point RGB values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::rgb::RGB;
|
||||
///
|
||||
/// let red = RGB::new(255.0, 0.0, 0.0);
|
||||
/// ```
|
||||
pub fn new(r: f64, g: f64, b: f64) -> RGB {
|
||||
let r = util::minmax(r, 0.0, 255.0);
|
||||
let g = util::minmax(g, 0.0, 255.0);
|
||||
let b = util::minmax(b, 0.0, 255.0);
|
||||
RGB { r, g, b }
|
||||
}
|
||||
|
||||
/// Creates a new RGB struct from a hex code string.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::rgb::RGB;
|
||||
///
|
||||
/// let red = RGB::from_hex("FF0000");
|
||||
/// ```
|
||||
pub fn from_hex(code: &str) -> RGB {
|
||||
let hex = Hex::from_code(&code);
|
||||
let (r, g, b) = (hex.r as f64, hex.g as f64, hex.b as f64);
|
||||
RGB::new(r, g, b)
|
||||
}
|
||||
|
||||
/// Creates a new RGB struct from floating point HSV values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::rgb::RGB;
|
||||
///
|
||||
/// let red = RGB::from_hsv(0.0, 1.0, 1.0);
|
||||
/// ```
|
||||
pub fn from_hsv(h: f64, s: f64, v: f64) -> RGB {
|
||||
// Calculate constants
|
||||
let c = v * s;
|
||||
let x = c * calc::h_prime(h);
|
||||
let m = v - c;
|
||||
|
||||
// Calculate RGB
|
||||
let [r, g, b] = calc::rgb_prime(h, c, x).map(|n| 255.0 * (n + m));
|
||||
|
||||
return RGB::new(r, g, b);
|
||||
}
|
||||
|
||||
/// Creates a new RGB struct from floating point HCL values.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```
|
||||
/// use color::rgb::RGB;
|
||||
///
|
||||
/// let red = RGB::from_hcl(0.0, 1.0, 0.55);
|
||||
/// ```
|
||||
pub fn from_hcl(h: f64, c: f64, l: f64) -> RGB {
|
||||
let hsv = HSV::from_hcl(h, c, l);
|
||||
RGB::from_hsv(hsv.h, hsv.s, hsv.v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::color::util::assert_close;
|
||||
|
||||
mod struct_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_new() {
|
||||
let rgb = RGB::new(0.0, 1.0, 2.0);
|
||||
assert_eq!(rgb.r, 0.0);
|
||||
assert_eq!(rgb.g, 1.0);
|
||||
assert_eq!(rgb.b, 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_min() {
|
||||
let rgb = RGB::new(-1.0, -1.0, -1.0);
|
||||
assert_eq!(rgb.r, 0.0);
|
||||
assert_eq!(rgb.g, 0.0);
|
||||
assert_eq!(rgb.b, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_max() {
|
||||
let rgb = RGB::new(1000.0, 1000.0, 1000.0);
|
||||
assert_eq!(rgb.r, 255.0);
|
||||
assert_eq!(rgb.g, 255.0);
|
||||
assert_eq!(rgb.b, 255.0);
|
||||
}
|
||||
}
|
||||
|
||||
mod conversion_tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! from_hsv {
|
||||
($name:ident, $h:expr, $s:expr, $v:expr, $r:expr, $g:expr, $b:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let rgb = RGB::from_hsv($h, $s, $v);
|
||||
assert_close(rgb.r, $r);
|
||||
assert_close(rgb.g, $g);
|
||||
assert_close(rgb.b, $b);
|
||||
|
||||
let hsv = HSV::from_rgb(rgb.r, rgb.g, rgb.b);
|
||||
assert_close(hsv.h, $h);
|
||||
assert_close(hsv.s, $s);
|
||||
assert_close(hsv.v, $v);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
from_hsv!(from_hsv_black, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
from_hsv!(from_hsv_grey, 0.0, 0.0, 0.498039, 127.0, 127.0, 127.0);
|
||||
from_hsv!(from_hsv_white, 0.0, 0.0, 1.0, 255.0, 255.0, 255.0);
|
||||
from_hsv!(from_hsv_red, 0.0, 0.9, 1.0, 255.0, 25.5, 25.5);
|
||||
from_hsv!(from_hsv_yellow, 60.0, 0.9, 1.0, 255.0, 255.0, 25.5);
|
||||
from_hsv!(from_hsv_green, 120.0, 0.9, 1.0, 25.5, 255.0, 25.5);
|
||||
from_hsv!(from_hsv_cyan, 180.0, 0.9, 1.0, 25.5, 255.0, 255.0);
|
||||
from_hsv!(from_hsv_blue, 240.0, 0.9, 1.0, 25.5, 25.5, 255.0);
|
||||
from_hsv!(from_hsv_magenta, 300.0, 0.9, 1.0, 255.0, 25.5, 255.0);
|
||||
|
||||
#[test]
|
||||
fn from_hsv_red_360() {
|
||||
let rgb = RGB::from_hsv(360.0, 0.9, 1.0);
|
||||
assert_close(rgb.r, 255.0);
|
||||
assert_close(rgb.g, 25.5);
|
||||
assert_close(rgb.b, 25.5);
|
||||
|
||||
let hsv = HSV::from_rgb(rgb.r, rgb.g, rgb.b);
|
||||
assert_close(hsv.h, 0.0);
|
||||
assert_close(hsv.s, 0.9);
|
||||
assert_close(hsv.v, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//! Utility Functions
|
||||
|
||||
/// Converts a short Hex code (XXX) to a regular Hex code (XXXXXX)
|
||||
pub fn convert_short_code(short_code: &String) -> String {
|
||||
short_code
|
||||
.chars()
|
||||
.flat_map(|c| std::iter::repeat(c).take(2))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Enforces minimum and maximum values on a number
|
||||
pub fn minmax(number: f64, min: f64, max: f64) -> f64 {
|
||||
number.max(min).min(max)
|
||||
}
|
||||
|
||||
/// Maps a value in Degrees to the [0, 360) domain.
|
||||
pub fn map_degrees(degrees: f64) -> f64 {
|
||||
degrees - 360.0 * (degrees / 360.0).floor()
|
||||
}
|
||||
|
||||
/// Asserts two values are approximately equal
|
||||
#[cfg(test)]
|
||||
pub fn assert_close(value: f64, expected: f64) {
|
||||
let diff = (value - expected).abs();
|
||||
assert!(diff < 1e-4);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
pub mod color;
|
||||
|
||||
pub use color::hcl;
|
||||
pub use color::hex;
|
||||
pub use color::hsv;
|
||||
pub use color::rgb;
|
||||
pub use color::Color;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ColorSquare {
|
||||
size: usize,
|
||||
pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ColorSquare {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(size: usize) -> ColorSquare {
|
||||
let pixels = vec![0; size * size * 4];
|
||||
ColorSquare { size, pixels }
|
||||
}
|
||||
|
||||
pub fn fill_chroma(&mut self, chroma: f64) {
|
||||
let mut next = self.pixels.clone();
|
||||
|
||||
for row in 0..self.size {
|
||||
let lum = 1.0 - (1.0 / self.size as f64) * row as f64;
|
||||
for col in 0..self.size {
|
||||
let hue = (360.0 / self.size as f64) * col as f64;
|
||||
let hex_color = hex::Hex::from_hcl(hue, chroma, lum);
|
||||
let idx = self.size * 4 * row + col * 4;
|
||||
next[idx] = hex_color.r;
|
||||
next[idx + 1] = hex_color.g;
|
||||
next[idx + 2] = hex_color.b;
|
||||
next[idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
self.pixels = next;
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn get_pixels_pointer(&self) -> *const u8 {
|
||||
self.pixels.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ColorBar {
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ColorBar {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(width: usize, height: usize) -> ColorBar {
|
||||
let pixels = vec![0; width * height * 4];
|
||||
ColorBar {
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_color(&mut self, hue: f64, lum: f64) {
|
||||
let mut next = self.pixels.clone();
|
||||
|
||||
for row in 0..self.height {
|
||||
for col in 0..self.width {
|
||||
let chroma = (1.0 / self.width as f64) * col as f64;
|
||||
let hex_color = hex::Hex::from_hcl(hue, chroma, lum);
|
||||
let idx = self.width * 4 * row + col * 4;
|
||||
next[idx] = hex_color.r;
|
||||
next[idx + 1] = hex_color.g;
|
||||
next[idx + 2] = hex_color.b;
|
||||
next[idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
self.pixels = next;
|
||||
}
|
||||
|
||||
pub fn get_width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn get_height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn get_pixels_pointer(&self) -> *const u8 {
|
||||
self.pixels.as_ptr()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Luminance</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+3736
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "luminance",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -b && vite build",
|
||||
"build:wasm": "wasm-pack build colorlib -t bundler -d pkg --release",
|
||||
"clean": "rm -rf dist colorlib/pkg*",
|
||||
"cypress:open": "1>/dev/null 2>/dev/null cypress open -d &",
|
||||
"dev": "vite",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"test:wasm": "cargo test --lib --manifest-path colorlib/Cargo.toml",
|
||||
"test:wasmdoc": "cargo test --doc --manifest-path colorlib/Cargo.toml"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@rollup/plugin-wasm": "^6.2.2",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"clsx": "^2.1.1",
|
||||
"colorlib": "file:colorlib/pkg",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-top-level-await": "^1.5.0",
|
||||
"vite-plugin-wasm": "^3.4.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,8 @@
|
||||
.card {
|
||||
padding: 2em;
|
||||
--main-text-color: lightblue;
|
||||
}
|
||||
|
||||
.test {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { useState } from "react";
|
||||
import reactLogo from "./assets/react.svg";
|
||||
import viteLogo from "/vite.svg";
|
||||
import * as colorlib from "colorlib";
|
||||
import "./App.css";
|
||||
import style from "./App.module.css";
|
||||
import clsx from "clsx";
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0);
|
||||
const color = colorlib.Color.from_hex("123123");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src={viteLogo} className="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://react.dev" target="_blank">
|
||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + React</h1>
|
||||
<div className={clsx(style.card)}>
|
||||
<button onClick={() => setCount((count) => count + 1)}>
|
||||
count is {count}
|
||||
</button>
|
||||
<p className={clsx(style.test)}>
|
||||
Edit <code>src/App.tsx</code> and save to test HMR
|
||||
</p>
|
||||
<p>The color is: {color.hex.to_code()}</p>
|
||||
</div>
|
||||
<p className="read-the-docs">
|
||||
Click on the Vite and React logos to learn more
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,68 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import wasm from "vite-plugin-wasm";
|
||||
import topLevelAwait from "vite-plugin-top-level-await";
|
||||
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
wasm(),
|
||||
topLevelAwait(),
|
||||
],
|
||||
})
|
||||
Reference in New Issue
Block a user