增加从WASM中生成Material Design 3主题样式的功能。
This commit is contained in:
parent
c9626f3b8e
commit
d07fe9d41a
26
color-module/src/convert/mod.rs
Normal file
26
color-module/src/convert/mod.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use palette::{
|
||||
cam16::{Cam16Jch, Parameters},
|
||||
convert::FromColorUnclamped,
|
||||
IsWithinBounds, Srgb,
|
||||
};
|
||||
|
||||
pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
|
||||
let mut new_original = Cam16Jch::new(origin.lightness, origin.chroma, origin.hue);
|
||||
const FACTOR: f32 = 0.99;
|
||||
loop {
|
||||
let new_srgb =
|
||||
Srgb::from_color_unclamped(new_original.into_xyz(Parameters::default_static_wp(40.0)));
|
||||
if new_srgb.is_within_bounds() {
|
||||
break new_srgb;
|
||||
}
|
||||
new_original = Cam16Jch::new(
|
||||
new_original.lightness,
|
||||
new_original.chroma * FACTOR,
|
||||
new_original.hue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_cam16jch_to_srgb_hex(origin: &Cam16Jch<f32>) -> String {
|
||||
format!("{:x}", map_cam16jch_to_srgb(origin).into_format::<u8>())
|
||||
}
|
|
@ -7,6 +7,10 @@ pub enum ColorError {
|
|||
UnrecogniazedRGB(String),
|
||||
#[error("Some color component is out of bounds")]
|
||||
ComponentOutOfBounds,
|
||||
#[error("Unable to parse argument")]
|
||||
UnableToParseArgument,
|
||||
#[error("Unable to assemble output")]
|
||||
UnableToAssembleOutput,
|
||||
}
|
||||
|
||||
impl Into<JsValue> for ColorError {
|
||||
|
|
|
@ -14,9 +14,11 @@ use wasm_bindgen::prelude::*;
|
|||
|
||||
mod color_card;
|
||||
mod color_differ;
|
||||
mod convert;
|
||||
mod errors;
|
||||
mod palettes;
|
||||
mod reversing;
|
||||
mod schemes;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn represent_rgb(color: &str) -> Result<Box<[u8]>, errors::ColorError> {
|
||||
|
|
157
color-module/src/schemes/material_design_3/baseline.rs
Normal file
157
color-module/src/schemes/material_design_3/baseline.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
|
||||
use super::{color_set::M3ColorSet, surface::M3SurfaceSet, tonal_palette::TonalPalette};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct M3BaselineColors {
|
||||
pub primary: M3ColorSet,
|
||||
pub secondary: M3ColorSet,
|
||||
pub tertiary: M3ColorSet,
|
||||
pub error: M3ColorSet,
|
||||
pub surface: M3SurfaceSet,
|
||||
pub outline: String,
|
||||
pub outline_variant: String,
|
||||
pub scrim: String,
|
||||
pub shadown: String,
|
||||
pub customs: HashMap<String, M3ColorSet>,
|
||||
dark_set: bool,
|
||||
}
|
||||
|
||||
impl M3BaselineColors {
|
||||
pub fn new(
|
||||
p: &TonalPalette,
|
||||
s: &TonalPalette,
|
||||
t: &TonalPalette,
|
||||
n: &TonalPalette,
|
||||
nv: &TonalPalette,
|
||||
e: &TonalPalette,
|
||||
dark_set: bool,
|
||||
) -> Self {
|
||||
let color_set_generator = if dark_set {
|
||||
M3ColorSet::new_dark_set
|
||||
} else {
|
||||
M3ColorSet::new_light_set
|
||||
};
|
||||
let surface_set_generator = if dark_set {
|
||||
M3SurfaceSet::new_dark_set
|
||||
} else {
|
||||
M3SurfaceSet::new_light_set
|
||||
};
|
||||
|
||||
let primary = color_set_generator(p);
|
||||
let secondary = color_set_generator(s);
|
||||
let tertiary = color_set_generator(t);
|
||||
let surface = surface_set_generator(n, nv);
|
||||
let error = color_set_generator(e);
|
||||
let outline = if dark_set { n.tone(60.0) } else { n.tone(50.0) };
|
||||
let outline_variant = if dark_set {
|
||||
nv.tone(30.0)
|
||||
} else {
|
||||
nv.tone(80.0)
|
||||
};
|
||||
let scrim = n.tone(0.0);
|
||||
let shadow = n.tone(0.0);
|
||||
|
||||
Self {
|
||||
primary,
|
||||
secondary,
|
||||
tertiary,
|
||||
error,
|
||||
surface,
|
||||
outline: map_cam16jch_to_srgb_hex(&outline),
|
||||
outline_variant: map_cam16jch_to_srgb_hex(&outline_variant),
|
||||
scrim: map_cam16jch_to_srgb_hex(&scrim),
|
||||
shadown: map_cam16jch_to_srgb_hex(&shadow),
|
||||
customs: HashMap::new(),
|
||||
dark_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_custom_set(&mut self, name: String, c: &TonalPalette) {
|
||||
let color_set_generator = if self.dark_set {
|
||||
M3ColorSet::new_dark_set
|
||||
} else {
|
||||
M3ColorSet::new_light_set
|
||||
};
|
||||
self.customs.insert(name, color_set_generator(c));
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self) -> Vec<String> {
|
||||
let mut css_variables = Vec::new();
|
||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||
|
||||
css_variables.extend(self.primary.to_css_variables(prefix, "primary"));
|
||||
css_variables.extend(self.secondary.to_css_variables(prefix, "secondary"));
|
||||
css_variables.extend(self.tertiary.to_css_variables(prefix, "tertiary"));
|
||||
css_variables.extend(self.error.to_css_variables(prefix, "error"));
|
||||
css_variables.extend(self.surface.to_css_variables(prefix));
|
||||
css_variables.push(format!("--color-{}-outline: #{};", prefix, self.outline));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-outline-variant: #{};",
|
||||
prefix, self.outline_variant
|
||||
));
|
||||
css_variables.push(format!("--color-{}-scrim: #{};", prefix, self.scrim));
|
||||
css_variables.push(format!("--color-{}-shadow: #{};", prefix, self.shadown));
|
||||
for (name, color_set) in &self.customs {
|
||||
css_variables.extend(color_set.to_css_variables(prefix, name));
|
||||
}
|
||||
|
||||
css_variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self) -> Vec<String> {
|
||||
let mut scss_variables = Vec::new();
|
||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||
|
||||
scss_variables.extend(self.primary.to_scss_variables(prefix, "primary"));
|
||||
scss_variables.extend(self.secondary.to_scss_variables(prefix, "secondary"));
|
||||
scss_variables.extend(self.tertiary.to_scss_variables(prefix, "tertiary"));
|
||||
scss_variables.extend(self.error.to_scss_variables(prefix, "error"));
|
||||
scss_variables.extend(self.surface.to_scss_variables(prefix));
|
||||
scss_variables.push(format!("$color-{}-outline: #{};", prefix, self.outline));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-outline-variant: #{};",
|
||||
prefix, self.outline_variant
|
||||
));
|
||||
scss_variables.push(format!("$color-{}-scrim: #{};", prefix, self.scrim));
|
||||
scss_variables.push(format!("$color-{}-shadow: #{};", prefix, self.shadown));
|
||||
for (name, color_set) in &self.customs {
|
||||
scss_variables.extend(color_set.to_scss_variables(prefix, name));
|
||||
}
|
||||
|
||||
scss_variables
|
||||
}
|
||||
|
||||
pub fn to_javascript_object_fields(&self) -> Vec<String> {
|
||||
let mut js_object_fields = Vec::new();
|
||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||
|
||||
js_object_fields.extend(self.primary.to_javascript_object_fields(prefix, "primary"));
|
||||
js_object_fields.extend(
|
||||
self.secondary
|
||||
.to_javascript_object_fields(prefix, "secondary"),
|
||||
);
|
||||
js_object_fields.extend(
|
||||
self.tertiary
|
||||
.to_javascript_object_fields(prefix, "tertiary"),
|
||||
);
|
||||
js_object_fields.extend(self.error.to_javascript_object_fields(prefix, "error"));
|
||||
js_object_fields.extend(self.surface.to_javascript_object_fields(prefix));
|
||||
js_object_fields.push(format!("{}Outline: '#{}',", prefix, self.outline));
|
||||
js_object_fields.push(format!(
|
||||
"{}OutlineVariant: '#{}',",
|
||||
prefix, self.outline_variant
|
||||
));
|
||||
js_object_fields.push(format!("{}Scrim: '#{}',", prefix, self.scrim));
|
||||
js_object_fields.push(format!("{}Shadow: '#{}',", prefix, self.shadown));
|
||||
for (name, color_set) in &self.customs {
|
||||
js_object_fields.extend(color_set.to_javascript_object_fields(prefix, name));
|
||||
}
|
||||
|
||||
js_object_fields
|
||||
}
|
||||
}
|
181
color-module/src/schemes/material_design_3/color_set.rs
Normal file
181
color-module/src/schemes/material_design_3/color_set.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
|
||||
use super::tonal_palette::TonalPalette;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct M3ColorSet {
|
||||
pub root: String,
|
||||
pub on_root: String,
|
||||
pub container: String,
|
||||
pub on_conatiner: String,
|
||||
pub fixed: String,
|
||||
pub fixed_dim: String,
|
||||
pub on_fixed: String,
|
||||
pub fixed_variant: String,
|
||||
pub inverse: String,
|
||||
}
|
||||
|
||||
impl M3ColorSet {
|
||||
pub fn new_light_set(palette: &TonalPalette) -> Self {
|
||||
let root = palette.tone(40.0);
|
||||
let on_root = palette.tone(100.0);
|
||||
let container = palette.tone(90.0);
|
||||
let on_container = palette.tone(30.0);
|
||||
let fixed = palette.tone(90.0);
|
||||
let fixed_dim = palette.tone(80.0);
|
||||
let on_fixed = palette.tone(10.0);
|
||||
let fixed_variant = palette.tone(30.0);
|
||||
let inverse = palette.tone(80.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
on_conatiner: map_cam16jch_to_srgb_hex(&on_container),
|
||||
fixed: map_cam16jch_to_srgb_hex(&fixed),
|
||||
fixed_dim: map_cam16jch_to_srgb_hex(&fixed_dim),
|
||||
on_fixed: map_cam16jch_to_srgb_hex(&on_fixed),
|
||||
fixed_variant: map_cam16jch_to_srgb_hex(&fixed_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dark_set(palette: &TonalPalette) -> Self {
|
||||
let root = palette.tone(80.0);
|
||||
let on_root = palette.tone(20.0);
|
||||
let container = palette.tone(30.0);
|
||||
let on_container = palette.tone(90.0);
|
||||
let fixed = palette.tone(90.0);
|
||||
let fixed_dim = palette.tone(80.0);
|
||||
let on_fixed = palette.tone(10.0);
|
||||
let fixed_variant = palette.tone(30.0);
|
||||
let inverse = palette.tone(40.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
on_conatiner: map_cam16jch_to_srgb_hex(&on_container),
|
||||
fixed: map_cam16jch_to_srgb_hex(&fixed),
|
||||
fixed_dim: map_cam16jch_to_srgb_hex(&fixed_dim),
|
||||
on_fixed: map_cam16jch_to_srgb_hex(&on_fixed),
|
||||
fixed_variant: map_cam16jch_to_srgb_hex(&fixed_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
|
||||
variable_lines.push(format!("--color-{}-{}: #{};", prefix, name, self.root));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-on-{}: #{};",
|
||||
prefix, name, self.on_root
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-{}-container: #{};",
|
||||
prefix, name, self.container
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-on-{}-container: #{};",
|
||||
prefix, name, self.on_conatiner
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-{}-fixed: #{};",
|
||||
prefix, name, self.fixed
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-{}-fixed-dim: #{};",
|
||||
prefix, name, self.fixed_dim
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-on-{}-fixed: #{};",
|
||||
prefix, name, self.on_fixed
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-on-{}-fixed-variant: #{};",
|
||||
prefix, name, self.fixed_variant
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-inverse-{}: #{};",
|
||||
prefix, name, self.inverse
|
||||
));
|
||||
|
||||
variable_lines
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
|
||||
variable_lines.push(format!("$color-{}-{}: #{};", prefix, name, self.root));
|
||||
variable_lines.push(format!("$color-{}-on-{}: #{};", prefix, name, self.on_root));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-{}-container: #{};",
|
||||
prefix, name, self.container
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-on-{}-container: #{};",
|
||||
prefix, name, self.on_conatiner
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-{}-fixed: #{};",
|
||||
prefix, name, self.fixed
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-{}-fixed-dim: #{};",
|
||||
prefix, name, self.fixed_dim
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-on-{}-fixed: #{};",
|
||||
prefix, name, self.on_fixed
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-on-{}-fixed-variant: #{};",
|
||||
prefix, name, self.fixed_variant
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-inverse-{}: #{};",
|
||||
prefix, name, self.inverse
|
||||
));
|
||||
|
||||
variable_lines
|
||||
}
|
||||
|
||||
pub fn to_javascript_object_fields(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
let prefix = prefix.to_ascii_lowercase();
|
||||
let name = name
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_ascii_uppercase()
|
||||
.to_string()
|
||||
+ &name[1..];
|
||||
|
||||
variable_lines.push(format!("{}{}: '#{}',", prefix, name, self.root));
|
||||
variable_lines.push(format!("{}on{}: '#{}',", prefix, name, self.on_root));
|
||||
variable_lines.push(format!(
|
||||
"{}{}Container: '#{}',",
|
||||
prefix, name, self.container
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"{}On{}Container: '#{}',",
|
||||
prefix, name, self.on_conatiner
|
||||
));
|
||||
variable_lines.push(format!("{}{}Fixed: '#{}',", prefix, name, self.fixed));
|
||||
variable_lines.push(format!(
|
||||
"{}{}FixedDim: '#{}',",
|
||||
prefix, name, self.fixed_dim
|
||||
));
|
||||
variable_lines.push(format!("{}On{}Fixed: '#{}',", prefix, name, self.on_fixed));
|
||||
variable_lines.push(format!(
|
||||
"{}On{}FixedVariant: '#{}',",
|
||||
prefix, name, self.fixed_variant
|
||||
));
|
||||
variable_lines.push(format!("{}Inverse{}: '#{}',", prefix, name, self.inverse));
|
||||
|
||||
variable_lines
|
||||
}
|
||||
}
|
130
color-module/src/schemes/material_design_3/mod.rs
Normal file
130
color-module/src/schemes/material_design_3/mod.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use baseline::M3BaselineColors;
|
||||
use palette::cam16::{Cam16Jch, Parameters};
|
||||
use palette::{IntoColor, Srgb};
|
||||
use serde::Serialize;
|
||||
use tonal_palette::TonalPalette;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
use crate::errors;
|
||||
|
||||
use super::SchemeExport;
|
||||
|
||||
mod baseline;
|
||||
mod color_set;
|
||||
mod surface;
|
||||
mod tonal_palette;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct MaterialDesign3Scheme {
|
||||
pub white: String,
|
||||
pub black: String,
|
||||
pub light_baseline: M3BaselineColors,
|
||||
pub dark_baseline: M3BaselineColors,
|
||||
}
|
||||
|
||||
impl MaterialDesign3Scheme {
|
||||
pub fn new(source_color: &str, error_color: &str) -> Result<Self, errors::ColorError> {
|
||||
let source = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(source_color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(source_color.to_string()))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
let error = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(error_color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(error_color.to_string()))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
let source_hue = source.hue.into_positive_degrees();
|
||||
let p = TonalPalette::from_hue_and_chroma(source_hue, source.chroma);
|
||||
let s = TonalPalette::from_hue_and_chroma(source_hue, source.chroma / 3.0);
|
||||
let t = TonalPalette::from_hue_and_chroma(source_hue + 60.0, source.chroma / 2.0);
|
||||
let n = TonalPalette::from_hue_and_chroma(source_hue, (source.chroma / 12.0).min(4.0));
|
||||
let nv = TonalPalette::from_hue_and_chroma(source_hue, (source.chroma / 6.0).min(8.0));
|
||||
let e = TonalPalette::from_hue_and_chroma(error.hue.into_positive_degrees(), 84.0);
|
||||
|
||||
Ok(Self {
|
||||
white: map_cam16jch_to_srgb_hex(&Cam16Jch::new(100.0, 0.0, 0.0)),
|
||||
black: map_cam16jch_to_srgb_hex(&Cam16Jch::new(0.0, 0.0, 0.0)),
|
||||
light_baseline: M3BaselineColors::new(&p, &s, &t, &n, &nv, &e, false),
|
||||
dark_baseline: M3BaselineColors::new(&p, &s, &t, &n, &nv, &e, true),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_custom_color(
|
||||
&mut self,
|
||||
name: String,
|
||||
color: String,
|
||||
) -> Result<(), errors::ColorError> {
|
||||
let custom_color = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(&color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.clone()))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
let hue = custom_color.hue.into_positive_degrees();
|
||||
let palette = TonalPalette::from_hue_and_chroma(hue, custom_color.chroma);
|
||||
self.light_baseline.add_custom_set(name.clone(), &palette);
|
||||
self.dark_baseline.add_custom_set(name.clone(), &palette);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeExport for MaterialDesign3Scheme {
|
||||
fn output_css_variables(&self) -> String {
|
||||
let mut css_variables = Vec::new();
|
||||
|
||||
css_variables.push(format!("--color-white: #{};", self.white));
|
||||
css_variables.push(format!("--color-black: #{};", self.black));
|
||||
css_variables.extend(self.light_baseline.to_css_variables());
|
||||
css_variables.extend(self.dark_baseline.to_css_variables());
|
||||
|
||||
css_variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_scss_variables(&self) -> String {
|
||||
let mut scss_variables = Vec::new();
|
||||
|
||||
scss_variables.push(format!("$color-white: #{};", self.white));
|
||||
scss_variables.push(format!("$color-black: #{};", self.black));
|
||||
scss_variables.extend(self.light_baseline.to_scss_variables());
|
||||
scss_variables.extend(self.dark_baseline.to_scss_variables());
|
||||
|
||||
scss_variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_javascript_object(&self) -> String {
|
||||
let mut js_object = Vec::new();
|
||||
|
||||
js_object.push("const colorScheme = {".to_string());
|
||||
js_object.push(format!(" white: '#{}'", self.white));
|
||||
js_object.push(format!(" black: '#{}'", self.black));
|
||||
js_object.push(" light: {".to_string());
|
||||
js_object.extend(
|
||||
self.light_baseline
|
||||
.to_javascript_object_fields()
|
||||
.into_iter()
|
||||
.map(|s| format!(" {}", s))
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
js_object.push(" },".to_string());
|
||||
js_object.push(" dark: {".to_string());
|
||||
js_object.extend(
|
||||
self.dark_baseline
|
||||
.to_javascript_object_fields()
|
||||
.into_iter()
|
||||
.map(|s| format!(" {}", s))
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
js_object.push(" },".to_string());
|
||||
js_object.push("}".to_string());
|
||||
|
||||
js_object.join(",\n")
|
||||
}
|
||||
}
|
215
color-module/src/schemes/material_design_3/surface.rs
Normal file
215
color-module/src/schemes/material_design_3/surface.rs
Normal file
|
@ -0,0 +1,215 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
|
||||
use super::tonal_palette::TonalPalette;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct M3SurfaceSet {
|
||||
pub root: String,
|
||||
pub dim: String,
|
||||
pub bright: String,
|
||||
pub container: String,
|
||||
pub container_lowest: String,
|
||||
pub container_low: String,
|
||||
pub container_high: String,
|
||||
pub container_highest: String,
|
||||
pub on_root: String,
|
||||
pub on_root_variant: String,
|
||||
pub inverse: String,
|
||||
pub on_inverse: String,
|
||||
}
|
||||
|
||||
impl M3SurfaceSet {
|
||||
pub fn new_light_set(neutral: &TonalPalette, neutral_variant: &TonalPalette) -> Self {
|
||||
let root = neutral.tone(98.0);
|
||||
let dim = neutral.tone(87.0);
|
||||
let bright = neutral.tone(98.0);
|
||||
let container = neutral.tone(94.0);
|
||||
let container_lowest = neutral.tone(100.0);
|
||||
let container_low = neutral.tone(96.0);
|
||||
let container_high = neutral.tone(92.0);
|
||||
let container_highest = neutral.tone(90.0);
|
||||
let on_root = neutral_variant.tone(10.0);
|
||||
let on_root_variant = neutral_variant.tone(30.0);
|
||||
let inverse = neutral.tone(20.0);
|
||||
let on_inverse = neutral_variant.tone(95.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
dim: map_cam16jch_to_srgb_hex(&dim),
|
||||
bright: map_cam16jch_to_srgb_hex(&bright),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
container_lowest: map_cam16jch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_cam16jch_to_srgb_hex(&container_low),
|
||||
container_high: map_cam16jch_to_srgb_hex(&container_high),
|
||||
container_highest: map_cam16jch_to_srgb_hex(&container_highest),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
on_root_variant: map_cam16jch_to_srgb_hex(&on_root_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
on_inverse: map_cam16jch_to_srgb_hex(&on_inverse),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dark_set(neutral: &TonalPalette, neutral_variant: &TonalPalette) -> Self {
|
||||
let root = neutral.tone(6.0);
|
||||
let dim = neutral.tone(6.0);
|
||||
let bright = neutral.tone(24.0);
|
||||
let container = neutral.tone(12.0);
|
||||
let container_lowest = neutral.tone(4.0);
|
||||
let container_low = neutral.tone(10.0);
|
||||
let container_high = neutral.tone(17.0);
|
||||
let container_highest = neutral.tone(22.0);
|
||||
let on_root = neutral_variant.tone(90.0);
|
||||
let on_root_variant = neutral_variant.tone(80.0);
|
||||
let inverse = neutral.tone(90.0);
|
||||
let on_inverse = neutral_variant.tone(20.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
dim: map_cam16jch_to_srgb_hex(&dim),
|
||||
bright: map_cam16jch_to_srgb_hex(&bright),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
container_lowest: map_cam16jch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_cam16jch_to_srgb_hex(&container_low),
|
||||
container_high: map_cam16jch_to_srgb_hex(&container_high),
|
||||
container_highest: map_cam16jch_to_srgb_hex(&container_highest),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
on_root_variant: map_cam16jch_to_srgb_hex(&on_root_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
on_inverse: map_cam16jch_to_srgb_hex(&on_inverse),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, prefix: &str) -> Vec<String> {
|
||||
let mut css_variables = Vec::new();
|
||||
|
||||
css_variables.push(format!("--color-{}-surface: ${};", prefix, self.root));
|
||||
css_variables.push(format!("--color-{}-surface-dim: ${};", prefix, self.dim));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-bright: ${};",
|
||||
prefix, self.bright
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container: ${};",
|
||||
prefix, self.container
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-lowest: ${};",
|
||||
prefix, self.container_lowest
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-low: ${};",
|
||||
prefix, self.container_low
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-high: ${};",
|
||||
prefix, self.container_high
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-highest: ${};",
|
||||
prefix, self.container_highest
|
||||
));
|
||||
css_variables.push(format!("--color-{}-on-surface: ${};", prefix, self.on_root));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-on-surface-variant: ${};",
|
||||
prefix, self.on_root_variant
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-inverse-surface: ${};",
|
||||
prefix, self.inverse
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-inverse-on-surface: ${};",
|
||||
prefix, self.on_inverse
|
||||
));
|
||||
|
||||
css_variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str) -> Vec<String> {
|
||||
let mut scss_variables = Vec::new();
|
||||
|
||||
scss_variables.push(format!("$color-{}-surface: ${};", prefix, self.root));
|
||||
scss_variables.push(format!("$color-{}-surface-dim: ${};", prefix, self.dim));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-bright: ${};",
|
||||
prefix, self.bright
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container: ${};",
|
||||
prefix, self.container
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-lowest: ${};",
|
||||
prefix, self.container_lowest
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-low: ${};",
|
||||
prefix, self.container_low
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-high: ${};",
|
||||
prefix, self.container_high
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-highest: ${};",
|
||||
prefix, self.container_highest
|
||||
));
|
||||
scss_variables.push(format!("$color-{}-on-surface: ${};", prefix, self.on_root));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-on-surface-variant: ${};",
|
||||
prefix, self.on_root_variant
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-inverse-surface: ${};",
|
||||
prefix, self.inverse
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-inverse-on-surface: ${};",
|
||||
prefix, self.on_inverse
|
||||
));
|
||||
|
||||
scss_variables
|
||||
}
|
||||
|
||||
pub fn to_javascript_object_fields(&self, prefix: &str) -> Vec<String> {
|
||||
let mut js_object_fields = Vec::new();
|
||||
|
||||
js_object_fields.push(format!("{}Surface: '#{}',", prefix, self.root));
|
||||
js_object_fields.push(format!("{}SurfaceDim: '#{}',", prefix, self.dim));
|
||||
js_object_fields.push(format!("{}SurfaceBright: '#{}',", prefix, self.bright));
|
||||
js_object_fields.push(format!(
|
||||
"{}SurfaceContainer: '#{}',",
|
||||
prefix, self.container
|
||||
));
|
||||
js_object_fields.push(format!(
|
||||
"{}SurfaceContainerLowest: '#{}',",
|
||||
prefix, self.container_lowest
|
||||
));
|
||||
js_object_fields.push(format!(
|
||||
"{}SurfaceContainerLow: '#{}',",
|
||||
prefix, self.container_low
|
||||
));
|
||||
js_object_fields.push(format!(
|
||||
"{}SurfaceContainerHigh: '#{}',",
|
||||
prefix, self.container_high
|
||||
));
|
||||
js_object_fields.push(format!(
|
||||
"{}SurfaceContainerHighest: '#{}',",
|
||||
prefix, self.container_highest
|
||||
));
|
||||
js_object_fields.push(format!("{}OnSurface: '#{}',", prefix, self.on_root));
|
||||
js_object_fields.push(format!(
|
||||
"{}OnSurfaceVariant: '#{}',",
|
||||
prefix, self.on_root_variant
|
||||
));
|
||||
js_object_fields.push(format!("{}InverseSurface: '#{}',", prefix, self.inverse));
|
||||
js_object_fields.push(format!(
|
||||
"{}InverseOnSurface: '#{}',",
|
||||
prefix, self.on_inverse
|
||||
));
|
||||
|
||||
js_object_fields
|
||||
}
|
||||
}
|
95
color-module/src/schemes/material_design_3/tonal_palette.rs
Normal file
95
color-module/src/schemes/material_design_3/tonal_palette.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use palette::{
|
||||
cam16::{Cam16Jch, Parameters},
|
||||
IntoColor, Srgb,
|
||||
};
|
||||
|
||||
use crate::errors;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TonalPalette {
|
||||
pub key_color: Cam16Jch<f32>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approximately_equal(a: f32, b: f32) -> bool {
|
||||
const EPSILON: f32 = 0.000001;
|
||||
(a - b).abs() < EPSILON
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn find_max_chroma(cache: &mut Vec<(f32, f32)>, hue: f32, tone: f32) -> f32 {
|
||||
for (k, v) in cache.iter() {
|
||||
if approximately_equal(*k, tone) {
|
||||
return *v;
|
||||
}
|
||||
}
|
||||
let chroma = Cam16Jch::new(tone, 200.0, hue).chroma;
|
||||
cache.push((tone, chroma));
|
||||
chroma
|
||||
}
|
||||
|
||||
fn from_hue_and_chroma(hue: f32, chroma: f32) -> Cam16Jch<f32> {
|
||||
let mut max_chroma_cache = Vec::new();
|
||||
let hue = if hue >= 360.0 { hue - 360.0 } else { hue };
|
||||
const PIVOT_TONE: f32 = 50.0;
|
||||
const TONE_STEP_SIZE: f32 = 1.0;
|
||||
const EPSILON: f32 = 0.01;
|
||||
|
||||
let mut lower_tone = 0.0_f32;
|
||||
let mut upper_tone = 100.0_f32;
|
||||
while lower_tone < upper_tone {
|
||||
let mid_tone = ((lower_tone + upper_tone) / 2.0).floor();
|
||||
let is_ascending = find_max_chroma(&mut max_chroma_cache, hue, mid_tone)
|
||||
< find_max_chroma(&mut max_chroma_cache, hue, mid_tone + TONE_STEP_SIZE);
|
||||
let sufficient_chroma =
|
||||
find_max_chroma(&mut max_chroma_cache, hue, mid_tone) >= chroma - EPSILON;
|
||||
|
||||
if sufficient_chroma {
|
||||
if (lower_tone - PIVOT_TONE).abs() < (upper_tone - PIVOT_TONE).abs() {
|
||||
upper_tone = mid_tone;
|
||||
} else {
|
||||
if approximately_equal(lower_tone, mid_tone) {
|
||||
return Cam16Jch::new(lower_tone, chroma, hue);
|
||||
}
|
||||
lower_tone = mid_tone;
|
||||
}
|
||||
} else {
|
||||
if is_ascending {
|
||||
lower_tone = mid_tone + TONE_STEP_SIZE;
|
||||
} else {
|
||||
upper_tone = mid_tone;
|
||||
}
|
||||
}
|
||||
}
|
||||
Cam16Jch::new(lower_tone, chroma, hue)
|
||||
}
|
||||
|
||||
impl TryFrom<String> for TonalPalette {
|
||||
type Error = errors::ColorError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
let key_color = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(&value)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(value))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
Ok(TonalPalette { key_color })
|
||||
}
|
||||
}
|
||||
|
||||
impl TonalPalette {
|
||||
pub fn from_hue_and_chroma(hue: f32, chroma: f32) -> Self {
|
||||
let key_color = from_hue_and_chroma(hue, chroma);
|
||||
TonalPalette { key_color }
|
||||
}
|
||||
|
||||
pub fn tone(&self, tone: f32) -> Cam16Jch<f32> {
|
||||
let toned_color = Cam16Jch::new(tone, self.key_color.chroma, self.key_color.hue);
|
||||
|
||||
toned_color
|
||||
}
|
||||
}
|
35
color-module/src/schemes/mod.rs
Normal file
35
color-module/src/schemes/mod.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use material_design_3::MaterialDesign3Scheme;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
||||
use crate::errors;
|
||||
|
||||
pub mod material_design_3;
|
||||
|
||||
pub trait SchemeExport {
|
||||
fn output_css_variables(&self) -> String;
|
||||
fn output_scss_variables(&self) -> String;
|
||||
fn output_javascript_object(&self) -> String;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_material_design_3_scheme(
|
||||
source_color: &str,
|
||||
error_color: &str,
|
||||
custom_colors: JsValue,
|
||||
) -> Result<JsValue, errors::ColorError> {
|
||||
let custom_colors: HashMap<String, String> = serde_wasm_bindgen::from_value(custom_colors)
|
||||
.map_err(|_| errors::ColorError::UnableToParseArgument)?;
|
||||
let mut scheme = MaterialDesign3Scheme::new(source_color, error_color)?;
|
||||
for (name, color) in custom_colors {
|
||||
scheme.add_custom_color(name, color)?;
|
||||
}
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
Loading…
Reference in New Issue
Block a user