增加Material Design 2主题样式的生成和导出。
This commit is contained in:
		| @@ -1,7 +1,7 @@ | ||||
| use palette::{ | ||||
|     cam16::{Cam16Jch, Parameters}, | ||||
|     convert::FromColorUnclamped, | ||||
|     IsWithinBounds, Srgb, | ||||
|     Hsl, IsWithinBounds, 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 { | ||||
|     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 material_design_2::MaterialDesign2Scheme; | ||||
| use material_design_3::MaterialDesign3Scheme; | ||||
| use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; | ||||
|  | ||||
| use crate::errors; | ||||
|  | ||||
| pub mod material_design_2; | ||||
| pub mod material_design_3; | ||||
|  | ||||
| pub trait SchemeExport { | ||||
| @@ -33,3 +35,25 @@ pub fn generate_material_design_3_scheme( | ||||
|     )) | ||||
|     .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)?) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user