增加内置的颜色色卡支持。

This commit is contained in:
徐涛 2025-01-10 09:03:14 +08:00
parent 5e7b1e709d
commit f2031f3d8c
3 changed files with 135 additions and 0 deletions

View File

@ -0,0 +1,89 @@
use std::sync::LazyLock;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColorDescription {
pub name: String,
pub pinyin: Vec<String>,
pub hue: f32,
pub lightness: f32,
pub category: String,
pub tags: Vec<String>,
pub rgb: [u8; 3],
pub hsl: [f32; 3],
pub lab: [f32; 3],
pub oklch: [f32; 3],
}
const COLOR_CARDS_JSON: &str = include_str!("colorcards.json");
pub const COLOR_CARDS: LazyLock<Vec<ColorDescription>> =
LazyLock::new(|| serde_json::from_str(COLOR_CARDS_JSON).expect("Failed to parse color cards"));
const CHROMA_EPSILON: f32 = 0.0001;
#[derive(Debug, Clone, PartialEq, Display, EnumString, EnumIter)]
#[strum(serialize_all = "lowercase")]
pub enum Category {
Red,
Orange,
Yellow,
Green,
Cyan,
Blue,
Purple,
Magenta,
White,
Black,
Gray,
Unknown,
}
impl Category {
pub fn from_oklch_components(lightness: f32, chroma: f32, hue: f32) -> Self {
if chroma < CHROMA_EPSILON {
if lightness < 0.1 {
Category::Black
} else if lightness > 0.9 {
Category::White
} else {
Category::Gray
}
} else {
let processed_hue = hue % 360.0;
match processed_hue {
0.0..=30.0 => Category::Red,
30.0..=60.0 => Category::Orange,
60.0..=90.0 => Category::Yellow,
90.0..=150.0 => Category::Green,
150.0..=210.0 => Category::Cyan,
210.0..=270.0 => Category::Blue,
270.0..=300.0 => Category::Purple,
300.0..=330.0 => Category::Magenta,
330.0..=360.0 => Category::Red,
_ => Category::Unknown,
}
}
}
pub fn from_oklch(oklch: &[f32; 3]) -> Self {
Category::from_oklch_components(oklch[0], oklch[1], oklch[2])
}
pub fn label(&self) -> String {
match self {
Category::Red => "Red".to_string(),
Category::Orange => "Orange".to_string(),
Category::Yellow => "Yellow".to_string(),
Category::Green => "Green".to_string(),
Category::Cyan => "Cyan".to_string(),
Category::Blue => "Blue".to_string(),
Category::Purple => "Purple".to_string(),
Category::Magenta => "Magenta".to_string(),
Category::White => "White".to_string(),
Category::Black => "Black".to_string(),
Category::Gray => "Gray".to_string(),
Category::Unknown => "Unknown".to_string(),
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
use std::{str::FromStr, sync::Arc};
use color_card::Category;
use palette::{
cam16::{Cam16Jch, Parameters},
color_difference::Wcag21RelativeContrast,
@ -7,8 +8,10 @@ use palette::{
convert::FromColorUnclamped,
Darken, FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Lighten, Mix, Oklch, ShiftHue, Srgb,
};
use strum::IntoEnumIterator;
use wasm_bindgen::prelude::*;
mod color_card;
mod errors;
#[wasm_bindgen]
@ -377,3 +380,45 @@ pub fn tonal_darken_series(
Ok(color_series.into_iter().rev().collect())
}
#[wasm_bindgen]
pub fn color_categories() -> Result<JsValue, String> {
let categories = Category::iter()
.map(|variant| {
serde_json::json!({
"label": variant.label(),
"value": variant.to_string(),
})
})
.collect::<Vec<_>>();
serde_wasm_bindgen::to_value(&categories).map_err(|e| e.to_string())
}
#[wasm_bindgen]
pub fn search_color_cards(tag: String, category: Option<String>) -> Result<JsValue, String> {
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::<Vec<_>>();
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())
}