Compare commits

...

14 Commits

Author SHA1 Message Date
徐涛
b126858d8e 构建自动色板功能的基本页面结构。 2025-01-16 12:27:04 +08:00
徐涛
7a8bbaa826 修正多余的避免事件默认处理。 2025-01-16 05:56:47 +08:00
徐涛
49bed04748 修复Switch组件无法有效传出值的问题。 2025-01-15 13:54:56 +08:00
徐涛
8573a5d372 增加LabeledPicker的禁用选项。 2025-01-15 13:41:05 +08:00
徐涛
b32884a31c 尝试增加Picker的禁用样式。 2025-01-15 13:40:26 +08:00
徐涛
a56d473148 增加附加标签组件中水平排列的功能。 2025-01-15 13:27:08 +08:00
徐涛
8a55c73c83 自动色谱改名为自动色板。 2025-01-15 09:05:33 +08:00
徐涛
4bcd84c358 调整对比值布局。 2025-01-14 17:06:40 +08:00
徐涛
4ba51e018d 调整HCT的相对颜色分析计算。 2025-01-14 11:27:59 +08:00
徐涛
e90e88954a 更新WASM包。 2025-01-14 11:24:37 +08:00
徐涛
a980db505b 纠正颜色对比时相减的操作顺序。 2025-01-14 11:24:09 +08:00
徐涛
44023fed29 增加颜色对比的分析模式选择。 2025-01-14 11:18:04 +08:00
徐涛
bfd179c4aa 更新WASM包。 2025-01-14 10:52:12 +08:00
徐涛
b7a5c4a109 增加颜色对比中的相对值对比方法。 2025-01-14 10:51:31 +08:00
29 changed files with 597 additions and 74 deletions

View File

@ -16,9 +16,9 @@ impl ColorDifference for Cam16Jch<f32> {
type Difference = HctDiffference;
fn difference(&self, other: &Self) -> Self::Difference {
let hue = self.hue.into_positive_degrees() - other.hue.into_positive_degrees();
let chroma = self.chroma - other.chroma;
let lightness = self.lightness - other.lightness;
let hue = other.hue.into_positive_degrees() - self.hue.into_positive_degrees();
let chroma = other.chroma - self.chroma;
let lightness = other.lightness - self.lightness;
HctDiffference {
hue: Differ {
@ -35,4 +35,38 @@ impl ColorDifference for Cam16Jch<f32> {
},
}
}
fn difference_relative(&self, other: &Self) -> Self::Difference {
let hue = if self.hue.into_positive_degrees() == 0.0 {
0.0
} else {
(other.hue.into_positive_degrees() - self.hue.into_positive_degrees())
/ (360.0 - self.hue.into_positive_degrees())
};
let chroma = if self.chroma == 0.0 {
0.0
} else {
(other.chroma - self.chroma) / self.chroma
};
let lightness = if self.lightness == 0.0 {
0.0
} else {
(other.lightness - self.lightness) / (100.0 - self.lightness)
};
HctDiffference {
hue: Differ {
delta: other.hue.into_positive_degrees() - self.hue.into_positive_degrees(),
percent: hue,
},
chroma: Differ {
delta: other.chroma - self.chroma,
percent: chroma,
},
lightness: Differ {
delta: other.lightness - self.lightness,
percent: lightness,
},
}
}
}

View File

@ -16,9 +16,9 @@ impl ColorDifference for Hsl {
type Difference = HSLDifference;
fn difference(&self, other: &Self) -> Self::Difference {
let hue = self.hue.into_positive_degrees() - other.hue.into_positive_degrees();
let saturation = self.saturation - other.saturation;
let lightness = self.lightness - other.lightness;
let hue = other.hue.into_positive_degrees() - self.hue.into_positive_degrees();
let saturation = other.saturation - self.saturation;
let lightness = other.lightness - self.lightness;
HSLDifference {
hue: Differ {
@ -35,4 +35,38 @@ impl ColorDifference for Hsl {
},
}
}
fn difference_relative(&self, other: &Self) -> Self::Difference {
let hue = if self.hue.into_positive_degrees() == 0.0 {
0.0
} else {
(other.hue.into_positive_degrees() - self.hue.into_positive_degrees())
/ (1.0 - self.hue.into_positive_degrees())
};
let saturation = if self.saturation == 0.0 {
0.0
} else {
(other.saturation - self.saturation) / (1.0 - self.saturation)
};
let lightness = if self.lightness == 0.0 {
0.0
} else {
(other.lightness - self.lightness) / (1.0 - self.lightness)
};
HSLDifference {
hue: Differ {
delta: other.hue.into_positive_degrees() - self.hue.into_positive_degrees(),
percent: hue,
},
saturation: Differ {
delta: other.saturation - self.saturation,
percent: saturation,
},
lightness: Differ {
delta: other.lightness - self.lightness,
percent: lightness,
},
}
}
}

View File

@ -17,4 +17,5 @@ pub trait ColorDifference {
type Difference;
fn difference(&self, other: &Self) -> Self::Difference;
fn difference_relative(&self, other: &Self) -> Self::Difference;
}

View File

@ -16,9 +16,9 @@ impl ColorDifference for Oklch {
type Difference = OklchDifference;
fn difference(&self, other: &Self) -> Self::Difference {
let hue = self.hue.into_positive_degrees() - other.hue.into_positive_degrees();
let chroma = self.chroma - other.chroma;
let lightness = self.l - other.l;
let hue = other.hue.into_positive_degrees() - self.hue.into_positive_degrees();
let chroma = other.chroma - self.chroma;
let lightness = other.l - self.l;
OklchDifference {
hue: Differ {
@ -35,4 +35,38 @@ impl ColorDifference for Oklch {
},
}
}
fn difference_relative(&self, other: &Self) -> Self::Difference {
let hue = if self.hue.into_positive_degrees() == 0.0 {
0.0
} else {
(other.hue.into_positive_degrees() - self.hue.into_positive_degrees())
/ (1.0 - self.hue.into_positive_degrees())
};
let chroma = if self.chroma == 0.0 {
0.0
} else {
(other.chroma - self.chroma) / self.chroma
};
let lightness = if self.l == 0.0 {
0.0
} else {
(other.l - self.l) / (1.0 - self.l)
};
OklchDifference {
hue: Differ {
delta: other.hue.into_positive_degrees() - self.hue.into_positive_degrees(),
percent: hue,
},
chroma: Differ {
delta: other.chroma - self.chroma,
percent: chroma,
},
lightness: Differ {
delta: other.l - self.l,
percent: lightness,
},
}
}
}

View File

@ -16,9 +16,9 @@ impl ColorDifference for Srgb {
type Difference = RGBDifference;
fn difference(&self, other: &Self) -> Self::Difference {
let r = self.red - other.red;
let g = self.green - other.green;
let b = self.blue - other.blue;
let r = other.red - self.red;
let g = other.green - self.green;
let b = other.blue - self.blue;
RGBDifference {
r: Differ {
@ -35,4 +35,37 @@ impl ColorDifference for Srgb {
},
}
}
fn difference_relative(&self, other: &Self) -> Self::Difference {
let r = if self.red == 0.0 {
0.0
} else {
(other.red - self.red) / (1.0 - self.red)
};
let g = if self.green == 0.0 {
0.0
} else {
(other.green - self.green) / (1.0 - self.green)
};
let b = if self.blue == 0.0 {
0.0
} else {
(other.blue - self.blue) / (1.0 - self.blue)
};
RGBDifference {
r: Differ {
delta: other.red - self.red,
percent: r,
},
g: Differ {
delta: other.green - self.green,
percent: g,
},
b: Differ {
delta: other.blue - self.blue,
percent: b,
},
}
}
}

View File

@ -440,6 +440,20 @@ pub fn differ_in_rgb(
Ok(srgb.difference(&other_srgb))
}
#[wasm_bindgen]
pub fn relative_differ_in_rgb(
color: &str,
other: &str,
) -> Result<color_differ::rgb::RGBDifference, errors::ColorError> {
let srgb = Srgb::from_str(color)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))?
.into_format::<f32>();
let other_srgb = Srgb::from_str(other)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))?
.into_format::<f32>();
Ok(srgb.difference_relative(&other_srgb))
}
#[wasm_bindgen]
pub fn differ_in_hsl(
color: &str,
@ -458,6 +472,24 @@ pub fn differ_in_hsl(
Ok(hsl.difference(&other_hsl))
}
#[wasm_bindgen]
pub fn relative_differ_in_hsl(
color: &str,
other: &str,
) -> Result<color_differ::hsl::HSLDifference, errors::ColorError> {
let hsl = Hsl::from_color(
Srgb::from_str(color)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))?
.into_format::<f32>(),
);
let other_hsl = Hsl::from_color(
Srgb::from_str(other)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))?
.into_format::<f32>(),
);
Ok(hsl.difference_relative(&other_hsl))
}
#[wasm_bindgen]
pub fn differ_in_hct(
color: &str,
@ -480,6 +512,28 @@ pub fn differ_in_hct(
Ok(hct.difference(&other_hct))
}
#[wasm_bindgen]
pub fn relative_differ_in_hct(
color: &str,
other: &str,
) -> Result<color_differ::cam16jch::HctDiffference, errors::ColorError> {
let hct = Cam16Jch::from_xyz(
Srgb::from_str(color)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))?
.into_format::<f32>()
.into_color(),
Parameters::default_static_wp(40.0),
);
let other_hct = Cam16Jch::from_xyz(
Srgb::from_str(other)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))?
.into_format::<f32>()
.into_color(),
Parameters::default_static_wp(40.0),
);
Ok(hct.difference_relative(&other_hct))
}
#[wasm_bindgen]
pub fn differ_in_oklch(
color: &str,
@ -498,6 +552,24 @@ pub fn differ_in_oklch(
Ok(oklch.difference(&other_oklch))
}
#[wasm_bindgen]
pub fn relative_differ_in_oklch(
color: &str,
other: &str,
) -> Result<color_differ::oklch::OklchDifference, errors::ColorError> {
let oklch = Oklch::from_color(
Srgb::from_str(color)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.to_string()))?
.into_format::<f32>(),
);
let other_oklch = Oklch::from_color(
Srgb::from_str(other)
.map_err(|_| errors::ColorError::UnrecogniazedRGB(other.to_string()))?
.into_format::<f32>(),
);
Ok(oklch.difference_relative(&other_oklch))
}
#[wasm_bindgen]
pub fn tint_scale(
basic_color: &str,

View File

@ -10,6 +10,7 @@ import { LightenDarken } from './pages/LightenDarken';
import { MainLayout } from './pages/MainLayout';
import { Mixer } from './pages/Mixer';
import { NewScheme } from './pages/NewScheme';
import { AutomaticPalette } from './pages/Palette';
import { SchemeDetail } from './pages/SchemeDetail';
import { SchemeNotFound } from './pages/SchemeNotFound';
import { Schemes } from './pages/Schemes';
@ -39,6 +40,7 @@ const routes = createBrowserRouter([
{ path: 'tints-shades', element: <TintsShades /> },
{ path: 'lighten-darken', element: <LightenDarken /> },
{ path: 'mixer', element: <Mixer /> },
{ path: 'palette', element: <AutomaticPalette /> },
{ path: 'wacg', element: <WACGCheck /> },
{ path: 'compare', element: <ColorCompare /> },
{

View File

@ -31,9 +31,13 @@ export function tonal_darken_series(color: string, expand_amount: number, step:
export function color_categories(): any;
export function search_color_cards(tag: string, category?: string): any;
export function differ_in_rgb(color: string, other: string): RGBDifference;
export function relative_differ_in_rgb(color: string, other: string): RGBDifference;
export function differ_in_hsl(color: string, other: string): HSLDifference;
export function relative_differ_in_hsl(color: string, other: string): HSLDifference;
export function differ_in_hct(color: string, other: string): HctDiffference;
export function relative_differ_in_hct(color: string, other: string): HctDiffference;
export function differ_in_oklch(color: string, other: string): OklchDifference;
export function relative_differ_in_oklch(color: string, other: string): OklchDifference;
export function tint_scale(basic_color: string, mixed_color: string): MixReversing;
export function shade_scale(basic_color: string, mixed_color: string): MixReversing;
export class Differ {
@ -114,11 +118,20 @@ export interface InitOutput {
readonly color_categories: () => [number, number, number];
readonly search_color_cards: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly relative_differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly relative_differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly relative_differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly relative_differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly tint_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly shade_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly __wbg_differ_free: (a: number, b: number) => void;
readonly __wbg_get_differ_delta: (a: number) => number;
readonly __wbg_set_differ_delta: (a: number, b: number) => void;
readonly __wbg_get_differ_percent: (a: number) => number;
readonly __wbg_set_differ_percent: (a: number, b: number) => void;
readonly __wbg_mixreversing_free: (a: number, b: number) => void;
readonly __wbg_get_mixreversing_r_factor: (a: number) => number;
readonly __wbg_set_mixreversing_r_factor: (a: number, b: number) => void;
@ -128,18 +141,6 @@ export interface InitOutput {
readonly __wbg_set_mixreversing_b_factor: (a: number, b: number) => void;
readonly __wbg_get_mixreversing_average: (a: number) => number;
readonly __wbg_set_mixreversing_average: (a: number, b: number) => void;
readonly __wbg_rgbdifference_free: (a: number, b: number) => void;
readonly __wbg_get_rgbdifference_r: (a: number) => number;
readonly __wbg_set_rgbdifference_r: (a: number, b: number) => void;
readonly __wbg_get_rgbdifference_g: (a: number) => number;
readonly __wbg_set_rgbdifference_g: (a: number, b: number) => void;
readonly __wbg_get_rgbdifference_b: (a: number) => number;
readonly __wbg_set_rgbdifference_b: (a: number, b: number) => void;
readonly __wbg_differ_free: (a: number, b: number) => void;
readonly __wbg_get_differ_delta: (a: number) => number;
readonly __wbg_set_differ_delta: (a: number, b: number) => void;
readonly __wbg_get_differ_percent: (a: number) => number;
readonly __wbg_set_differ_percent: (a: number, b: number) => void;
readonly __wbg_oklchdifference_free: (a: number, b: number) => void;
readonly __wbg_get_oklchdifference_hue: (a: number) => number;
readonly __wbg_set_oklchdifference_hue: (a: number, b: number) => void;
@ -147,13 +148,13 @@ export interface InitOutput {
readonly __wbg_set_oklchdifference_chroma: (a: number, b: number) => void;
readonly __wbg_get_oklchdifference_lightness: (a: number) => number;
readonly __wbg_set_oklchdifference_lightness: (a: number, b: number) => void;
readonly __wbg_hctdiffference_free: (a: number, b: number) => void;
readonly __wbg_get_hctdiffference_hue: (a: number) => number;
readonly __wbg_set_hctdiffference_hue: (a: number, b: number) => void;
readonly __wbg_get_hctdiffference_chroma: (a: number) => number;
readonly __wbg_set_hctdiffference_chroma: (a: number, b: number) => void;
readonly __wbg_get_hctdiffference_lightness: (a: number) => number;
readonly __wbg_set_hctdiffference_lightness: (a: number, b: number) => void;
readonly __wbg_rgbdifference_free: (a: number, b: number) => void;
readonly __wbg_get_rgbdifference_r: (a: number) => number;
readonly __wbg_set_rgbdifference_r: (a: number, b: number) => void;
readonly __wbg_get_rgbdifference_g: (a: number) => number;
readonly __wbg_set_rgbdifference_g: (a: number, b: number) => void;
readonly __wbg_get_rgbdifference_b: (a: number) => number;
readonly __wbg_set_rgbdifference_b: (a: number, b: number) => void;
readonly __wbg_hsldifference_free: (a: number, b: number) => void;
readonly __wbg_get_hsldifference_hue: (a: number) => number;
readonly __wbg_set_hsldifference_hue: (a: number, b: number) => void;
@ -161,6 +162,13 @@ export interface InitOutput {
readonly __wbg_set_hsldifference_saturation: (a: number, b: number) => void;
readonly __wbg_get_hsldifference_lightness: (a: number) => number;
readonly __wbg_set_hsldifference_lightness: (a: number, b: number) => void;
readonly __wbg_hctdiffference_free: (a: number, b: number) => void;
readonly __wbg_get_hctdiffference_hue: (a: number) => number;
readonly __wbg_set_hctdiffference_hue: (a: number, b: number) => void;
readonly __wbg_get_hctdiffference_chroma: (a: number) => number;
readonly __wbg_set_hctdiffference_chroma: (a: number, b: number) => void;
readonly __wbg_get_hctdiffference_lightness: (a: number) => number;
readonly __wbg_set_hctdiffference_lightness: (a: number, b: number) => void;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;

View File

@ -756,6 +756,23 @@ export function differ_in_rgb(color, other) {
return RGBDifference.__wrap(ret[0]);
}
/**
* @param {string} color
* @param {string} other
* @returns {RGBDifference}
*/
export function relative_differ_in_rgb(color, other) {
const ptr0 = passStringToWasm0(color, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(other, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.relative_differ_in_rgb(ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return RGBDifference.__wrap(ret[0]);
}
/**
* @param {string} color
* @param {string} other
@ -773,6 +790,23 @@ export function differ_in_hsl(color, other) {
return HSLDifference.__wrap(ret[0]);
}
/**
* @param {string} color
* @param {string} other
* @returns {HSLDifference}
*/
export function relative_differ_in_hsl(color, other) {
const ptr0 = passStringToWasm0(color, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(other, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.relative_differ_in_hsl(ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return HSLDifference.__wrap(ret[0]);
}
/**
* @param {string} color
* @param {string} other
@ -790,6 +824,23 @@ export function differ_in_hct(color, other) {
return HctDiffference.__wrap(ret[0]);
}
/**
* @param {string} color
* @param {string} other
* @returns {HctDiffference}
*/
export function relative_differ_in_hct(color, other) {
const ptr0 = passStringToWasm0(color, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(other, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.relative_differ_in_hct(ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return HctDiffference.__wrap(ret[0]);
}
/**
* @param {string} color
* @param {string} other
@ -807,6 +858,23 @@ export function differ_in_oklch(color, other) {
return OklchDifference.__wrap(ret[0]);
}
/**
* @param {string} color
* @param {string} other
* @returns {OklchDifference}
*/
export function relative_differ_in_oklch(color, other) {
const ptr0 = passStringToWasm0(color, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(other, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.relative_differ_in_oklch(ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return OklchDifference.__wrap(ret[0]);
}
/**
* @param {string} basic_color
* @param {string} mixed_color

View File

@ -32,11 +32,20 @@ export const tonal_darken_series: (a: number, b: number, c: number, d: number) =
export const color_categories: () => [number, number, number];
export const search_color_cards: (a: number, b: number, c: number, d: number) => [number, number, number];
export const differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
export const relative_differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
export const differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
export const relative_differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
export const differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
export const relative_differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
export const differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
export const relative_differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
export const tint_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
export const shade_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
export const __wbg_differ_free: (a: number, b: number) => void;
export const __wbg_get_differ_delta: (a: number) => number;
export const __wbg_set_differ_delta: (a: number, b: number) => void;
export const __wbg_get_differ_percent: (a: number) => number;
export const __wbg_set_differ_percent: (a: number, b: number) => void;
export const __wbg_mixreversing_free: (a: number, b: number) => void;
export const __wbg_get_mixreversing_r_factor: (a: number) => number;
export const __wbg_set_mixreversing_r_factor: (a: number, b: number) => void;
@ -46,18 +55,6 @@ export const __wbg_get_mixreversing_b_factor: (a: number) => number;
export const __wbg_set_mixreversing_b_factor: (a: number, b: number) => void;
export const __wbg_get_mixreversing_average: (a: number) => number;
export const __wbg_set_mixreversing_average: (a: number, b: number) => void;
export const __wbg_rgbdifference_free: (a: number, b: number) => void;
export const __wbg_get_rgbdifference_r: (a: number) => number;
export const __wbg_set_rgbdifference_r: (a: number, b: number) => void;
export const __wbg_get_rgbdifference_g: (a: number) => number;
export const __wbg_set_rgbdifference_g: (a: number, b: number) => void;
export const __wbg_get_rgbdifference_b: (a: number) => number;
export const __wbg_set_rgbdifference_b: (a: number, b: number) => void;
export const __wbg_differ_free: (a: number, b: number) => void;
export const __wbg_get_differ_delta: (a: number) => number;
export const __wbg_set_differ_delta: (a: number, b: number) => void;
export const __wbg_get_differ_percent: (a: number) => number;
export const __wbg_set_differ_percent: (a: number, b: number) => void;
export const __wbg_oklchdifference_free: (a: number, b: number) => void;
export const __wbg_get_oklchdifference_hue: (a: number) => number;
export const __wbg_set_oklchdifference_hue: (a: number, b: number) => void;
@ -65,13 +62,13 @@ export const __wbg_get_oklchdifference_chroma: (a: number) => number;
export const __wbg_set_oklchdifference_chroma: (a: number, b: number) => void;
export const __wbg_get_oklchdifference_lightness: (a: number) => number;
export const __wbg_set_oklchdifference_lightness: (a: number, b: number) => void;
export const __wbg_hctdiffference_free: (a: number, b: number) => void;
export const __wbg_get_hctdiffference_hue: (a: number) => number;
export const __wbg_set_hctdiffference_hue: (a: number, b: number) => void;
export const __wbg_get_hctdiffference_chroma: (a: number) => number;
export const __wbg_set_hctdiffference_chroma: (a: number, b: number) => void;
export const __wbg_get_hctdiffference_lightness: (a: number) => number;
export const __wbg_set_hctdiffference_lightness: (a: number, b: number) => void;
export const __wbg_rgbdifference_free: (a: number, b: number) => void;
export const __wbg_get_rgbdifference_r: (a: number) => number;
export const __wbg_set_rgbdifference_r: (a: number, b: number) => void;
export const __wbg_get_rgbdifference_g: (a: number) => number;
export const __wbg_set_rgbdifference_g: (a: number, b: number) => void;
export const __wbg_get_rgbdifference_b: (a: number) => number;
export const __wbg_set_rgbdifference_b: (a: number, b: number) => void;
export const __wbg_hsldifference_free: (a: number, b: number) => void;
export const __wbg_get_hsldifference_hue: (a: number) => number;
export const __wbg_set_hsldifference_hue: (a: number, b: number) => void;
@ -79,6 +76,13 @@ export const __wbg_get_hsldifference_saturation: (a: number) => number;
export const __wbg_set_hsldifference_saturation: (a: number, b: number) => void;
export const __wbg_get_hsldifference_lightness: (a: number) => number;
export const __wbg_set_hsldifference_lightness: (a: number, b: number) => void;
export const __wbg_hctdiffference_free: (a: number, b: number) => void;
export const __wbg_get_hctdiffference_hue: (a: number) => number;
export const __wbg_set_hctdiffference_hue: (a: number, b: number) => void;
export const __wbg_get_hctdiffference_chroma: (a: number) => number;
export const __wbg_set_hctdiffference_chroma: (a: number, b: number) => void;
export const __wbg_get_hctdiffference_lightness: (a: number) => number;
export const __wbg_set_hctdiffference_lightness: (a: number, b: number) => void;
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_export_2: WebAssembly.Table;

View File

@ -384,6 +384,23 @@
background: transparent;
border-radius: var(--border-radius-xxs);
}
&[disabled] {
cursor: not-allowed;
&::-webkit-slider-thumb {
background: oklch(from var(--color-primary-disabled) l c h / 70%);
cursor: not-allowed;
}
&::-moz-range-thumb {
background: oklch(from var(--color-primary-disabled) l c h / 70%);
cursor: not-allowed;
}
&::-webkit-slider-runnable-track {
cursor: not-allowed;
}
&::-moz-range-track {
cursor: not-allowed;
}
}
}
/* Badge */

View File

@ -5,12 +5,19 @@
flex-direction: column;
align-items: stretch;
gap: var(--spacing-xs);
&.inline {
flex-direction: row;
gap: var(--spacing-s);
}
label {
width: 100%;
display: flex;
flex-direction: row;
align-items: baseline;
font-size: var(--font-size-xs);
.inline & {
width: fit-content;
}
}
}
}

View File

@ -1,13 +1,15 @@
import cx from 'clsx';
import styles from './Labeled.module.css';
type LabeledProps = {
label: string;
inline?: boolean;
children: React.ReactNode;
};
export function Labeled({ label, children }: LabeledProps) {
export function Labeled({ label, inline = false, children }: LabeledProps) {
return (
<div className={styles.labeled}>
<div className={cx(styles.labeled, inline && styles.inline)}>
<label>{label}</label>
{children}
</div>

View File

@ -11,6 +11,7 @@ type LabeledPickerProps = {
min?: number;
max?: number;
step?: number;
disabled?: boolean;
};
export function LabeledPicker({
@ -21,6 +22,7 @@ export function LabeledPicker({
min = 0,
max = 100,
step = 1,
disabled = false,
}: LabeledPickerProps) {
const [pickerValue, setPickerValue] = useState(value ?? min);
const handlePickerChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@ -52,6 +54,7 @@ export function LabeledPicker({
min={min}
max={max}
step={step}
disabled={disabled}
/>
</div>
);

View File

@ -152,7 +152,6 @@ export function ScrollArea({
const [xScrollNeeded, setXScrollNeeded] = useState(false);
const [yScrollNeeded, setYScrollNeeded] = useState(false);
const handleWheel = (evt: WheelEvent) => {
evt.preventDefault();
const container = scrollContainerRef?.current;
if (enableY && container) {
const delta = evt.deltaY;

View File

@ -16,7 +16,7 @@ export function Switch({ checked = false, disabled = false, onChange }: SwitchPr
setIsChecked((prev) => !prev);
onChange?.(!isChecked);
}
}, [onChange, disabled]);
}, [onChange, isChecked, disabled]);
useEffect(() => {
if (!isEqual(checked, isChecked)) {

View File

@ -8,6 +8,7 @@
margin-top: var(--spacing-s);
}
.element {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;

View File

@ -19,20 +19,28 @@ const defaultCompareResult: HctDiffference = {
},
};
export function HCTCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
export function HCTCompare({
basic = '000000',
compare = '000000',
mode = 'absolute',
}: CompareMethodProps) {
const { colorFn } = useColorFunction();
const differ = useMemo(() => {
if (!colorFn) {
return defaultCompareResult;
}
try {
const diff = colorFn.differ_in_hct(basic, compare);
const diffFn = {
absolute: colorFn.differ_in_hct,
relative: colorFn.relative_differ_in_hct,
};
const diff = diffFn[mode](basic, compare);
return diff;
} catch (e) {
console.error('[Compare in HCT]', e);
}
return defaultCompareResult;
}, [basic, compare]);
}, [basic, compare, mode]);
return (
<div>

View File

@ -19,20 +19,28 @@ const defaultCompareResult: HSLDifference = {
},
};
export function HSLCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
export function HSLCompare({
basic = '000000',
compare = '000000',
mode = 'absolute',
}: CompareMethodProps) {
const { colorFn } = useColorFunction();
const differ = useMemo(() => {
if (!colorFn) {
return defaultCompareResult;
}
try {
const diff = colorFn.differ_in_hsl(basic, compare);
const diffFn = {
absolute: colorFn.differ_in_hsl,
relative: colorFn.relative_differ_in_hsl,
};
const diff = diffFn[mode](basic, compare);
return diff;
} catch (e) {
console.error('[Compare in HSL]', e);
}
return defaultCompareResult;
}, [basic, compare]);
}, [basic, compare, mode]);
return (
<div>

View File

@ -19,20 +19,28 @@ const defaultCompareResult: OklchDifference = {
},
};
export function OklchCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
export function OklchCompare({
basic = '000000',
compare = '000000',
mode = 'absolute',
}: CompareMethodProps) {
const { colorFn } = useColorFunction();
const differ = useMemo(() => {
if (!colorFn) {
return defaultCompareResult;
}
try {
const diff = colorFn.differ_in_oklch(basic, compare);
const diffFn = {
absolute: colorFn.differ_in_oklch,
relative: colorFn.relative_differ_in_oklch,
};
const diff = diffFn[mode](basic, compare);
return diff;
} catch (e) {
console.error('[Compare in Oklch]', e);
}
return defaultCompareResult;
}, [basic, compare]);
}, [basic, compare, mode]);
return (
<div>

View File

@ -19,14 +19,22 @@ const defaultCompareResult: RGBDifference = {
},
};
export function RGBCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
export function RGBCompare({
basic = '000000',
compare = '000000',
mode = 'absolute',
}: CompareMethodProps) {
const { colorFn } = useColorFunction();
const differ = useMemo(() => {
if (!colorFn) {
return defaultCompareResult;
}
try {
const diff = colorFn?.differ_in_rgb(basic, compare);
const diffFn = {
absolute: colorFn.differ_in_rgb,
relative: colorFn.relative_differ_in_rgb,
};
const diff = diffFn[mode](basic, compare);
return {
r: {
delta: Math.round(diff.r.delta * 255),
@ -45,7 +53,7 @@ export function RGBCompare({ basic = '000000', compare = '000000' }: CompareMeth
console.error('[Compare in RGB]', e);
}
return defaultCompareResult;
}, [basic, compare]);
}, [basic, compare, mode]);
return (
<div>

View File

@ -46,7 +46,7 @@ export function ShadeScale({ basic = '000000', compare = '000000' }: CompareMeth
</div>
<div className={styles.elements}>
<div className={styles.element}>
<div className={cx(styles.element_name, styles.mean)}>F</div>
<div className={cx(styles.element_name, styles.mean)}>Factor</div>
<div className={styles.element_value}>{(mixFactors.average * 100).toFixed(2)}%</div>
</div>
</div>

View File

@ -46,7 +46,7 @@ export function TintScale({ basic = '000000', compare = '000000' }: CompareMetho
</div>
<div className={styles.elements}>
<div className={styles.element}>
<div className={cx(styles.element_name, styles.mean)}>F</div>
<div className={cx(styles.element_name, styles.mean)}>Factor</div>
<div className={styles.element_value}>{(mixFactors.average * 100).toFixed(2)}%</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
export type CompareMethodProps = {
basic?: string;
compare?: string;
mode?: 'absolute' | 'relative';
};

View File

@ -58,9 +58,9 @@ export function NavigationMenu() {
</li>
<li>
<NavLink
to="/chroma"
to="/palette"
className={({ isActive }) => cx(styles.nav_link, isActive && styles.active)}>
Chromatography
Auto Palette
</NavLink>
</li>
<li>

View File

@ -1,6 +1,8 @@
import cx from 'clsx';
import { useState } from 'react';
import { ColorPicker } from '../components/ColorPicker';
import { HSegmentedControl } from '../components/HSegmentedControl';
import { Labeled } from '../components/Labeled';
import { ScrollArea } from '../components/ScrollArea';
import { HCTCompare } from '../page-components/color-compare/HCTCompare';
import { HSLCompare } from '../page-components/color-compare/HSLCompare';
@ -13,6 +15,7 @@ import styles from './Compare.module.css';
export function ColorCompare() {
const [basicColor, setBasicColor] = useState('000000');
const [compareColor, setCompareColor] = useState('000000');
const [analysisMode, setMode] = useState<'relative' | 'absolute'>('relative');
return (
<div className={cx('workspace', styles.compare_workspace)}>
@ -34,10 +37,20 @@ export function ColorCompare() {
</div>
<div className={styles.compare_column}>
<h5>Compare Result</h5>
<RGBCompare basic={basicColor} compare={compareColor} />
<HSLCompare basic={basicColor} compare={compareColor} />
<HCTCompare basic={basicColor} compare={compareColor} />
<OklchCompare basic={basicColor} compare={compareColor} />
<Labeled label="Analysis Mode">
<HSegmentedControl
options={[
{ label: 'Absolute', value: 'absolute' },
{ label: 'Relative', value: 'relative' },
]}
value={analysisMode}
onChange={setMode}
/>
</Labeled>
<RGBCompare basic={basicColor} compare={compareColor} mode={analysisMode} />
<HSLCompare basic={basicColor} compare={compareColor} mode={analysisMode} />
<HCTCompare basic={basicColor} compare={compareColor} mode={analysisMode} />
<OklchCompare basic={basicColor} compare={compareColor} mode={analysisMode} />
<TintScale basic={basicColor} compare={compareColor} />
<ShadeScale basic={basicColor} compare={compareColor} />
</div>

View File

@ -0,0 +1,47 @@
@layer pages {
.palette_workspace {
flex-direction: column;
}
.palette_section {
width: 100%;
flex: 1;
display: flex;
flex-direction: row;
align-items: stretch;
gap: var(--spacing-m);
}
.function_side {
display: flex;
flex-direction: column;
gap: var(--spacing-m);
font-size: var(--font-size-s);
.mode_navigation {
display: flex;
flex-direction: column;
align-items: stretch;
gap: var(--spacing-s);
}
h5 {
padding-block: var(--spacing-m);
font-size: var(--font-size-m);
}
}
.palette_content {
flex: 1;
padding: 0 var(--spacing-m);
display: flex;
flex-direction: column;
gap: var(--spacing-s);
h5 {
padding-block: var(--spacing-m);
font-size: var(--font-size-m);
}
.color_value_mode {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--spacing-s);
font-size: var(--font-size-s);
}
}
}

111
src/pages/Palette.tsx Normal file
View File

@ -0,0 +1,111 @@
import cx from 'clsx';
import { useAtom } from 'jotai';
import { useEffect, useMemo, useState } from 'react';
import { ColorPicker } from '../components/ColorPicker';
import { HSegmentedControl } from '../components/HSegmentedControl';
import { Labeled } from '../components/Labeled';
import { LabeledPicker } from '../components/LabeledPicker';
import { ScrollArea } from '../components/ScrollArea';
import { Switch } from '../components/Switch';
import { currentPickedColor } from '../stores/colors';
import styles from './Palette.module.css';
export function AutomaticPalette() {
const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
const [useReferenceColor, setUseReferenceColor] = useState(false);
const [swatchAmount, setSwatchAmount] = useState(5);
const maxBias = useMemo(
() => (swatchAmount > 3 ? Math.floor(swatchAmount / 2) - 1 : 0),
[swatchAmount],
);
const [referenceBias, setReferenceBias] = useState(0);
const [minLightness, setMinLightness] = useState(10);
const [maxLightness, setMaxLightness] = useState(90);
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
useEffect(() => {
if (useReferenceColor) {
setReferenceBias(0);
}
}, [swatchAmount, useReferenceColor]);
return (
<div className={cx('workspace', styles.palette_workspace)}>
<header>
<h3>Automatic Palette</h3>
<p>
Automatically generate a color palette suitable for UI color matching according to the
given color algorithm.
</p>
</header>
<ScrollArea enableY>
<section className={styles.palette_section}>
<aside className={styles.function_side}>
<div>
<h5>Reference Color</h5>
<ColorPicker color={selectedColor} onSelect={(color) => setSelectedColor(color)} />
</div>
<div>
<h5>Palette Setting</h5>
<Labeled label="Use Reference Color" inline>
<Switch checked={useReferenceColor} onChange={setUseReferenceColor} />
</Labeled>
<LabeledPicker
title="Swatch Amount"
min={3}
max={15}
step={1}
value={swatchAmount}
onChange={(value) => setSwatchAmount(value)}
/>
<LabeledPicker
title="Reference Bias"
min={0}
max={maxBias}
step={1}
value={referenceBias}
onChange={setReferenceBias}
disabled={!useReferenceColor || maxBias < 2}
/>
<LabeledPicker
title="Min Lightness"
min={0}
max={100}
step={1}
value={minLightness}
onChange={setMinLightness}
unit="%"
/>
<LabeledPicker
title="Max Lightness"
min={0}
max={100}
step={1}
value={maxLightness}
onChange={setMaxLightness}
unit="%"
/>
</div>
</aside>
<div className={styles.palette_content}>
<h5>Generated Palette</h5>
<div className={styles.color_value_mode}>
<label>Copy color value in</label>
<HSegmentedControl
options={[
{ label: 'HEX', value: 'hex' },
{ label: 'RGB', value: 'rgb' },
{ label: 'HSL', value: 'hsl' },
{ label: 'LAB', value: 'lab' },
{ label: 'OKLCH', value: 'oklch' },
]}
valu={mode}
onChange={setMode}
/>
</div>
</div>
</section>
</ScrollArea>
</div>
);
}