Re-initialize app using vite.
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user