@trishchuk/coolors-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +39 -0
- package/.env +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +73 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
- package/.github/pull_request_template.md +97 -0
- package/.github/workflows/ci.yml +127 -0
- package/.github/workflows/deploy-docs.yml +56 -0
- package/.github/workflows/release.yml +99 -0
- package/.mcp.json +12 -0
- package/.prettierignore +1 -0
- package/CLAUDE.md +201 -0
- package/DOCUMENTATION.md +274 -0
- package/GEMINI.md +54 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/demo/content_based_color.png +0 -0
- package/demo/music-player.html +621 -0
- package/demo/podcast-player.html +903 -0
- package/dist/bin/coolors-mcp.d.ts +1 -0
- package/dist/bin/coolors-mcp.js +154 -0
- package/dist/bin/coolors-mcp.js.map +1 -0
- package/dist/bin/server.d.ts +1 -0
- package/dist/bin/server.js +3292 -0
- package/dist/bin/server.js.map +1 -0
- package/dist/chunk-IQ7NN26V.js +114 -0
- package/dist/chunk-IQ7NN26V.js.map +1 -0
- package/dist/chunk-P3ARRKLS.js +1214 -0
- package/dist/chunk-P3ARRKLS.js.map +1 -0
- package/dist/color/index.d.ts +716 -0
- package/dist/color/index.js +153 -0
- package/dist/color/index.js.map +1 -0
- package/dist/coolors-mcp.d.ts +136 -0
- package/dist/coolors-mcp.js +7 -0
- package/dist/coolors-mcp.js.map +1 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
- package/docs/.vitepress/cache/deps/_metadata.json +127 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +12683 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +9719 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape.js +30278 -0
- package/docs/.vitepress/cache/deps/cytoscape.js.map +7 -0
- package/docs/.vitepress/cache/deps/dayjs.js +285 -0
- package/docs/.vitepress/cache/deps/dayjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/debug.js +468 -0
- package/docs/.vitepress/cache/deps/debug.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/prismjs.js +1466 -0
- package/docs/.vitepress/cache/deps/prismjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +228 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +142 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +27 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +65 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +53 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +73 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1146 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1814 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +344 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/components/ClientGrid.vue +125 -0
- package/docs/.vitepress/components/CodeBlock.vue +231 -0
- package/docs/.vitepress/components/ConfigModal.vue +477 -0
- package/docs/.vitepress/components/DiagramModal.vue +528 -0
- package/docs/.vitepress/components/TroubleshootingModal.vue +472 -0
- package/docs/.vitepress/config.js +162 -0
- package/docs/.vitepress/theme/FundingLayout.vue +251 -0
- package/docs/.vitepress/theme/Layout.vue +134 -0
- package/docs/.vitepress/theme/components/AdBanner.vue +317 -0
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +78 -0
- package/docs/.vitepress/theme/components/FundingEffects.vue +322 -0
- package/docs/.vitepress/theme/components/FundingHero.vue +345 -0
- package/docs/.vitepress/theme/components/SupportSection.vue +511 -0
- package/docs/.vitepress/theme/custom-app.css +339 -0
- package/docs/.vitepress/theme/custom.css +699 -0
- package/docs/.vitepress/theme/index.js +25 -0
- package/docs/README.md +198 -0
- package/docs/concepts/accessibility.md +473 -0
- package/docs/concepts/color-spaces.md +222 -0
- package/docs/concepts/distance-metrics.md +384 -0
- package/docs/concepts/hct.md +261 -0
- package/docs/concepts/image-analysis.md +396 -0
- package/docs/concepts/material-design.md +306 -0
- package/docs/concepts/theme-matching.md +399 -0
- package/docs/examples/basic-colors.md +490 -0
- package/docs/examples/creating-themes.md +898 -0
- package/docs/examples/css-refactoring.md +824 -0
- package/docs/examples/image-extraction.md +882 -0
- package/docs/getting-started.md +366 -0
- package/docs/index.md +190 -0
- package/docs/installation.md +157 -0
- package/docs/tools/README.md +234 -0
- package/docs/tools/accessibility.md +614 -0
- package/docs/tools/color-operations.md +374 -0
- package/docs/tools/image-extraction.md +624 -0
- package/docs/tools/material-design.md +347 -0
- package/docs/tools/theme-matching.md +552 -0
- package/eslint.config.ts +14 -0
- package/examples/theme-matching.md +113 -0
- package/jsr.json +7 -0
- package/mcp-config.json +8 -0
- package/note.md +35 -0
- package/package.json +122 -0
- package/research_results.md +53 -0
- package/src/bin/coolors-mcp.ts +194 -0
- package/src/bin/server.ts +61 -0
- package/src/color/__tests__/conversions-argb.test.ts +198 -0
- package/src/color/__tests__/extract-colors.test.ts +360 -0
- package/src/color/__tests__/image-utils.test.ts +242 -0
- package/src/color/__tests__/reference-colors.test.ts +278 -0
- package/src/color/__tests__/round-trip.test.ts +197 -0
- package/src/color/conversions.test.ts +402 -0
- package/src/color/conversions.ts +393 -0
- package/src/color/dislike/__tests__/dislike-analyzer.test.ts +248 -0
- package/src/color/dislike/dislike-analyzer.ts +114 -0
- package/src/color/extract-colors.ts +228 -0
- package/src/color/hct/__tests__/hct-class.test.ts +232 -0
- package/src/color/hct/harmonization.ts +204 -0
- package/src/color/hct/hct-class.ts +109 -0
- package/src/color/hct/hct-solver.ts +168 -0
- package/src/color/hct/index.ts +39 -0
- package/src/color/hct/tonal-palette.ts +211 -0
- package/src/color/hct/types.ts +88 -0
- package/src/color/image-utils.ts +79 -0
- package/src/color/index.ts +87 -0
- package/src/color/material-theme.ts +157 -0
- package/src/color/metrics.test.ts +276 -0
- package/src/color/metrics.ts +281 -0
- package/src/color/quantize/__tests__/quantizer_celebi.test.ts +195 -0
- package/src/color/quantize/lab_point_provider.ts +55 -0
- package/src/color/quantize/point_provider.ts +27 -0
- package/src/color/quantize/quantizer_celebi.ts +51 -0
- package/src/color/quantize/quantizer_celebi_test.ts +71 -0
- package/src/color/quantize/quantizer_map.ts +47 -0
- package/src/color/quantize/quantizer_wsmeans.ts +232 -0
- package/src/color/quantize/quantizer_wu.ts +472 -0
- package/src/color/score/__tests__/score.test.ts +224 -0
- package/src/color/score/score.ts +175 -0
- package/src/color/types.ts +151 -0
- package/src/color/utils/color_utils.ts +292 -0
- package/src/color/utils/math_utils.ts +145 -0
- package/src/color/utils.test.ts +403 -0
- package/src/color/utils.ts +315 -0
- package/src/constants.ts +5 -0
- package/src/coolors-mcp.ts +37 -0
- package/src/examples/addition.ts +333 -0
- package/src/examples/color-demo.ts +125 -0
- package/src/examples/custom-logger.ts +201 -0
- package/src/examples/oauth-server.ts +113 -0
- package/src/examples/session-context.ts +269 -0
- package/src/session.ts +116 -0
- package/src/theme/__tests__/matcher.test.ts +180 -0
- package/src/theme/__tests__/parser.test.ts +148 -0
- package/src/theme/__tests__/refactor.test.ts +224 -0
- package/src/theme/index.ts +34 -0
- package/src/theme/matcher.ts +395 -0
- package/src/theme/parser.ts +392 -0
- package/src/theme/refactor.ts +360 -0
- package/src/theme/types.ts +152 -0
- package/src/tools/__tests__/gradient-generator.test.ts +206 -0
- package/src/tools/__tests__/palette-with-locks.test.ts +109 -0
- package/src/tools/color-conversion.tool.ts +54 -0
- package/src/tools/color-distance.tool.ts +41 -0
- package/src/tools/colors.ts +31 -0
- package/src/tools/contrast-checker.tool.ts +37 -0
- package/src/tools/dislike-analyzer.tool.ts +247 -0
- package/src/tools/gradient-generator.tool.ts +250 -0
- package/src/tools/image-extraction.tools.ts +289 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/material-theme.tools.ts +250 -0
- package/src/tools/palette-generator.tool.ts +135 -0
- package/src/tools/palette-with-locks.tool.ts +221 -0
- package/src/tools/registry.ts +142 -0
- package/src/tools/simple-tools.ts +37 -0
- package/src/tools/theme-matching.tools.ts +334 -0
- package/src/types.ts +182 -0
- package/src/utils.ts +22 -0
- package/tsconfig.json +8 -0
- package/vitest.config.js +15 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tonal Palette Generator
|
|
3
|
+
* Creates Material Design 3 tonal palettes from HCT colors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RGB } from "../types.js";
|
|
7
|
+
import type { HCT } from "./types.js";
|
|
8
|
+
|
|
9
|
+
import { hct, hctToRgb, rgbToHct } from "./hct-solver.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard Material 3 tone values
|
|
13
|
+
*/
|
|
14
|
+
export const MATERIAL_TONES = [
|
|
15
|
+
0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100,
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extended tone values for more granularity
|
|
20
|
+
*/
|
|
21
|
+
export const EXTENDED_TONES = [
|
|
22
|
+
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 92,
|
|
23
|
+
94, 96, 98, 99, 100,
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Material 3 Core Palette
|
|
28
|
+
* Contains all the tonal palettes needed for a complete theme
|
|
29
|
+
*/
|
|
30
|
+
export interface CorePalette {
|
|
31
|
+
/** Error color palette */
|
|
32
|
+
error: TonalPalette;
|
|
33
|
+
/** Neutral color palette (grays) */
|
|
34
|
+
neutral: TonalPalette;
|
|
35
|
+
/** Neutral variant palette (slightly tinted grays) */
|
|
36
|
+
neutralVariant: TonalPalette;
|
|
37
|
+
/** Primary color palette */
|
|
38
|
+
primary: TonalPalette;
|
|
39
|
+
/** Secondary color palette */
|
|
40
|
+
secondary: TonalPalette;
|
|
41
|
+
/** Tertiary color palette */
|
|
42
|
+
tertiary: TonalPalette;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A tonal palette - variations of a single color at different tones
|
|
47
|
+
*/
|
|
48
|
+
export class TonalPalette {
|
|
49
|
+
/**
|
|
50
|
+
* Get extended tones for more options
|
|
51
|
+
*/
|
|
52
|
+
get extendedTones(): Record<number, RGB> {
|
|
53
|
+
const tones: Record<number, RGB> = {};
|
|
54
|
+
for (const t of EXTENDED_TONES) {
|
|
55
|
+
tones[t] = this.tone(t);
|
|
56
|
+
}
|
|
57
|
+
return tones;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get all Material 3 standard tones
|
|
62
|
+
*/
|
|
63
|
+
get materialTones(): Record<number, RGB> {
|
|
64
|
+
const tones: Record<number, RGB> = {};
|
|
65
|
+
for (const t of MATERIAL_TONES) {
|
|
66
|
+
tones[t] = this.tone(t);
|
|
67
|
+
}
|
|
68
|
+
return tones;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private cache = new Map<number, RGB>();
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
public readonly hue: number,
|
|
75
|
+
public readonly chroma: number,
|
|
76
|
+
) {}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a tonal palette from an HCT color
|
|
80
|
+
*/
|
|
81
|
+
static fromHct(color: HCT): TonalPalette {
|
|
82
|
+
return new TonalPalette(color.h, color.c);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a tonal palette from an RGB color
|
|
87
|
+
*/
|
|
88
|
+
static fromRgb(rgb: RGB): TonalPalette {
|
|
89
|
+
const hctColor = rgbToHct(rgb);
|
|
90
|
+
return new TonalPalette(hctColor.h, hctColor.c);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get the color at a specific tone
|
|
95
|
+
*/
|
|
96
|
+
tone(tone: number): RGB {
|
|
97
|
+
// Check cache first
|
|
98
|
+
if (this.cache.has(tone)) {
|
|
99
|
+
return this.cache.get(tone)!;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Generate and cache
|
|
103
|
+
const color = hctToRgb(hct(this.hue, this.chroma, tone));
|
|
104
|
+
this.cache.set(tone, color);
|
|
105
|
+
return color;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generate an analogous palette (adjacent hues)
|
|
111
|
+
*/
|
|
112
|
+
export function analogousPalette(
|
|
113
|
+
source: RGB,
|
|
114
|
+
count: number = 5,
|
|
115
|
+
hueShift: number = 30,
|
|
116
|
+
): TonalPalette[] {
|
|
117
|
+
const sourceHct = rgbToHct(source);
|
|
118
|
+
const palettes: TonalPalette[] = [];
|
|
119
|
+
|
|
120
|
+
const startHue = sourceHct.h - hueShift * Math.floor(count / 2);
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < count; i++) {
|
|
123
|
+
const hue = (startHue + i * hueShift + 360) % 360;
|
|
124
|
+
palettes.push(new TonalPalette(hue, sourceHct.c));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return palettes;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generate a complementary palette
|
|
132
|
+
*/
|
|
133
|
+
export function complementaryPalette(
|
|
134
|
+
source: RGB,
|
|
135
|
+
): [TonalPalette, TonalPalette] {
|
|
136
|
+
const sourceHct = rgbToHct(source);
|
|
137
|
+
|
|
138
|
+
return [
|
|
139
|
+
new TonalPalette(sourceHct.h, sourceHct.c),
|
|
140
|
+
new TonalPalette((sourceHct.h + 180) % 360, sourceHct.c),
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate a complete Material 3 core palette from a source color
|
|
146
|
+
*/
|
|
147
|
+
export function corePaletteFromRgb(source: RGB): CorePalette {
|
|
148
|
+
const sourceHct = rgbToHct(source);
|
|
149
|
+
|
|
150
|
+
// Primary uses the source color
|
|
151
|
+
const primary = new TonalPalette(sourceHct.h, Math.max(48, sourceHct.c));
|
|
152
|
+
|
|
153
|
+
// Secondary is shifted in hue and reduced chroma
|
|
154
|
+
const secondary = new TonalPalette(
|
|
155
|
+
sourceHct.h,
|
|
156
|
+
Math.max(16, sourceHct.c / 3),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Tertiary shifts hue by 60 degrees
|
|
160
|
+
const tertiary = new TonalPalette(
|
|
161
|
+
(sourceHct.h + 60) % 360,
|
|
162
|
+
Math.max(24, sourceHct.c / 2),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Error is always red-ish
|
|
166
|
+
const error = new TonalPalette(25, 84);
|
|
167
|
+
|
|
168
|
+
// Neutral has very low chroma
|
|
169
|
+
const neutral = new TonalPalette(sourceHct.h, Math.min(4, sourceHct.c / 12));
|
|
170
|
+
|
|
171
|
+
// Neutral variant has slightly more chroma
|
|
172
|
+
const neutralVariant = new TonalPalette(
|
|
173
|
+
sourceHct.h,
|
|
174
|
+
Math.min(8, sourceHct.c / 6),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
error,
|
|
179
|
+
neutral,
|
|
180
|
+
neutralVariant,
|
|
181
|
+
primary,
|
|
182
|
+
secondary,
|
|
183
|
+
tertiary,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Generate a monochromatic palette (single hue, varying tones)
|
|
189
|
+
*/
|
|
190
|
+
export function monochromaticPalette(
|
|
191
|
+
source: RGB,
|
|
192
|
+
tones: readonly number[] = MATERIAL_TONES,
|
|
193
|
+
): RGB[] {
|
|
194
|
+
const palette = TonalPalette.fromRgb(source);
|
|
195
|
+
return tones.map((t) => palette.tone(t));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Generate a triadic palette (three colors evenly spaced)
|
|
200
|
+
*/
|
|
201
|
+
export function triadicPalette(
|
|
202
|
+
source: RGB,
|
|
203
|
+
): [TonalPalette, TonalPalette, TonalPalette] {
|
|
204
|
+
const sourceHct = rgbToHct(source);
|
|
205
|
+
|
|
206
|
+
return [
|
|
207
|
+
new TonalPalette(sourceHct.h, sourceHct.c),
|
|
208
|
+
new TonalPalette((sourceHct.h + 120) % 360, sourceHct.c),
|
|
209
|
+
new TonalPalette((sourceHct.h + 240) % 360, sourceHct.c),
|
|
210
|
+
];
|
|
211
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HCT (Hue, Chroma, Tone) color space types
|
|
3
|
+
* Based on Material Color Utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* CAM16 color appearance model
|
|
8
|
+
* Predicts color appearance under different viewing conditions
|
|
9
|
+
*/
|
|
10
|
+
export interface CAM16 {
|
|
11
|
+
/** Chroma composition */
|
|
12
|
+
astar: number;
|
|
13
|
+
/** Brightness composition */
|
|
14
|
+
bstar: number;
|
|
15
|
+
/** Chroma */
|
|
16
|
+
chroma: number;
|
|
17
|
+
/** Hue angle */
|
|
18
|
+
hue: number;
|
|
19
|
+
/** Lightness */
|
|
20
|
+
j: number;
|
|
21
|
+
/** Hue composition */
|
|
22
|
+
jstar: number;
|
|
23
|
+
/** Colorfulness */
|
|
24
|
+
m: number;
|
|
25
|
+
/** Brightness */
|
|
26
|
+
q: number;
|
|
27
|
+
/** Saturation */
|
|
28
|
+
s: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* HCT color representation
|
|
33
|
+
* A perceptually accurate color space that combines:
|
|
34
|
+
* - Hue from CAM16
|
|
35
|
+
* - Chroma from CAM16
|
|
36
|
+
* - Tone (Lightness) from L* (LAB)
|
|
37
|
+
*/
|
|
38
|
+
export interface HCT {
|
|
39
|
+
/** Chroma (colorfulness) [0, ~150] - actual max varies by hue and tone */
|
|
40
|
+
c: number;
|
|
41
|
+
/** Hue angle in degrees [0, 360) */
|
|
42
|
+
h: number;
|
|
43
|
+
/** Tone (lightness) [0, 100] */
|
|
44
|
+
t: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Viewing conditions for CAM16
|
|
49
|
+
* Describes the environment in which colors are viewed
|
|
50
|
+
*/
|
|
51
|
+
export interface ViewingConditions {
|
|
52
|
+
/** Background luminance ratio */
|
|
53
|
+
aw: number;
|
|
54
|
+
/** Base exponential nonlinearity */
|
|
55
|
+
c: number;
|
|
56
|
+
/** Degree of adaptation */
|
|
57
|
+
fl: number;
|
|
58
|
+
/** Luminance level adaptation factor */
|
|
59
|
+
flRoot: number;
|
|
60
|
+
/** Adapting luminance */
|
|
61
|
+
n: number;
|
|
62
|
+
/** Luminance level adaptation factor */
|
|
63
|
+
nbb: number;
|
|
64
|
+
/** Chromatic induction factor */
|
|
65
|
+
nc: number;
|
|
66
|
+
/** Chromatic induction factor */
|
|
67
|
+
ncb: number;
|
|
68
|
+
/** Achromatic response for white */
|
|
69
|
+
rgbD: [number, number, number];
|
|
70
|
+
/** Surround factor */
|
|
71
|
+
z: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Standard viewing conditions
|
|
76
|
+
*/
|
|
77
|
+
export const STANDARD_CONDITIONS: ViewingConditions = {
|
|
78
|
+
aw: 29.98,
|
|
79
|
+
c: 0.69,
|
|
80
|
+
fl: 0.388,
|
|
81
|
+
flRoot: 0.789,
|
|
82
|
+
n: 0.184,
|
|
83
|
+
nbb: 1.017,
|
|
84
|
+
nc: 1.0,
|
|
85
|
+
ncb: 1.017,
|
|
86
|
+
rgbD: [1.021, 0.986, 0.934],
|
|
87
|
+
z: 1.909,
|
|
88
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image processing utilities for color extraction
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as utils from "./utils/color_utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Filter out near-white and near-black pixels
|
|
9
|
+
* @param pixels - Array of ARGB pixels
|
|
10
|
+
* @returns Filtered array
|
|
11
|
+
*/
|
|
12
|
+
export function filterExtremeTones(pixels: number[]): number[] {
|
|
13
|
+
return pixels.filter((pixel) => {
|
|
14
|
+
const r = utils.redFromArgb(pixel);
|
|
15
|
+
const g = utils.greenFromArgb(pixel);
|
|
16
|
+
const b = utils.blueFromArgb(pixel);
|
|
17
|
+
|
|
18
|
+
// Calculate luminance (rough approximation)
|
|
19
|
+
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
20
|
+
|
|
21
|
+
// Filter out very dark (< 5%) and very light (> 95%) pixels
|
|
22
|
+
return luminance > 12.75 && luminance < 242.25;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert image data to ARGB pixel array
|
|
28
|
+
* @param imageData - Canvas ImageData or similar structure
|
|
29
|
+
* @returns Array of ARGB colors as 32-bit integers
|
|
30
|
+
*/
|
|
31
|
+
export function imageDataToPixels(imageData: {
|
|
32
|
+
data: number[] | Uint8ClampedArray;
|
|
33
|
+
height: number;
|
|
34
|
+
width: number;
|
|
35
|
+
}): number[] {
|
|
36
|
+
const pixels: number[] = [];
|
|
37
|
+
const data = imageData.data;
|
|
38
|
+
|
|
39
|
+
// Process RGBA data (4 bytes per pixel)
|
|
40
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
41
|
+
const r = data[i];
|
|
42
|
+
const g = data[i + 1];
|
|
43
|
+
const b = data[i + 2];
|
|
44
|
+
const a = data[i + 3];
|
|
45
|
+
|
|
46
|
+
// Skip fully transparent pixels
|
|
47
|
+
if (a < 255 * 0.01) continue;
|
|
48
|
+
|
|
49
|
+
// Convert to ARGB format
|
|
50
|
+
const argb = utils.argbFromRgb(r, g, b);
|
|
51
|
+
pixels.push(argb);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return pixels;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Sample pixels from image for faster processing
|
|
59
|
+
* @param pixels - Full array of pixels
|
|
60
|
+
* @param maxPixels - Maximum number of pixels to sample
|
|
61
|
+
* @returns Sampled array of pixels
|
|
62
|
+
*/
|
|
63
|
+
export function samplePixels(
|
|
64
|
+
pixels: number[],
|
|
65
|
+
maxPixels: number = 10000,
|
|
66
|
+
): number[] {
|
|
67
|
+
if (pixels.length <= maxPixels) {
|
|
68
|
+
return pixels;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const sampled: number[] = [];
|
|
72
|
+
const step = Math.ceil(pixels.length / maxPixels);
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < pixels.length; i += step) {
|
|
75
|
+
sampled.push(pixels[i]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return sampled;
|
|
79
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color utilities module
|
|
3
|
+
* Main exports for color manipulation, conversion, and distance calculations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Export all conversion functions
|
|
7
|
+
export {
|
|
8
|
+
argbToRgb,
|
|
9
|
+
hexToRgb,
|
|
10
|
+
hslToRgb,
|
|
11
|
+
hsvToRgb,
|
|
12
|
+
labToRgb,
|
|
13
|
+
labToXyz,
|
|
14
|
+
parseColor,
|
|
15
|
+
rgbToArgb,
|
|
16
|
+
rgbToHex,
|
|
17
|
+
rgbToHsl,
|
|
18
|
+
rgbToHsv,
|
|
19
|
+
rgbToLab,
|
|
20
|
+
rgbToXyz,
|
|
21
|
+
xyzToLab,
|
|
22
|
+
xyzToRgb,
|
|
23
|
+
} from "./conversions.js";
|
|
24
|
+
|
|
25
|
+
// Export dislike analyzer
|
|
26
|
+
export { DislikeAnalyzer } from "./dislike/dislike-analyzer.js";
|
|
27
|
+
|
|
28
|
+
// Export HCT functionality
|
|
29
|
+
export * from "./hct/index.js";
|
|
30
|
+
|
|
31
|
+
// Export all metric functions
|
|
32
|
+
export {
|
|
33
|
+
areColorsSimilar,
|
|
34
|
+
colorDistance,
|
|
35
|
+
deltaE2000,
|
|
36
|
+
deltaE76,
|
|
37
|
+
deltaE94,
|
|
38
|
+
euclideanDistance,
|
|
39
|
+
findMostDifferentColor,
|
|
40
|
+
findMostSimilarColor,
|
|
41
|
+
weightedRgbDistance,
|
|
42
|
+
} from "./metrics.js";
|
|
43
|
+
|
|
44
|
+
// Export all types
|
|
45
|
+
export type {
|
|
46
|
+
ColorDistanceOptions,
|
|
47
|
+
ColorInput,
|
|
48
|
+
HCT,
|
|
49
|
+
HEX,
|
|
50
|
+
HSL,
|
|
51
|
+
HSV,
|
|
52
|
+
LAB,
|
|
53
|
+
ParsedColor,
|
|
54
|
+
RGB,
|
|
55
|
+
XYZ,
|
|
56
|
+
} from "./types.js";
|
|
57
|
+
|
|
58
|
+
export { ColorConstants, ColorFormat } from "./types.js";
|
|
59
|
+
|
|
60
|
+
// Export all utility functions
|
|
61
|
+
export {
|
|
62
|
+
clampHsl,
|
|
63
|
+
clampHsv,
|
|
64
|
+
clampRgb,
|
|
65
|
+
darken,
|
|
66
|
+
desaturate,
|
|
67
|
+
formatColor,
|
|
68
|
+
formatHsl,
|
|
69
|
+
formatRgb,
|
|
70
|
+
getComplementary,
|
|
71
|
+
getContrastRatio,
|
|
72
|
+
getLuminance,
|
|
73
|
+
invertColor,
|
|
74
|
+
isDark,
|
|
75
|
+
isLight,
|
|
76
|
+
isValidHex,
|
|
77
|
+
isValidHsl,
|
|
78
|
+
isValidHsv,
|
|
79
|
+
isValidRgb,
|
|
80
|
+
lighten,
|
|
81
|
+
meetsContrastAA,
|
|
82
|
+
meetsContrastAAA,
|
|
83
|
+
mixColors,
|
|
84
|
+
randomColor,
|
|
85
|
+
saturate,
|
|
86
|
+
toGrayscale,
|
|
87
|
+
} from "./utils.js";
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Material Design Theme Generation
|
|
3
|
+
* Core functionality for Material Design 3 color theme creation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DislikeAnalyzer } from "./dislike/dislike-analyzer.js";
|
|
7
|
+
import { Hct } from "./hct/hct-class.js";
|
|
8
|
+
import {
|
|
9
|
+
corePaletteFromRgb,
|
|
10
|
+
parseColor,
|
|
11
|
+
rgbToArgb,
|
|
12
|
+
rgbToHex,
|
|
13
|
+
} from "./index.js";
|
|
14
|
+
|
|
15
|
+
export interface MaterialColorScheme {
|
|
16
|
+
background: string;
|
|
17
|
+
error: string;
|
|
18
|
+
errorContainer: string;
|
|
19
|
+
onBackground: string;
|
|
20
|
+
onError: string;
|
|
21
|
+
onErrorContainer: string;
|
|
22
|
+
onPrimary: string;
|
|
23
|
+
onPrimaryContainer: string;
|
|
24
|
+
onSecondary: string;
|
|
25
|
+
onSecondaryContainer: string;
|
|
26
|
+
onSurface: string;
|
|
27
|
+
onSurfaceVariant: string;
|
|
28
|
+
onTertiary: string;
|
|
29
|
+
onTertiaryContainer: string;
|
|
30
|
+
outline: string;
|
|
31
|
+
primary: string;
|
|
32
|
+
primaryContainer: string;
|
|
33
|
+
secondary: string;
|
|
34
|
+
secondaryContainer: string;
|
|
35
|
+
surface: string;
|
|
36
|
+
surfaceVariant: string;
|
|
37
|
+
tertiary: string;
|
|
38
|
+
tertiaryContainer: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface MaterialTheme {
|
|
42
|
+
customColors?: Record<string, unknown>;
|
|
43
|
+
schemes: {
|
|
44
|
+
dark: MaterialColorScheme;
|
|
45
|
+
light: MaterialColorScheme;
|
|
46
|
+
};
|
|
47
|
+
sourceColor: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface MaterialThemeOptions {
|
|
51
|
+
fixDislikedColors?: boolean;
|
|
52
|
+
includeCustomColors?: boolean;
|
|
53
|
+
isDark?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a complete Material Design 3 theme from a source color
|
|
58
|
+
*/
|
|
59
|
+
export function generateMaterialTheme(
|
|
60
|
+
sourceColor: string,
|
|
61
|
+
options: MaterialThemeOptions = {},
|
|
62
|
+
): MaterialTheme {
|
|
63
|
+
const { fixDislikedColors = true } = options;
|
|
64
|
+
|
|
65
|
+
let source = parseColor(sourceColor);
|
|
66
|
+
if (!source) {
|
|
67
|
+
throw new Error(`Invalid color format: ${sourceColor}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fix source color if it's disliked
|
|
71
|
+
if (fixDislikedColors) {
|
|
72
|
+
const sourceHct = Hct.fromInt(rgbToArgb(source));
|
|
73
|
+
if (DislikeAnalyzer.isDisliked(sourceHct)) {
|
|
74
|
+
const fixedHct = DislikeAnalyzer.fixIfDisliked(sourceHct);
|
|
75
|
+
const fixedArgb = fixedHct.toInt();
|
|
76
|
+
source = {
|
|
77
|
+
b: fixedArgb & 0xff,
|
|
78
|
+
g: (fixedArgb >> 8) & 0xff,
|
|
79
|
+
r: (fixedArgb >> 16) & 0xff,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const corePalette = corePaletteFromRgb(source);
|
|
85
|
+
|
|
86
|
+
// Generate light theme colors
|
|
87
|
+
const lightScheme: MaterialColorScheme = {
|
|
88
|
+
background: rgbToHex(corePalette.neutral.tone(99)),
|
|
89
|
+
error: rgbToHex(corePalette.error.tone(40)),
|
|
90
|
+
errorContainer: rgbToHex(corePalette.error.tone(90)),
|
|
91
|
+
onBackground: rgbToHex(corePalette.neutral.tone(10)),
|
|
92
|
+
|
|
93
|
+
onError: rgbToHex(corePalette.error.tone(100)),
|
|
94
|
+
onErrorContainer: rgbToHex(corePalette.error.tone(10)),
|
|
95
|
+
onPrimary: rgbToHex(corePalette.primary.tone(100)),
|
|
96
|
+
onPrimaryContainer: rgbToHex(corePalette.primary.tone(10)),
|
|
97
|
+
|
|
98
|
+
onSecondary: rgbToHex(corePalette.secondary.tone(100)),
|
|
99
|
+
onSecondaryContainer: rgbToHex(corePalette.secondary.tone(10)),
|
|
100
|
+
onSurface: rgbToHex(corePalette.neutral.tone(10)),
|
|
101
|
+
onSurfaceVariant: rgbToHex(corePalette.neutralVariant.tone(30)),
|
|
102
|
+
|
|
103
|
+
onTertiary: rgbToHex(corePalette.tertiary.tone(100)),
|
|
104
|
+
onTertiaryContainer: rgbToHex(corePalette.tertiary.tone(10)),
|
|
105
|
+
outline: rgbToHex(corePalette.neutralVariant.tone(50)),
|
|
106
|
+
primary: rgbToHex(corePalette.primary.tone(40)),
|
|
107
|
+
|
|
108
|
+
primaryContainer: rgbToHex(corePalette.primary.tone(90)),
|
|
109
|
+
secondary: rgbToHex(corePalette.secondary.tone(40)),
|
|
110
|
+
secondaryContainer: rgbToHex(corePalette.secondary.tone(90)),
|
|
111
|
+
surface: rgbToHex(corePalette.neutral.tone(99)),
|
|
112
|
+
|
|
113
|
+
surfaceVariant: rgbToHex(corePalette.neutralVariant.tone(90)),
|
|
114
|
+
tertiary: rgbToHex(corePalette.tertiary.tone(40)),
|
|
115
|
+
tertiaryContainer: rgbToHex(corePalette.tertiary.tone(90)),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Generate dark theme colors
|
|
119
|
+
const darkScheme: MaterialColorScheme = {
|
|
120
|
+
background: rgbToHex(corePalette.neutral.tone(10)),
|
|
121
|
+
error: rgbToHex(corePalette.error.tone(80)),
|
|
122
|
+
errorContainer: rgbToHex(corePalette.error.tone(30)),
|
|
123
|
+
onBackground: rgbToHex(corePalette.neutral.tone(90)),
|
|
124
|
+
|
|
125
|
+
onError: rgbToHex(corePalette.error.tone(20)),
|
|
126
|
+
onErrorContainer: rgbToHex(corePalette.error.tone(90)),
|
|
127
|
+
onPrimary: rgbToHex(corePalette.primary.tone(20)),
|
|
128
|
+
onPrimaryContainer: rgbToHex(corePalette.primary.tone(90)),
|
|
129
|
+
|
|
130
|
+
onSecondary: rgbToHex(corePalette.secondary.tone(20)),
|
|
131
|
+
onSecondaryContainer: rgbToHex(corePalette.secondary.tone(90)),
|
|
132
|
+
onSurface: rgbToHex(corePalette.neutral.tone(90)),
|
|
133
|
+
onSurfaceVariant: rgbToHex(corePalette.neutralVariant.tone(80)),
|
|
134
|
+
|
|
135
|
+
onTertiary: rgbToHex(corePalette.tertiary.tone(20)),
|
|
136
|
+
onTertiaryContainer: rgbToHex(corePalette.tertiary.tone(90)),
|
|
137
|
+
outline: rgbToHex(corePalette.neutralVariant.tone(60)),
|
|
138
|
+
primary: rgbToHex(corePalette.primary.tone(80)),
|
|
139
|
+
|
|
140
|
+
primaryContainer: rgbToHex(corePalette.primary.tone(30)),
|
|
141
|
+
secondary: rgbToHex(corePalette.secondary.tone(80)),
|
|
142
|
+
secondaryContainer: rgbToHex(corePalette.secondary.tone(30)),
|
|
143
|
+
surface: rgbToHex(corePalette.neutral.tone(10)),
|
|
144
|
+
|
|
145
|
+
surfaceVariant: rgbToHex(corePalette.neutralVariant.tone(30)),
|
|
146
|
+
tertiary: rgbToHex(corePalette.tertiary.tone(80)),
|
|
147
|
+
tertiaryContainer: rgbToHex(corePalette.tertiary.tone(30)),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
schemes: {
|
|
152
|
+
dark: darkScheme,
|
|
153
|
+
light: lightScheme,
|
|
154
|
+
},
|
|
155
|
+
sourceColor,
|
|
156
|
+
};
|
|
157
|
+
}
|