增加Material Design 2主题样式的生成和导出。
This commit is contained in:
parent
d07fe9d41a
commit
119436608a
|
@ -1,7 +1,7 @@
|
||||||
use palette::{
|
use palette::{
|
||||||
cam16::{Cam16Jch, Parameters},
|
cam16::{Cam16Jch, Parameters},
|
||||||
convert::FromColorUnclamped,
|
convert::FromColorUnclamped,
|
||||||
IsWithinBounds, Srgb,
|
Hsl, IsWithinBounds, Srgb,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
|
pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
|
||||||
|
@ -24,3 +24,23 @@ pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
|
||||||
pub fn map_cam16jch_to_srgb_hex(origin: &Cam16Jch<f32>) -> String {
|
pub fn map_cam16jch_to_srgb_hex(origin: &Cam16Jch<f32>) -> String {
|
||||||
format!("{:x}", map_cam16jch_to_srgb(origin).into_format::<u8>())
|
format!("{:x}", map_cam16jch_to_srgb(origin).into_format::<u8>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_hsl_to_srgb(origin: &Hsl) -> Srgb {
|
||||||
|
let mut new_original = Hsl::new(origin.hue, origin.saturation, origin.lightness);
|
||||||
|
const FACTOR: f32 = 0.99;
|
||||||
|
loop {
|
||||||
|
let new_srgb = Srgb::from_color_unclamped(new_original);
|
||||||
|
if new_srgb.is_within_bounds() {
|
||||||
|
break new_srgb;
|
||||||
|
}
|
||||||
|
new_original = Hsl::new(
|
||||||
|
new_original.hue,
|
||||||
|
new_original.saturation * FACTOR,
|
||||||
|
new_original.lightness,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_hsl_to_srgb_hex(origin: &Hsl) -> String {
|
||||||
|
format!("{:x}", map_hsl_to_srgb(origin).into_format::<u8>())
|
||||||
|
}
|
||||||
|
|
146
color-module/src/schemes/material_design_2/baseline.rs
Normal file
146
color-module/src/schemes/material_design_2/baseline.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
|
|
||||||
|
use crate::{convert::map_hsl_to_srgb_hex, errors, schemes::material_design_2::swatch::M2Swatch};
|
||||||
|
|
||||||
|
use super::{color_set::M2ColorSet, swatch::SwatchIndex};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct M2BaselineColors {
|
||||||
|
pub primary: M2ColorSet,
|
||||||
|
pub secondary: M2ColorSet,
|
||||||
|
pub error: M2ColorSet,
|
||||||
|
pub background: M2ColorSet,
|
||||||
|
pub surface: M2ColorSet,
|
||||||
|
pub shadow: String,
|
||||||
|
pub custom_colors: HashMap<String, M2ColorSet>,
|
||||||
|
neutral_swatch: M2Swatch,
|
||||||
|
dark_set: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl M2BaselineColors {
|
||||||
|
pub fn new(
|
||||||
|
primary_color: &str,
|
||||||
|
secondary_color: &str,
|
||||||
|
error_color: &str,
|
||||||
|
dark_baseline: bool,
|
||||||
|
) -> Result<Self, errors::ColorError> {
|
||||||
|
let primary_swatch = M2Swatch::from_color(primary_color)?;
|
||||||
|
let secondary_swatch = M2Swatch::from_color(secondary_color)?;
|
||||||
|
let error_swatch = M2Swatch::from_color(error_color)?;
|
||||||
|
let neutral_swatch = M2Swatch::default_neutral();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
primary: M2ColorSet::from_swatch(
|
||||||
|
&primary_swatch,
|
||||||
|
&neutral_swatch,
|
||||||
|
dark_baseline,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
secondary: M2ColorSet::from_swatch(
|
||||||
|
&secondary_swatch,
|
||||||
|
&neutral_swatch,
|
||||||
|
dark_baseline,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
error: M2ColorSet::from_swatch(&error_swatch, &neutral_swatch, dark_baseline, None)?,
|
||||||
|
background: M2ColorSet::from_swatch(
|
||||||
|
&neutral_swatch,
|
||||||
|
&neutral_swatch,
|
||||||
|
dark_baseline,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
surface: M2ColorSet::from_swatch(
|
||||||
|
&neutral_swatch,
|
||||||
|
&neutral_swatch,
|
||||||
|
dark_baseline,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
shadow: map_hsl_to_srgb_hex(&neutral_swatch.tone(SwatchIndex::SI900)),
|
||||||
|
custom_colors: HashMap::new(),
|
||||||
|
neutral_swatch,
|
||||||
|
dark_set: dark_baseline,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_custom_color(&mut self, name: &str, color: &str) -> Result<(), errors::ColorError> {
|
||||||
|
let swatch = M2Swatch::from_color(color)?;
|
||||||
|
self.custom_colors.insert(
|
||||||
|
name.to_string(),
|
||||||
|
M2ColorSet::from_swatch(&swatch, &self.neutral_swatch, self.dark_set, None)?,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_css_variable(&self) -> Vec<String> {
|
||||||
|
let mut variable_lines = Vec::new();
|
||||||
|
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||||
|
|
||||||
|
variable_lines.extend(self.primary.to_css_variable(&prefix, "primary"));
|
||||||
|
variable_lines.extend(self.secondary.to_css_variable(&prefix, "secondary"));
|
||||||
|
variable_lines.extend(self.error.to_css_variable(&prefix, "error"));
|
||||||
|
variable_lines.extend(self.background.to_css_variable(&prefix, "background"));
|
||||||
|
variable_lines.extend(self.surface.to_css_variable(&prefix, "surface"));
|
||||||
|
variable_lines.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
|
||||||
|
|
||||||
|
for (name, color_set) in &self.custom_colors {
|
||||||
|
variable_lines.extend(color_set.to_css_variable(&prefix, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_lines
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_scss_variable(&self) -> Vec<String> {
|
||||||
|
let mut variable_lines = Vec::new();
|
||||||
|
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||||
|
|
||||||
|
variable_lines.extend(self.primary.to_scss_variable(&prefix, "primary"));
|
||||||
|
variable_lines.extend(self.secondary.to_scss_variable(&prefix, "secondary"));
|
||||||
|
variable_lines.extend(self.error.to_scss_variable(&prefix, "error"));
|
||||||
|
variable_lines.extend(self.background.to_scss_variable(&prefix, "background"));
|
||||||
|
variable_lines.extend(self.surface.to_scss_variable(&prefix, "surface"));
|
||||||
|
variable_lines.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
|
||||||
|
|
||||||
|
for (name, color_set) in &self.custom_colors {
|
||||||
|
variable_lines.extend(color_set.to_scss_variable(&prefix, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_lines
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_javascript_object(&self) -> Vec<String> {
|
||||||
|
let mut variable_lines = Vec::new();
|
||||||
|
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||||
|
|
||||||
|
variable_lines.extend(self.primary.to_javascript_object(&prefix, "primary"));
|
||||||
|
variable_lines.extend(self.secondary.to_javascript_object(&prefix, "secondary"));
|
||||||
|
variable_lines.extend(self.error.to_javascript_object(&prefix, "error"));
|
||||||
|
variable_lines.extend(self.background.to_javascript_object(&prefix, "background"));
|
||||||
|
variable_lines.extend(self.surface.to_javascript_object(&prefix, "surface"));
|
||||||
|
variable_lines.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
|
||||||
|
|
||||||
|
for (name, color_set) in &self.custom_colors {
|
||||||
|
variable_lines.extend(color_set.to_javascript_object(&prefix, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for M2BaselineColors {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let mut map = serializer.serialize_struct("baseline", 7)?;
|
||||||
|
map.serialize_field("primary", &self.primary)?;
|
||||||
|
map.serialize_field("secondary", &self.secondary)?;
|
||||||
|
map.serialize_field("error", &self.error)?;
|
||||||
|
map.serialize_field("background", &self.background)?;
|
||||||
|
map.serialize_field("surface", &self.surface)?;
|
||||||
|
map.serialize_field("shadow", &self.shadow)?;
|
||||||
|
map.serialize_field("custom_colors", &self.custom_colors)?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
94
color-module/src/schemes/material_design_2/color_set.rs
Normal file
94
color-module/src/schemes/material_design_2/color_set.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{convert::map_hsl_to_srgb_hex, errors};
|
||||||
|
|
||||||
|
use super::swatch::M2Swatch;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct M2ColorSet {
|
||||||
|
pub root: String,
|
||||||
|
pub variant: String,
|
||||||
|
pub on: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl M2ColorSet {
|
||||||
|
pub fn from_swatch(
|
||||||
|
swatch: &M2Swatch,
|
||||||
|
neutral: &M2Swatch,
|
||||||
|
dark: bool,
|
||||||
|
required_wacg_ratio: Option<f32>,
|
||||||
|
) -> Result<Self, errors::ColorError> {
|
||||||
|
let root_color = if dark {
|
||||||
|
map_hsl_to_srgb_hex(&swatch.desaturated_key_tone())
|
||||||
|
} else {
|
||||||
|
map_hsl_to_srgb_hex(&swatch.key_tone())
|
||||||
|
};
|
||||||
|
if dark {
|
||||||
|
Ok(Self {
|
||||||
|
variant: map_hsl_to_srgb_hex(&swatch.desaturated_tone(&swatch.key_index - 2)),
|
||||||
|
on: match required_wacg_ratio {
|
||||||
|
Some(ratio) => map_hsl_to_srgb_hex(
|
||||||
|
&neutral.desaturated_wacg_min_above(&root_color, ratio)?,
|
||||||
|
),
|
||||||
|
None => map_hsl_to_srgb_hex(&neutral.desaturated_wacg_max_tone(&root_color)?),
|
||||||
|
},
|
||||||
|
root: root_color,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
variant: map_hsl_to_srgb_hex(&swatch.tone(&swatch.key_index + 2)),
|
||||||
|
on: match required_wacg_ratio {
|
||||||
|
Some(ratio) => {
|
||||||
|
map_hsl_to_srgb_hex(&neutral.wacg_min_above(&root_color, ratio)?)
|
||||||
|
}
|
||||||
|
None => map_hsl_to_srgb_hex(&neutral.wacg_max_tone(&root_color)?),
|
||||||
|
},
|
||||||
|
root: root_color,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_css_variable(&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-{}-{}-variant: #{};",
|
||||||
|
prefix, name, self.variant
|
||||||
|
));
|
||||||
|
variable_lines.push(format!("--color-{}-on-{}: #{};", prefix, name, self.on));
|
||||||
|
|
||||||
|
variable_lines
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_scss_variable(&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-{}-{}-variant: #{};",
|
||||||
|
prefix, name, self.variant
|
||||||
|
));
|
||||||
|
variable_lines.push(format!("$color-{}-on-{}: #{};", prefix, name, self.on));
|
||||||
|
|
||||||
|
variable_lines
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_javascript_object(&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!("{}{}Variant: '#{}',", prefix, name, self.variant));
|
||||||
|
variable_lines.push(format!("{}On{}: '#{}',", prefix, name, self.on));
|
||||||
|
|
||||||
|
variable_lines
|
||||||
|
}
|
||||||
|
}
|
95
color-module/src/schemes/material_design_2/mod.rs
Normal file
95
color-module/src/schemes/material_design_2/mod.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use baseline::M2BaselineColors;
|
||||||
|
use palette::Hsl;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{convert::map_hsl_to_srgb_hex, errors};
|
||||||
|
|
||||||
|
use super::SchemeExport;
|
||||||
|
|
||||||
|
pub mod baseline;
|
||||||
|
pub mod color_set;
|
||||||
|
pub mod swatch;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct MaterialDesign2Scheme {
|
||||||
|
pub white: String,
|
||||||
|
pub black: String,
|
||||||
|
pub light: M2BaselineColors,
|
||||||
|
pub dark: M2BaselineColors,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaterialDesign2Scheme {
|
||||||
|
pub fn new(primary: &str, secondary: &str, error: &str) -> Result<Self, errors::ColorError> {
|
||||||
|
Ok(Self {
|
||||||
|
white: map_hsl_to_srgb_hex(&Hsl::new(0.0, 0.0, 1.0)),
|
||||||
|
black: map_hsl_to_srgb_hex(&Hsl::new(0.0, 0.0, 0.0)),
|
||||||
|
light: M2BaselineColors::new(primary, secondary, error, false)?,
|
||||||
|
dark: M2BaselineColors::new(primary, secondary, error, true)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_custom_color(&mut self, name: &str, color: &str) -> Result<(), errors::ColorError> {
|
||||||
|
self.light.add_custom_color(name, color)?;
|
||||||
|
self.dark.add_custom_color(name, color)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemeExport for MaterialDesign2Scheme {
|
||||||
|
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.to_css_variable());
|
||||||
|
css_variables.extend(self.dark.to_css_variable());
|
||||||
|
|
||||||
|
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.to_scss_variable());
|
||||||
|
scss_variables.extend(self.dark.to_scss_variable());
|
||||||
|
|
||||||
|
scss_variables.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_javascript_object(&self) -> String {
|
||||||
|
let mut js_object = Vec::new();
|
||||||
|
|
||||||
|
js_object.push("{".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
|
||||||
|
.to_javascript_object()
|
||||||
|
.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
|
||||||
|
.to_javascript_object()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| format!(" {}", s))
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
);
|
||||||
|
js_object.push(" }".to_string());
|
||||||
|
|
||||||
|
js_object.push("}".to_string());
|
||||||
|
|
||||||
|
js_object.join("\n")
|
||||||
|
}
|
||||||
|
}
|
306
color-module/src/schemes/material_design_2/swatch.rs
Normal file
306
color-module/src/schemes/material_design_2/swatch.rs
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
use std::{
|
||||||
|
ops::{Add, Sub},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use palette::{color_difference::Wcag21RelativeContrast, FromColor, Hsl, Srgb};
|
||||||
|
|
||||||
|
use crate::{convert::map_hsl_to_srgb, errors};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct M2Swatch {
|
||||||
|
_key_color: Hsl,
|
||||||
|
swatch: Vec<Hsl>,
|
||||||
|
desaturated_swatch: Vec<Hsl>,
|
||||||
|
pub key_index: SwatchIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum SwatchIndex {
|
||||||
|
SI900,
|
||||||
|
SI800,
|
||||||
|
SI700,
|
||||||
|
SI600,
|
||||||
|
SI500,
|
||||||
|
SI400,
|
||||||
|
SI300,
|
||||||
|
SI200,
|
||||||
|
SI100,
|
||||||
|
SI50,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwatchIndex {
|
||||||
|
pub fn to_usize(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
SwatchIndex::SI900 => 0,
|
||||||
|
SwatchIndex::SI800 => 1,
|
||||||
|
SwatchIndex::SI700 => 2,
|
||||||
|
SwatchIndex::SI600 => 3,
|
||||||
|
SwatchIndex::SI500 => 4,
|
||||||
|
SwatchIndex::SI400 => 5,
|
||||||
|
SwatchIndex::SI300 => 6,
|
||||||
|
SwatchIndex::SI200 => 7,
|
||||||
|
SwatchIndex::SI100 => 8,
|
||||||
|
SwatchIndex::SI50 => 9,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_usize(index: usize) -> Self {
|
||||||
|
match index {
|
||||||
|
0 => SwatchIndex::SI900,
|
||||||
|
1 => SwatchIndex::SI800,
|
||||||
|
2 => SwatchIndex::SI700,
|
||||||
|
3 => SwatchIndex::SI600,
|
||||||
|
4 => SwatchIndex::SI500,
|
||||||
|
5 => SwatchIndex::SI400,
|
||||||
|
6 => SwatchIndex::SI300,
|
||||||
|
7 => SwatchIndex::SI200,
|
||||||
|
8 => SwatchIndex::SI100,
|
||||||
|
9 => SwatchIndex::SI50,
|
||||||
|
_ => panic!("Invalid index"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<i16> for SwatchIndex {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: i16) -> Self::Output {
|
||||||
|
let index = (self.to_usize() as i16 + rhs).clamp(0, 9);
|
||||||
|
SwatchIndex::from_usize(index as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<i16> for &SwatchIndex {
|
||||||
|
type Output = SwatchIndex;
|
||||||
|
|
||||||
|
fn add(self, rhs: i16) -> Self::Output {
|
||||||
|
let index = (self.to_usize() as i16 + rhs).clamp(0, 9);
|
||||||
|
SwatchIndex::from_usize(index as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<i16> for SwatchIndex {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, rhs: i16) -> Self::Output {
|
||||||
|
let index = (self.to_usize() as i16 - rhs).clamp(0, 9);
|
||||||
|
SwatchIndex::from_usize(index as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<i16> for &SwatchIndex {
|
||||||
|
type Output = SwatchIndex;
|
||||||
|
|
||||||
|
fn sub(self, rhs: i16) -> Self::Output {
|
||||||
|
let index = (self.to_usize() as i16 - rhs).clamp(0, 9);
|
||||||
|
SwatchIndex::from_usize(index as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_key_color_place(value: f32, min_value: f32, max_value: f32) -> usize {
|
||||||
|
let interval = (max_value - min_value) / 9.0;
|
||||||
|
let values = (0..=9)
|
||||||
|
.map(|i| min_value + interval * i as f32)
|
||||||
|
.collect::<Vec<f32>>();
|
||||||
|
|
||||||
|
let mut closest_index = 0;
|
||||||
|
let mut min_diff = (value - values[closest_index]).abs();
|
||||||
|
|
||||||
|
for (i, v) in values.iter().enumerate() {
|
||||||
|
let diff = (value - v).abs();
|
||||||
|
if diff < min_diff {
|
||||||
|
min_diff = diff;
|
||||||
|
closest_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closest_index
|
||||||
|
}
|
||||||
|
|
||||||
|
impl M2Swatch {
|
||||||
|
pub fn default_neutral() -> Self {
|
||||||
|
let key_color = Hsl::new(0.0, 0.0, 0.0);
|
||||||
|
let mut swatch = Vec::new();
|
||||||
|
|
||||||
|
let interval: f32 = 1.0 / 9.0;
|
||||||
|
for i in 0..=9 {
|
||||||
|
swatch.push(Hsl::new(0.0, 0.0, interval * i as f32));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
_key_color: key_color,
|
||||||
|
desaturated_swatch: swatch.clone(),
|
||||||
|
swatch,
|
||||||
|
key_index: SwatchIndex::SI900,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_color(color: &str) -> Result<Self, errors::ColorError> {
|
||||||
|
let key_color = Hsl::from_color(
|
||||||
|
Srgb::from_str(color)
|
||||||
|
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))?
|
||||||
|
.into_format::<f32>(),
|
||||||
|
);
|
||||||
|
let mut swatch = Vec::new();
|
||||||
|
let mut desaturated_swatch = Vec::new();
|
||||||
|
|
||||||
|
let max_lightness = key_color.lightness.max(0.95);
|
||||||
|
let min_lightness = key_color.lightness.min(0.2);
|
||||||
|
let max_staturation = key_color.saturation.max(0.80);
|
||||||
|
let min_staturation = key_color.saturation.min(0.45);
|
||||||
|
|
||||||
|
let key_place = find_key_color_place(key_color.lightness, min_lightness, max_lightness);
|
||||||
|
|
||||||
|
if key_place > 0 {
|
||||||
|
let lightness_interval = (key_color.lightness - min_lightness) / key_place as f32;
|
||||||
|
let saturation_interval = (max_staturation - key_color.saturation) / key_place as f32;
|
||||||
|
for i in 0..key_place {
|
||||||
|
swatch.push(Hsl::new(
|
||||||
|
key_color.hue,
|
||||||
|
max_staturation - saturation_interval * i as f32,
|
||||||
|
min_lightness + lightness_interval * i as f32,
|
||||||
|
));
|
||||||
|
desaturated_swatch.push(Hsl::new(
|
||||||
|
key_color.hue,
|
||||||
|
(min_staturation + saturation_interval * i as f32) * 0.7_f32,
|
||||||
|
min_lightness + lightness_interval * i as f32,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swatch.push(key_color.clone());
|
||||||
|
|
||||||
|
if key_place < 9 {
|
||||||
|
let lightness_interval = (max_lightness - key_color.lightness) / (9 - key_place) as f32;
|
||||||
|
let saturation_interval =
|
||||||
|
(key_color.saturation - min_staturation) / (9 - key_place) as f32;
|
||||||
|
for i in 1..=9 - key_place {
|
||||||
|
swatch.push(Hsl::new(
|
||||||
|
key_color.hue,
|
||||||
|
key_color.saturation - saturation_interval * i as f32,
|
||||||
|
key_color.lightness + lightness_interval * i as f32,
|
||||||
|
));
|
||||||
|
desaturated_swatch.push(Hsl::new(
|
||||||
|
key_color.hue,
|
||||||
|
(min_staturation + saturation_interval * i as f32) * 0.7_f32,
|
||||||
|
key_color.lightness + lightness_interval * i as f32,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
_key_color: key_color,
|
||||||
|
swatch,
|
||||||
|
desaturated_swatch,
|
||||||
|
key_index: SwatchIndex::from_usize(key_place),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tone(&self, index: SwatchIndex) -> Hsl {
|
||||||
|
self.swatch[index.to_usize()].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_tone(&self) -> Hsl {
|
||||||
|
self.swatch[self.key_index.to_usize()].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn desaturated_tone(&self, index: SwatchIndex) -> Hsl {
|
||||||
|
self.desaturated_swatch[index.to_usize()].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn desaturated_key_tone(&self) -> Hsl {
|
||||||
|
self.desaturated_swatch[self.key_index.to_usize()].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wacg_max_tone(&self, reference_color: &str) -> Result<Hsl, errors::ColorError> {
|
||||||
|
let reference_color = Srgb::from_str(reference_color)
|
||||||
|
.map_err(|_| errors::ColorError::UnrecogniazedRGB(reference_color.to_string()))?
|
||||||
|
.into_format::<f32>();
|
||||||
|
|
||||||
|
let mut max_wacg_index = 0;
|
||||||
|
let mut max_wacg = 0.0;
|
||||||
|
|
||||||
|
for (i, tone) in self.swatch.iter().enumerate() {
|
||||||
|
let swatch_color = map_hsl_to_srgb(tone);
|
||||||
|
let wacg = swatch_color.relative_contrast(reference_color);
|
||||||
|
if wacg > max_wacg {
|
||||||
|
max_wacg = wacg;
|
||||||
|
max_wacg_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.swatch[max_wacg_index].clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn desaturated_wacg_max_tone(
|
||||||
|
&self,
|
||||||
|
reference_color: &str,
|
||||||
|
) -> Result<Hsl, errors::ColorError> {
|
||||||
|
let reference_color = Srgb::from_str(reference_color)
|
||||||
|
.map_err(|_| errors::ColorError::UnrecogniazedRGB(reference_color.to_string()))?
|
||||||
|
.into_format::<f32>();
|
||||||
|
|
||||||
|
let mut max_wacg_index = 0;
|
||||||
|
let mut max_wacg = 0.0;
|
||||||
|
|
||||||
|
for (i, tone) in self.desaturated_swatch.iter().enumerate() {
|
||||||
|
let swatch_color = map_hsl_to_srgb(tone);
|
||||||
|
let wacg = swatch_color.relative_contrast(reference_color);
|
||||||
|
if wacg > max_wacg {
|
||||||
|
max_wacg = wacg;
|
||||||
|
max_wacg_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.desaturated_swatch[max_wacg_index].clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wacg_min_above(
|
||||||
|
&self,
|
||||||
|
reference_color: &str,
|
||||||
|
ratio: f32,
|
||||||
|
) -> Result<Hsl, errors::ColorError> {
|
||||||
|
let reference_color = Srgb::from_str(reference_color)
|
||||||
|
.map_err(|_| errors::ColorError::UnrecogniazedRGB(reference_color.to_string()))?
|
||||||
|
.into_format::<f32>();
|
||||||
|
|
||||||
|
let mut min_wacg_index = 0;
|
||||||
|
let mut min_wacg = 21.0;
|
||||||
|
|
||||||
|
for (i, tone) in self.swatch.iter().enumerate() {
|
||||||
|
let swatch_color = map_hsl_to_srgb(tone);
|
||||||
|
let wacg = swatch_color.relative_contrast(reference_color);
|
||||||
|
if wacg < min_wacg && wacg > ratio {
|
||||||
|
min_wacg = wacg;
|
||||||
|
min_wacg_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.swatch[min_wacg_index].clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn desaturated_wacg_min_above(
|
||||||
|
&self,
|
||||||
|
reference_color: &str,
|
||||||
|
ratio: f32,
|
||||||
|
) -> Result<Hsl, errors::ColorError> {
|
||||||
|
let reference_color = Srgb::from_str(reference_color)
|
||||||
|
.map_err(|_| errors::ColorError::UnrecogniazedRGB(reference_color.to_string()))?
|
||||||
|
.into_format::<f32>();
|
||||||
|
|
||||||
|
let mut min_wacg_index = 0;
|
||||||
|
let mut min_wacg = 21.0;
|
||||||
|
|
||||||
|
for (i, tone) in self.desaturated_swatch.iter().enumerate() {
|
||||||
|
let swatch_color = map_hsl_to_srgb(tone);
|
||||||
|
let wacg = swatch_color.relative_contrast(reference_color);
|
||||||
|
if wacg < min_wacg && wacg > ratio {
|
||||||
|
min_wacg = wacg;
|
||||||
|
min_wacg_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.desaturated_swatch[min_wacg_index].clone())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use material_design_2::MaterialDesign2Scheme;
|
||||||
use material_design_3::MaterialDesign3Scheme;
|
use material_design_3::MaterialDesign3Scheme;
|
||||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||||
|
|
||||||
use crate::errors;
|
use crate::errors;
|
||||||
|
|
||||||
|
pub mod material_design_2;
|
||||||
pub mod material_design_3;
|
pub mod material_design_3;
|
||||||
|
|
||||||
pub trait SchemeExport {
|
pub trait SchemeExport {
|
||||||
|
@ -33,3 +35,25 @@ pub fn generate_material_design_3_scheme(
|
||||||
))
|
))
|
||||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn generate_material_design_2_scheme(
|
||||||
|
primary_color: &str,
|
||||||
|
secondary_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 = MaterialDesign2Scheme::new(primary_color, secondary_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