From 4a9dfbb664534fd4af53753cc5598fe286c85c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 22 Jan 2025 15:29:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E6=8B=86=E6=A0=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=8A=9F=E8=83=BD=E5=87=BD=E6=95=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- color-module/src/analysis.rs | 190 ++++++++++++ color-module/src/color_card.rs | 47 ++- color-module/src/color_shifting.rs | 80 +++++ color-module/src/lib.rs | 483 +---------------------------- color-module/src/series.rs | 87 ++++++ color-module/src/theory.rs | 108 +++++++ 6 files changed, 516 insertions(+), 479 deletions(-) create mode 100644 color-module/src/analysis.rs create mode 100644 color-module/src/color_shifting.rs create mode 100644 color-module/src/series.rs create mode 100644 color-module/src/theory.rs diff --git a/color-module/src/analysis.rs b/color-module/src/analysis.rs new file mode 100644 index 0000000..418da4a --- /dev/null +++ b/color-module/src/analysis.rs @@ -0,0 +1,190 @@ +use std::str::FromStr; + +use palette::{ + cam16::{Cam16Jch, Parameters}, + FromColor, Hsl, IntoColor, Oklch, Srgb, +}; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::{ + color_differ::{self, ColorDifference}, + errors, reversing, +}; + +#[wasm_bindgen] +pub fn differ_in_rgb( + color: &str, + other: &str, +) -> Result { + let srgb = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let other_srgb = Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::(); + Ok(srgb.difference(&other_srgb)) +} + +#[wasm_bindgen] +pub fn relative_differ_in_rgb( + color: &str, + other: &str, +) -> Result { + let srgb = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let other_srgb = Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::(); + Ok(srgb.difference_relative(&other_srgb)) +} + +#[wasm_bindgen] +pub fn differ_in_hsl( + color: &str, + other: &str, +) -> Result { + let hsl = Hsl::from_color( + Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(), + ); + let other_hsl = Hsl::from_color( + Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::(), + ); + Ok(hsl.difference(&other_hsl)) +} + +#[wasm_bindgen] +pub fn relative_differ_in_hsl( + color: &str, + other: &str, +) -> Result { + let hsl = Hsl::from_color( + Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(), + ); + let other_hsl = Hsl::from_color( + Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::(), + ); + Ok(hsl.difference_relative(&other_hsl)) +} + +#[wasm_bindgen] +pub fn differ_in_hct( + color: &str, + other: &str, +) -> Result { + let hct = Cam16Jch::from_xyz( + Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::() + .into_color(), + Parameters::default_static_wp(40.0), + ); + let other_hct = Cam16Jch::from_xyz( + Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::() + .into_color(), + Parameters::default_static_wp(40.0), + ); + Ok(hct.difference(&other_hct)) +} + +#[wasm_bindgen] +pub fn relative_differ_in_hct( + color: &str, + other: &str, +) -> Result { + let hct = Cam16Jch::from_xyz( + Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::() + .into_color(), + Parameters::default_static_wp(40.0), + ); + let other_hct = Cam16Jch::from_xyz( + Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::() + .into_color(), + Parameters::default_static_wp(40.0), + ); + Ok(hct.difference_relative(&other_hct)) +} + +#[wasm_bindgen] +pub fn differ_in_oklch( + color: &str, + other: &str, +) -> Result { + let oklch = Oklch::from_color( + Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(), + ); + let other_oklch = Oklch::from_color( + Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::(), + ); + Ok(oklch.difference(&other_oklch)) +} + +#[wasm_bindgen] +pub fn relative_differ_in_oklch( + color: &str, + other: &str, +) -> Result { + let oklch = Oklch::from_color( + Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(), + ); + let other_oklch = Oklch::from_color( + Srgb::from_str(other) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? + .into_format::(), + ); + Ok(oklch.difference_relative(&other_oklch)) +} + +#[wasm_bindgen] +pub fn tint_scale( + basic_color: &str, + mixed_color: &str, +) -> Result { + let basic_color = Srgb::from_str(basic_color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(basic_color.to_string()))? + .into_format::(); + let mixed_color = Srgb::from_str(mixed_color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(mixed_color.to_string()))? + .into_format::(); + Ok(reversing::MixReversing::from_tint_rgb( + basic_color, + mixed_color, + )) +} + +#[wasm_bindgen] +pub fn shade_scale( + basic_color: &str, + mixed_color: &str, +) -> Result { + let basic_color = Srgb::from_str(basic_color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(basic_color.to_string()))? + .into_format::(); + let mixed_color = Srgb::from_str(mixed_color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(mixed_color.to_string()))? + .into_format::(); + Ok(reversing::MixReversing::from_shade_rgb( + basic_color, + mixed_color, + )) +} diff --git a/color-module/src/color_card.rs b/color-module/src/color_card.rs index e9edab6..a723ddd 100644 --- a/color-module/src/color_card.rs +++ b/color-module/src/color_card.rs @@ -1,7 +1,8 @@ -use std::sync::LazyLock; +use std::{str::FromStr, sync::LazyLock}; use serde::{Deserialize, Serialize}; -use strum::{Display, EnumIter, EnumString}; +use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; +use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ColorDescription { @@ -87,3 +88,45 @@ impl Category { } } } + +#[wasm_bindgen] +pub fn color_categories() -> Result { + let categories = Category::iter() + .map(|variant| { + serde_json::json!({ + "label": variant.label(), + "value": variant.to_string(), + }) + }) + .collect::>(); + + serde_wasm_bindgen::to_value(&categories).map_err(|e| e.to_string()) +} + +#[wasm_bindgen] +pub fn search_color_cards(tag: String, category: Option) -> Result { + let selected_category = category.and_then(|c| Category::from_str(&c).ok()); + let all_cards = &*COLOR_CARDS; + let mut cards = all_cards + .iter() + .filter(|card| card.tags.contains(&tag)) + .filter(|card| { + if let Some(category) = &selected_category { + let card_category = Category::from_oklch(&card.oklch); + card_category == *category + } else { + true + } + }) + .collect::>(); + + cards.sort_by(|a, b| { + a.oklch[2] + .partial_cmp(&b.oklch[2]) + .or_else(|| a.oklch[1].partial_cmp(&b.oklch[1])) + .or_else(|| a.oklch[0].partial_cmp(&b.oklch[0])) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + serde_wasm_bindgen::to_value(&cards).map_err(|e| e.to_string()) +} diff --git a/color-module/src/color_shifting.rs b/color-module/src/color_shifting.rs new file mode 100644 index 0000000..8876fc3 --- /dev/null +++ b/color-module/src/color_shifting.rs @@ -0,0 +1,80 @@ +use std::str::FromStr; + +use palette::{Darken, FromColor, Lighten, Mix, Oklch, Srgb}; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::errors; + +#[wasm_bindgen] +pub fn lighten(color: &str, percent: f32) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let lightened_color = oklch.lighten(percent); + let srgb = Srgb::from_color(lightened_color); + Ok(format!("{:x}", srgb.into_format::())) +} + +#[wasm_bindgen] +pub fn lighten_absolute(color: &str, value: f32) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let lightened_color = oklch.lighten_fixed(value); + let srgb = Srgb::from_color(lightened_color); + Ok(format!("{:x}", srgb.into_format::())) +} + +#[wasm_bindgen] +pub fn darken(color: &str, percent: f32) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let darkened_color = oklch.darken(percent); + let srgb = Srgb::from_color(darkened_color); + Ok(format!("{:x}", srgb.into_format::())) +} + +#[wasm_bindgen] +pub fn darken_absolute(color: &str, value: f32) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let darkened_color = oklch.darken_fixed(value); + let srgb = Srgb::from_color(darkened_color); + Ok(format!("{:x}", srgb.into_format::())) +} + +#[wasm_bindgen] +pub fn mix(color1: &str, color2: &str, percent: f32) -> Result { + let srgb1 = Srgb::from_str(color1) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color1.to_string()))? + .into_format::(); + let srgb2 = Srgb::from_str(color2) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color2.to_string()))? + .into_format::(); + let mixed_color = srgb1.mix(srgb2, percent); + Ok(format!("{:x}", mixed_color.into_format::())) +} + +#[wasm_bindgen] +pub fn tint(color: &str, percent: f32) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let tinted_color = origin_color.mix(Srgb::new(1.0, 1.0, 1.0), percent); + Ok(format!("{:x}", tinted_color.into_format::())) +} + +#[wasm_bindgen] +pub fn shade(color: &str, percent: f32) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let shaded_color = origin_color.mix(Srgb::new(0.0, 0.0, 0.0), percent); + Ok(format!("{:x}", shaded_color.into_format::())) +} diff --git a/color-module/src/lib.rs b/color-module/src/lib.rs index cad32dd..f7907d6 100644 --- a/color-module/src/lib.rs +++ b/color-module/src/lib.rs @@ -1,24 +1,24 @@ -use std::{str::FromStr, sync::Arc}; +use std::str::FromStr; -use color_card::Category; -use color_differ::ColorDifference; use palette::{ cam16::{Cam16Jch, Parameters}, color_difference::Wcag21RelativeContrast, - color_theory::*, convert::FromColorUnclamped, - Darken, FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Lighten, Mix, Oklch, ShiftHue, Srgb, + FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Oklch, Srgb, }; -use strum::IntoEnumIterator; use wasm_bindgen::prelude::*; +mod analysis; mod color_card; mod color_differ; +mod color_shifting; mod convert; mod errors; mod palettes; mod reversing; mod schemes; +mod series; +mod theory; #[wasm_bindgen] pub fn represent_rgb(color: &str) -> Result, errors::ColorError> { @@ -125,91 +125,6 @@ pub fn hct_to_hex(hue: f32, chroma: f32, tone: f32) -> Result())) } -#[wasm_bindgen] -pub fn shift_hue(color: &str, degree: f32) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let shifted_color = oklch.shift_hue(degree); - let srgb = Srgb::from_color(shifted_color); - Ok(format!("{:x}", srgb.into_format::())) -} - -#[wasm_bindgen] -pub fn lighten(color: &str, percent: f32) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let lightened_color = oklch.lighten(percent); - let srgb = Srgb::from_color(lightened_color); - Ok(format!("{:x}", srgb.into_format::())) -} - -#[wasm_bindgen] -pub fn lighten_absolute(color: &str, value: f32) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let lightened_color = oklch.lighten_fixed(value); - let srgb = Srgb::from_color(lightened_color); - Ok(format!("{:x}", srgb.into_format::())) -} - -#[wasm_bindgen] -pub fn darken(color: &str, percent: f32) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let darkened_color = oklch.darken(percent); - let srgb = Srgb::from_color(darkened_color); - Ok(format!("{:x}", srgb.into_format::())) -} - -#[wasm_bindgen] -pub fn darken_absolute(color: &str, value: f32) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let darkened_color = oklch.darken_fixed(value); - let srgb = Srgb::from_color(darkened_color); - Ok(format!("{:x}", srgb.into_format::())) -} - -#[wasm_bindgen] -pub fn mix(color1: &str, color2: &str, percent: f32) -> Result { - let srgb1 = Srgb::from_str(color1) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color1.to_string()))? - .into_format::(); - let srgb2 = Srgb::from_str(color2) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color2.to_string()))? - .into_format::(); - let mixed_color = srgb1.mix(srgb2, percent); - Ok(format!("{:x}", mixed_color.into_format::())) -} - -#[wasm_bindgen] -pub fn tint(color: &str, percent: f32) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let tinted_color = origin_color.mix(Srgb::new(1.0, 1.0, 1.0), percent); - Ok(format!("{:x}", tinted_color.into_format::())) -} - -#[wasm_bindgen] -pub fn shade(color: &str, percent: f32) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let shaded_color = origin_color.mix(Srgb::new(0.0, 0.0, 0.0), percent); - Ok(format!("{:x}", shaded_color.into_format::())) -} - #[wasm_bindgen] pub fn wacg_relative_contrast(fg_color: &str, bg_color: &str) -> Result { let fg_srgb = Srgb::from_str(fg_color) @@ -220,389 +135,3 @@ pub fn wacg_relative_contrast(fg_color: &str, bg_color: &str) -> Result(); Ok(fg_srgb.relative_contrast(bg_srgb)) } - -#[wasm_bindgen] -pub fn analogous_30(color: &str) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let (color_n30, color_p30) = oklch.analogous(); - let srgb_n30 = Srgb::from_color(color_n30); - let srgb_p30 = Srgb::from_color(color_p30); - Ok(vec![ - format!("{:x}", srgb_n30.into_format::()), - format!("{:x}", srgb_p30.into_format::()), - ]) -} - -#[wasm_bindgen] -pub fn analogous_60(color: &str) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let (color_n60, color_p60) = oklch.analogous_secondary(); - let srgb_n60 = Srgb::from_color(color_n60); - let srgb_p60 = Srgb::from_color(color_p60); - Ok(vec![ - format!("{:x}", srgb_n60.into_format::()), - format!("{:x}", srgb_p60.into_format::()), - ]) -} - -#[wasm_bindgen] -pub fn complementary(color: &str) -> Result { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let complementary_color = oklch.complementary(); - let srgb = Srgb::from_color(complementary_color); - Ok(format!("{:x}", srgb.into_format::())) -} - -#[wasm_bindgen] -pub fn split_complementary(color: &str) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let (color_p150, color_p210) = oklch.split_complementary(); - let srgb_p150 = Srgb::from_color(color_p150); - let srgb_p210 = Srgb::from_color(color_p210); - Ok(vec![ - format!("{:x}", srgb_p150.into_format::()), - format!("{:x}", srgb_p210.into_format::()), - ]) -} - -#[wasm_bindgen] -pub fn tetradic(color: &str) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let (color_p90, color_p180, color_p270) = oklch.tetradic(); - let srgb_p90 = Srgb::from_color(color_p90); - let srgb_p180 = Srgb::from_color(color_p180); - let srgb_p270 = Srgb::from_color(color_p270); - Ok(vec![ - format!("{:x}", srgb_p90.into_format::()), - format!("{:x}", srgb_p180.into_format::()), - format!("{:x}", srgb_p270.into_format::()), - ]) -} - -#[wasm_bindgen] -pub fn triadic(color: &str) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Oklch::from_color(origin_color); - let (color_p120, color_p240) = oklch.triadic(); - let srgb_p120 = Srgb::from_color(color_p120); - let srgb_p240 = Srgb::from_color(color_p240); - Ok(vec![ - format!("{:x}", srgb_p120.into_format::()), - format!("{:x}", srgb_p240.into_format::()), - ]) -} - -#[wasm_bindgen] -pub fn series( - color: &str, - expand_amount: i16, - step: f32, -) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let oklch = Arc::new(Oklch::from_color(origin_color.clone())); - - let mut color_series = Vec::new(); - for s in (1..=expand_amount).rev() { - let darkened_color = Arc::clone(&oklch).darken(s as f32 * step); - let srgb = Srgb::from_color(darkened_color); - color_series.push(format!("{:x}", srgb.into_format::())); - } - color_series.push(format!("{:x}", origin_color.into_format::())); - for s in 1..=expand_amount { - let lightened_color = Arc::clone(&oklch).lighten(s as f32 * step); - let srgb = Srgb::from_color(lightened_color); - color_series.push(format!("{:x}", srgb.into_format::())); - } - Ok(color_series) -} - -#[wasm_bindgen] -pub fn tonal_lighten_series( - color: &str, - expand_amount: i16, - step: f32, -) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let hct = Cam16Jch::from_xyz( - origin_color.into_color(), - Parameters::default_static_wp(40.0), - ); - - let mut color_series = Vec::new(); - let mut lightness = hct.lightness; - for _ in 1..=expand_amount { - lightness += (100.0 - lightness) * step; - let lightened_color = Cam16Jch::new(lightness, hct.chroma, hct.hue); - let srgb = Srgb::from_color(lightened_color.into_xyz(Parameters::default_static_wp(40.0))); - color_series.push(format!("{:x}", srgb.into_format::())); - } - - Ok(color_series) -} - -#[wasm_bindgen] -pub fn tonal_darken_series( - color: &str, - expand_amount: i16, - step: f32, -) -> Result, errors::ColorError> { - let origin_color = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let hct = Cam16Jch::from_xyz( - origin_color.into_color(), - Parameters::default_static_wp(40.0), - ); - - let mut color_series = Vec::new(); - let mut lightness = hct.lightness; - for _ in 1..=expand_amount { - lightness *= 1.0 - step; - let darkened_color = Cam16Jch::new(lightness, hct.chroma, hct.hue); - let srgb = Srgb::from_color(darkened_color.into_xyz(Parameters::default_static_wp(40.0))); - color_series.push(format!("{:x}", srgb.into_format::())); - } - - Ok(color_series.into_iter().rev().collect()) -} - -#[wasm_bindgen] -pub fn color_categories() -> Result { - let categories = Category::iter() - .map(|variant| { - serde_json::json!({ - "label": variant.label(), - "value": variant.to_string(), - }) - }) - .collect::>(); - - serde_wasm_bindgen::to_value(&categories).map_err(|e| e.to_string()) -} - -#[wasm_bindgen] -pub fn search_color_cards(tag: String, category: Option) -> Result { - let selected_category = category.and_then(|c| Category::from_str(&c).ok()); - let all_cards = &*color_card::COLOR_CARDS; - let mut cards = all_cards - .iter() - .filter(|card| card.tags.contains(&tag)) - .filter(|card| { - if let Some(category) = &selected_category { - let card_category = Category::from_oklch(&card.oklch); - card_category == *category - } else { - true - } - }) - .collect::>(); - - cards.sort_by(|a, b| { - a.oklch[2] - .partial_cmp(&b.oklch[2]) - .or_else(|| a.oklch[1].partial_cmp(&b.oklch[1])) - .or_else(|| a.oklch[0].partial_cmp(&b.oklch[0])) - .unwrap_or(std::cmp::Ordering::Equal) - }); - - serde_wasm_bindgen::to_value(&cards).map_err(|e| e.to_string()) -} - -#[wasm_bindgen] -pub fn differ_in_rgb( - color: &str, - other: &str, -) -> Result { - let srgb = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let other_srgb = Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::(); - Ok(srgb.difference(&other_srgb)) -} - -#[wasm_bindgen] -pub fn relative_differ_in_rgb( - color: &str, - other: &str, -) -> Result { - let srgb = Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(); - let other_srgb = Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::(); - Ok(srgb.difference_relative(&other_srgb)) -} - -#[wasm_bindgen] -pub fn differ_in_hsl( - color: &str, - other: &str, -) -> Result { - let hsl = Hsl::from_color( - Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(), - ); - let other_hsl = Hsl::from_color( - Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::(), - ); - Ok(hsl.difference(&other_hsl)) -} - -#[wasm_bindgen] -pub fn relative_differ_in_hsl( - color: &str, - other: &str, -) -> Result { - let hsl = Hsl::from_color( - Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(), - ); - let other_hsl = Hsl::from_color( - Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::(), - ); - Ok(hsl.difference_relative(&other_hsl)) -} - -#[wasm_bindgen] -pub fn differ_in_hct( - color: &str, - other: &str, -) -> Result { - let hct = Cam16Jch::from_xyz( - Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::() - .into_color(), - Parameters::default_static_wp(40.0), - ); - let other_hct = Cam16Jch::from_xyz( - Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::() - .into_color(), - Parameters::default_static_wp(40.0), - ); - Ok(hct.difference(&other_hct)) -} - -#[wasm_bindgen] -pub fn relative_differ_in_hct( - color: &str, - other: &str, -) -> Result { - let hct = Cam16Jch::from_xyz( - Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::() - .into_color(), - Parameters::default_static_wp(40.0), - ); - let other_hct = Cam16Jch::from_xyz( - Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::() - .into_color(), - Parameters::default_static_wp(40.0), - ); - Ok(hct.difference_relative(&other_hct)) -} - -#[wasm_bindgen] -pub fn differ_in_oklch( - color: &str, - other: &str, -) -> Result { - let oklch = Oklch::from_color( - Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(), - ); - let other_oklch = Oklch::from_color( - Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::(), - ); - Ok(oklch.difference(&other_oklch)) -} - -#[wasm_bindgen] -pub fn relative_differ_in_oklch( - color: &str, - other: &str, -) -> Result { - let oklch = Oklch::from_color( - Srgb::from_str(color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? - .into_format::(), - ); - let other_oklch = Oklch::from_color( - Srgb::from_str(other) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))? - .into_format::(), - ); - Ok(oklch.difference_relative(&other_oklch)) -} - -#[wasm_bindgen] -pub fn tint_scale( - basic_color: &str, - mixed_color: &str, -) -> Result { - let basic_color = Srgb::from_str(basic_color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(basic_color.to_string()))? - .into_format::(); - let mixed_color = Srgb::from_str(mixed_color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(mixed_color.to_string()))? - .into_format::(); - Ok(reversing::MixReversing::from_tint_rgb( - basic_color, - mixed_color, - )) -} - -#[wasm_bindgen] -pub fn shade_scale( - basic_color: &str, - mixed_color: &str, -) -> Result { - let basic_color = Srgb::from_str(basic_color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(basic_color.to_string()))? - .into_format::(); - let mixed_color = Srgb::from_str(mixed_color) - .map_err(|_| errors::ColorError::UnrecogniazedRGB(mixed_color.to_string()))? - .into_format::(); - Ok(reversing::MixReversing::from_shade_rgb( - basic_color, - mixed_color, - )) -} diff --git a/color-module/src/series.rs b/color-module/src/series.rs new file mode 100644 index 0000000..1dba40a --- /dev/null +++ b/color-module/src/series.rs @@ -0,0 +1,87 @@ +use std::{str::FromStr, sync::Arc}; + +use palette::{ + cam16::{Cam16Jch, Parameters}, + Darken, FromColor, IntoColor, Lighten, Oklch, Srgb, +}; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::errors; + +#[wasm_bindgen] +pub fn series( + color: &str, + expand_amount: i16, + step: f32, +) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Arc::new(Oklch::from_color(origin_color.clone())); + + let mut color_series = Vec::new(); + for s in (1..=expand_amount).rev() { + let darkened_color = Arc::clone(&oklch).darken(s as f32 * step); + let srgb = Srgb::from_color(darkened_color); + color_series.push(format!("{:x}", srgb.into_format::())); + } + color_series.push(format!("{:x}", origin_color.into_format::())); + for s in 1..=expand_amount { + let lightened_color = Arc::clone(&oklch).lighten(s as f32 * step); + let srgb = Srgb::from_color(lightened_color); + color_series.push(format!("{:x}", srgb.into_format::())); + } + Ok(color_series) +} + +#[wasm_bindgen] +pub fn tonal_lighten_series( + color: &str, + expand_amount: i16, + step: f32, +) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let hct = Cam16Jch::from_xyz( + origin_color.into_color(), + Parameters::default_static_wp(40.0), + ); + + let mut color_series = Vec::new(); + let mut lightness = hct.lightness; + for _ in 1..=expand_amount { + lightness += (100.0 - lightness) * step; + let lightened_color = Cam16Jch::new(lightness, hct.chroma, hct.hue); + let srgb = Srgb::from_color(lightened_color.into_xyz(Parameters::default_static_wp(40.0))); + color_series.push(format!("{:x}", srgb.into_format::())); + } + + Ok(color_series) +} + +#[wasm_bindgen] +pub fn tonal_darken_series( + color: &str, + expand_amount: i16, + step: f32, +) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let hct = Cam16Jch::from_xyz( + origin_color.into_color(), + Parameters::default_static_wp(40.0), + ); + + let mut color_series = Vec::new(); + let mut lightness = hct.lightness; + for _ in 1..=expand_amount { + lightness *= 1.0 - step; + let darkened_color = Cam16Jch::new(lightness, hct.chroma, hct.hue); + let srgb = Srgb::from_color(darkened_color.into_xyz(Parameters::default_static_wp(40.0))); + color_series.push(format!("{:x}", srgb.into_format::())); + } + + Ok(color_series.into_iter().rev().collect()) +} diff --git a/color-module/src/theory.rs b/color-module/src/theory.rs new file mode 100644 index 0000000..9eccde8 --- /dev/null +++ b/color-module/src/theory.rs @@ -0,0 +1,108 @@ +use std::str::FromStr; + +use palette::{ + color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic}, + FromColor, Oklch, ShiftHue, Srgb, +}; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::errors; + +#[wasm_bindgen] +pub fn shift_hue(color: &str, degree: f32) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let shifted_color = oklch.shift_hue(degree); + let srgb = Srgb::from_color(shifted_color); + Ok(format!("{:x}", srgb.into_format::())) +} + +#[wasm_bindgen] +pub fn analogous_30(color: &str) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let (color_n30, color_p30) = oklch.analogous(); + let srgb_n30 = Srgb::from_color(color_n30); + let srgb_p30 = Srgb::from_color(color_p30); + Ok(vec![ + format!("{:x}", srgb_n30.into_format::()), + format!("{:x}", srgb_p30.into_format::()), + ]) +} + +#[wasm_bindgen] +pub fn analogous_60(color: &str) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let (color_n60, color_p60) = oklch.analogous_secondary(); + let srgb_n60 = Srgb::from_color(color_n60); + let srgb_p60 = Srgb::from_color(color_p60); + Ok(vec![ + format!("{:x}", srgb_n60.into_format::()), + format!("{:x}", srgb_p60.into_format::()), + ]) +} + +#[wasm_bindgen] +pub fn complementary(color: &str) -> Result { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let complementary_color = oklch.complementary(); + let srgb = Srgb::from_color(complementary_color); + Ok(format!("{:x}", srgb.into_format::())) +} + +#[wasm_bindgen] +pub fn split_complementary(color: &str) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let (color_p150, color_p210) = oklch.split_complementary(); + let srgb_p150 = Srgb::from_color(color_p150); + let srgb_p210 = Srgb::from_color(color_p210); + Ok(vec![ + format!("{:x}", srgb_p150.into_format::()), + format!("{:x}", srgb_p210.into_format::()), + ]) +} + +#[wasm_bindgen] +pub fn tetradic(color: &str) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let (color_p90, color_p180, color_p270) = oklch.tetradic(); + let srgb_p90 = Srgb::from_color(color_p90); + let srgb_p180 = Srgb::from_color(color_p180); + let srgb_p270 = Srgb::from_color(color_p270); + Ok(vec![ + format!("{:x}", srgb_p90.into_format::()), + format!("{:x}", srgb_p180.into_format::()), + format!("{:x}", srgb_p270.into_format::()), + ]) +} + +#[wasm_bindgen] +pub fn triadic(color: &str) -> Result, errors::ColorError> { + let origin_color = Srgb::from_str(color) + .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))? + .into_format::(); + let oklch = Oklch::from_color(origin_color); + let (color_p120, color_p240) = oklch.triadic(); + let srgb_p120 = Srgb::from_color(color_p120); + let srgb_p240 = Srgb::from_color(color_p240); + Ok(vec![ + format!("{:x}", srgb_p120.into_format::()), + format!("{:x}", srgb_p240.into_format::()), + ]) +}