将M3 Scheme的颜色生成从Cam16Jch重构为Lch。

This commit is contained in:
徐涛
2025-02-08 16:38:51 +08:00
parent c60aefaaff
commit 0369f238f2
10 changed files with 296 additions and 285 deletions

View File

@@ -2,9 +2,10 @@ use palette::{
cam16::{Cam16Jch, Parameters},
convert::FromColorUnclamped,
luma::Luma,
Hsl, IntoColor, IsWithinBounds, Lchuv, Oklab, Oklch, Srgb,
Hsl, IntoColor, IsWithinBounds, Lch, Lchuv, Oklab, Oklch, Srgb,
};
#[allow(dead_code)]
pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
let original_xyz = origin.into_xyz(Parameters::default_static_wp(40.0));
let mut new_srgb = Srgb::from_color_unclamped(original_xyz);
@@ -28,10 +29,36 @@ pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
}
}
#[allow(dead_code)]
pub fn map_cam16jch_to_srgb_hex(origin: &Cam16Jch<f32>) -> String {
format!("{:x}", map_cam16jch_to_srgb(origin).into_format::<u8>())
}
pub fn map_lch_to_srgb(origin: &Lch) -> Srgb {
let mut new_srgb: Srgb = (*origin).into_color();
if new_srgb.is_within_bounds() {
return new_srgb;
}
let mut c: f32 = origin.chroma;
let original_c = c;
let h = origin.hue;
let l = origin.l;
loop {
let new_lch = Lch::new(l, c, h);
new_srgb = new_lch.into_color();
c -= original_c / 1000.0;
if c > 0.0 && (new_srgb.is_within_bounds()) {
break new_srgb;
}
}
}
pub fn map_lch_to_srgb_hex(origin: &Lch) -> String {
format!("{:x}", map_lch_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;

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use serde::Serialize;
use crate::convert::map_cam16jch_to_srgb_hex;
use crate::convert::map_lch_to_srgb_hex;
use super::{color_set::M3ColorSet, surface::M3SurfaceSet, tonal_palette::TonalPalette};
@@ -62,10 +62,10 @@ impl M3BaselineColors {
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),
shadow: map_cam16jch_to_srgb_hex(&shadow),
outline: map_lch_to_srgb_hex(&outline),
outline_variant: map_lch_to_srgb_hex(&outline_variant),
scrim: map_lch_to_srgb_hex(&scrim),
shadow: map_lch_to_srgb_hex(&shadow),
customs: HashMap::new(),
dark_set,
}

View File

@@ -1,6 +1,6 @@
use serde::Serialize;
use crate::convert::map_cam16jch_to_srgb_hex;
use crate::convert::map_lch_to_srgb_hex;
use super::tonal_palette::TonalPalette;
@@ -22,7 +22,7 @@ impl M3ColorSet {
let root = palette.tone(40.0);
let on_root = palette.tone(100.0);
let container = palette.tone(90.0);
let on_container = palette.tone(10.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);
@@ -30,15 +30,15 @@ impl M3ColorSet {
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),
root: map_lch_to_srgb_hex(&root),
on_root: map_lch_to_srgb_hex(&on_root),
container: map_lch_to_srgb_hex(&container),
on_conatiner: map_lch_to_srgb_hex(&on_container),
fixed: map_lch_to_srgb_hex(&fixed),
fixed_dim: map_lch_to_srgb_hex(&fixed_dim),
on_fixed: map_lch_to_srgb_hex(&on_fixed),
fixed_variant: map_lch_to_srgb_hex(&fixed_variant),
inverse: map_lch_to_srgb_hex(&inverse),
}
}
@@ -54,15 +54,15 @@ impl M3ColorSet {
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),
root: map_lch_to_srgb_hex(&root),
on_root: map_lch_to_srgb_hex(&on_root),
container: map_lch_to_srgb_hex(&container),
on_conatiner: map_lch_to_srgb_hex(&on_container),
fixed: map_lch_to_srgb_hex(&fixed),
fixed_dim: map_lch_to_srgb_hex(&fixed_dim),
on_fixed: map_lch_to_srgb_hex(&on_fixed),
fixed_variant: map_lch_to_srgb_hex(&fixed_variant),
inverse: map_lch_to_srgb_hex(&inverse),
}
}

View File

@@ -1,12 +1,11 @@
use std::str::FromStr;
use baseline::M3BaselineColors;
use palette::cam16::{Cam16Jch, Parameters};
use palette::{IntoColor, Srgb};
use palette::{IntoColor, Lch, Srgb};
use serde::Serialize;
use tonal_palette::TonalPalette;
use crate::convert::map_cam16jch_to_srgb_hex;
use crate::convert::map_lch_to_srgb_hex;
use crate::errors;
use super::SchemeExport;
@@ -26,20 +25,14 @@ pub struct MaterialDesign3Scheme {
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: Lch = Srgb::from_str(source_color)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(source_color.to_string()))?
.into_format::<f32>()
.into_color();
let error: Lch = Srgb::from_str(error_color)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(error_color.to_string()))?
.into_format::<f32>()
.into_color();
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);
@@ -49,8 +42,8 @@ impl MaterialDesign3Scheme {
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)),
white: map_lch_to_srgb_hex(&Lch::new(100.0, 0.0, 0.0)),
black: map_lch_to_srgb_hex(&Lch::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),
})
@@ -61,13 +54,10 @@ impl MaterialDesign3Scheme {
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 custom_color: Lch = Srgb::from_str(&color)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.clone()))?
.into_format::<f32>()
.into_color();
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);

View File

@@ -1,6 +1,6 @@
use serde::Serialize;
use crate::convert::map_cam16jch_to_srgb_hex;
use crate::convert::map_lch_to_srgb_hex;
use super::tonal_palette::TonalPalette;
@@ -36,18 +36,18 @@ impl M3SurfaceSet {
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),
root: map_lch_to_srgb_hex(&root),
dim: map_lch_to_srgb_hex(&dim),
bright: map_lch_to_srgb_hex(&bright),
container: map_lch_to_srgb_hex(&container),
container_lowest: map_lch_to_srgb_hex(&container_lowest),
container_low: map_lch_to_srgb_hex(&container_low),
container_high: map_lch_to_srgb_hex(&container_high),
container_highest: map_lch_to_srgb_hex(&container_highest),
on_root: map_lch_to_srgb_hex(&on_root),
on_root_variant: map_lch_to_srgb_hex(&on_root_variant),
inverse: map_lch_to_srgb_hex(&inverse),
on_inverse: map_lch_to_srgb_hex(&on_inverse),
}
}
@@ -66,18 +66,18 @@ impl M3SurfaceSet {
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),
root: map_lch_to_srgb_hex(&root),
dim: map_lch_to_srgb_hex(&dim),
bright: map_lch_to_srgb_hex(&bright),
container: map_lch_to_srgb_hex(&container),
container_lowest: map_lch_to_srgb_hex(&container_lowest),
container_low: map_lch_to_srgb_hex(&container_low),
container_high: map_lch_to_srgb_hex(&container_high),
container_highest: map_lch_to_srgb_hex(&container_highest),
on_root: map_lch_to_srgb_hex(&on_root),
on_root_variant: map_lch_to_srgb_hex(&on_root_variant),
inverse: map_lch_to_srgb_hex(&inverse),
on_inverse: map_lch_to_srgb_hex(&on_inverse),
}
}

View File

@@ -1,15 +1,12 @@
use std::str::FromStr;
use palette::{
cam16::{Cam16Jch, Parameters},
IntoColor, Srgb,
};
use palette::{cam16::Cam16Jch, IntoColor, Lch, Srgb};
use crate::errors;
#[derive(Debug, Clone)]
pub struct TonalPalette {
pub key_color: Cam16Jch<f32>,
pub key_color: Lch,
}
#[inline]
@@ -30,7 +27,7 @@ fn find_max_chroma(cache: &mut Vec<(f32, f32)>, hue: f32, tone: f32) -> f32 {
chroma
}
fn from_hue_and_chroma(hue: f32, chroma: f32) -> Cam16Jch<f32> {
fn from_hue_and_chroma(hue: f32, chroma: f32) -> Lch {
let mut max_chroma_cache = Vec::new();
let hue = if hue >= 360.0 { hue - 360.0 } else { hue };
const PIVOT_TONE: f32 = 50.0;
@@ -51,7 +48,7 @@ fn from_hue_and_chroma(hue: f32, chroma: f32) -> Cam16Jch<f32> {
upper_tone = mid_tone;
} else {
if approximately_equal(lower_tone, mid_tone) {
return Cam16Jch::new(lower_tone, chroma, hue);
return Lch::new(lower_tone, chroma, hue);
}
lower_tone = mid_tone;
}
@@ -63,20 +60,17 @@ fn from_hue_and_chroma(hue: f32, chroma: f32) -> Cam16Jch<f32> {
}
}
}
Cam16Jch::new(lower_tone, chroma, hue)
Lch::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),
);
let key_color: Lch = Srgb::from_str(&value)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(value))?
.into_format::<f32>()
.into_color();
Ok(TonalPalette { key_color })
}
}
@@ -87,8 +81,8 @@ impl TonalPalette {
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);
pub fn tone(&self, tone: f32) -> Lch {
let toned_color = Lch::new(tone, self.key_color.chroma, self.key_color.hue);
toned_color
}