Re-initialize app using vite.

This commit is contained in:
Jay
2025-06-14 10:43:58 -04:00
commit 6d2f6661eb
40 changed files with 5895 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
node_modules
+54
View File
@@ -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,
},
})
```
+2
View File
@@ -0,0 +1,2 @@
target
pkg
+140
View File
@@ -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",
]
+13
View File
@@ -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"
+2
View File
@@ -0,0 +1,2 @@
max_width = 79
wrap_comments = true
+379
View File
@@ -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));
}
}
}
+367
View File
@@ -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);
}
}
+199
View File
@@ -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
);
}
}
+239
View File
@@ -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);
}
}
}
+179
View File
@@ -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);
}
}
+174
View File
@@ -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);
}
}
}
+26
View File
@@ -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);
}
+101
View File
@@ -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()
}
}
+28
View File
@@ -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
View File
@@ -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>
+3736
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -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"
}
}
+1
View File
@@ -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

+8
View File
@@ -0,0 +1,8 @@
.card {
padding: 2em;
--main-text-color: lightblue;
}
.test {
color: var(--main-text-color);
}
+40
View File
@@ -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;
View File
View File
+68
View File
@@ -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;
}
}
+11
View File
@@ -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>,
)
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+27
View File
@@ -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"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+25
View File
@@ -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"]
}
+14
View File
@@ -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(),
],
})