@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,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2021 Google LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Hct } from "../hct/index.js";
|
|
19
|
+
import * as math from "../utils/math_utils.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default options for ranking colors based on usage counts.
|
|
23
|
+
* desired: is the max count of the colors returned.
|
|
24
|
+
* fallbackColorARGB: Is the default color that should be used if no
|
|
25
|
+
* other colors are suitable.
|
|
26
|
+
* filter: controls if the resulting colors should be filtered to not include
|
|
27
|
+
* hues that are not used often enough, and colors that are effectively
|
|
28
|
+
* grayscale.
|
|
29
|
+
*/
|
|
30
|
+
declare interface ScoreOptions {
|
|
31
|
+
desired?: number;
|
|
32
|
+
fallbackColorARGB?: number;
|
|
33
|
+
filter?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SCORE_OPTION_DEFAULTS = {
|
|
37
|
+
desired: 4, // 4 colors matches what Android wallpaper picker.
|
|
38
|
+
fallbackColorARGB: 0xff4285f4, // Google Blue.
|
|
39
|
+
filter: true, // Avoid unsuitable colors.
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function compare(
|
|
43
|
+
a: { hct: Hct; score: number },
|
|
44
|
+
b: { hct: Hct; score: number },
|
|
45
|
+
): number {
|
|
46
|
+
if (a.score > b.score) {
|
|
47
|
+
return -1;
|
|
48
|
+
} else if (a.score < b.score) {
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Given a large set of colors, remove colors that are unsuitable for a UI
|
|
56
|
+
* theme, and rank the rest based on suitability.
|
|
57
|
+
*
|
|
58
|
+
* Enables use of a high cluster count for image quantization, thus ensuring
|
|
59
|
+
* colors aren't muddied, while curating the high cluster count to a much
|
|
60
|
+
* smaller number of appropriate choices.
|
|
61
|
+
*/
|
|
62
|
+
export class Score {
|
|
63
|
+
private static readonly CUTOFF_CHROMA = 5.0;
|
|
64
|
+
private static readonly CUTOFF_EXCITED_PROPORTION = 0.01;
|
|
65
|
+
private static readonly TARGET_CHROMA = 48.0; // A1 Chroma
|
|
66
|
+
private static readonly WEIGHT_CHROMA_ABOVE = 0.3;
|
|
67
|
+
private static readonly WEIGHT_CHROMA_BELOW = 0.1;
|
|
68
|
+
private static readonly WEIGHT_PROPORTION = 0.7;
|
|
69
|
+
|
|
70
|
+
private constructor() {}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Given a map with keys of colors and values of how often the color appears,
|
|
74
|
+
* rank the colors based on suitability for being used for a UI theme.
|
|
75
|
+
*
|
|
76
|
+
* @param colorsToPopulation map with keys of colors and values of how often
|
|
77
|
+
* the color appears, usually from a source image.
|
|
78
|
+
* @param {ScoreOptions} options optional parameters.
|
|
79
|
+
* @return Colors sorted by suitability for a UI theme. The most suitable
|
|
80
|
+
* color is the first item, the least suitable is the last. There will
|
|
81
|
+
* always be at least one color returned. If all the input colors
|
|
82
|
+
* were not suitable for a theme, a default fallback color will be
|
|
83
|
+
* provided, Google Blue.
|
|
84
|
+
*/
|
|
85
|
+
static score(
|
|
86
|
+
colorsToPopulation: Map<number, number>,
|
|
87
|
+
options?: ScoreOptions,
|
|
88
|
+
): number[] {
|
|
89
|
+
const { desired, fallbackColorARGB, filter } = {
|
|
90
|
+
...SCORE_OPTION_DEFAULTS,
|
|
91
|
+
...options,
|
|
92
|
+
};
|
|
93
|
+
// Get the HCT color for each Argb value, while finding the per hue count and
|
|
94
|
+
// total count.
|
|
95
|
+
const colorsHct: Hct[] = [];
|
|
96
|
+
const huePopulation = new Array<number>(360).fill(0);
|
|
97
|
+
let populationSum = 0;
|
|
98
|
+
for (const [argb, population] of colorsToPopulation.entries()) {
|
|
99
|
+
const hct = Hct.fromInt(argb);
|
|
100
|
+
colorsHct.push(hct);
|
|
101
|
+
const hue = Math.floor(hct.hue);
|
|
102
|
+
huePopulation[hue] += population;
|
|
103
|
+
populationSum += population;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Hues with more usage in neighboring 30 degree slice get a larger number.
|
|
107
|
+
const hueExcitedProportions = new Array<number>(360).fill(0.0);
|
|
108
|
+
for (let hue = 0; hue < 360; hue++) {
|
|
109
|
+
const proportion = huePopulation[hue] / populationSum;
|
|
110
|
+
for (let i = hue - 14; i < hue + 16; i++) {
|
|
111
|
+
const neighborHue = math.sanitizeDegreesInt(i);
|
|
112
|
+
hueExcitedProportions[neighborHue] += proportion;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Scores each HCT color based on usage and chroma, while optionally
|
|
117
|
+
// filtering out values that do not have enough chroma or usage.
|
|
118
|
+
const scoredHct = new Array<{ hct: Hct; score: number }>();
|
|
119
|
+
for (const hct of colorsHct) {
|
|
120
|
+
const hue = math.sanitizeDegreesInt(Math.round(hct.hue));
|
|
121
|
+
const proportion = hueExcitedProportions[hue];
|
|
122
|
+
if (
|
|
123
|
+
filter &&
|
|
124
|
+
(hct.chroma < Score.CUTOFF_CHROMA ||
|
|
125
|
+
proportion <= Score.CUTOFF_EXCITED_PROPORTION)
|
|
126
|
+
) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const proportionScore = proportion * 100.0 * Score.WEIGHT_PROPORTION;
|
|
131
|
+
const chromaWeight =
|
|
132
|
+
hct.chroma < Score.TARGET_CHROMA
|
|
133
|
+
? Score.WEIGHT_CHROMA_BELOW
|
|
134
|
+
: Score.WEIGHT_CHROMA_ABOVE;
|
|
135
|
+
const chromaScore = (hct.chroma - Score.TARGET_CHROMA) * chromaWeight;
|
|
136
|
+
const score = proportionScore + chromaScore;
|
|
137
|
+
scoredHct.push({ hct, score });
|
|
138
|
+
}
|
|
139
|
+
// Sorted so that colors with higher scores come first.
|
|
140
|
+
scoredHct.sort(compare);
|
|
141
|
+
|
|
142
|
+
// Iterates through potential hue differences in degrees in order to select
|
|
143
|
+
// the colors with the largest distribution of hues possible. Starting at
|
|
144
|
+
// 90 degrees(maximum difference for 4 colors) then decreasing down to a
|
|
145
|
+
// 15 degree minimum.
|
|
146
|
+
const chosenColors: Hct[] = [];
|
|
147
|
+
for (
|
|
148
|
+
let differenceDegrees = 90;
|
|
149
|
+
differenceDegrees >= 15;
|
|
150
|
+
differenceDegrees--
|
|
151
|
+
) {
|
|
152
|
+
chosenColors.length = 0;
|
|
153
|
+
for (const { hct } of scoredHct) {
|
|
154
|
+
const duplicateHue = chosenColors.find((chosenHct) => {
|
|
155
|
+
return (
|
|
156
|
+
math.differenceDegrees(hct.hue, chosenHct.hue) < differenceDegrees
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
if (!duplicateHue) {
|
|
160
|
+
chosenColors.push(hct);
|
|
161
|
+
}
|
|
162
|
+
if (chosenColors.length >= desired) break;
|
|
163
|
+
}
|
|
164
|
+
if (chosenColors.length >= desired) break;
|
|
165
|
+
}
|
|
166
|
+
const colors: number[] = [];
|
|
167
|
+
if (chosenColors.length === 0) {
|
|
168
|
+
colors.push(fallbackColorARGB);
|
|
169
|
+
}
|
|
170
|
+
for (const chosenHct of chosenColors) {
|
|
171
|
+
colors.push(chosenHct.toInt());
|
|
172
|
+
}
|
|
173
|
+
return colors;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color type definitions for various color spaces
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Supported color formats for parsing
|
|
7
|
+
*/
|
|
8
|
+
export enum ColorFormat {
|
|
9
|
+
HEX = "hex",
|
|
10
|
+
HSL = "hsl",
|
|
11
|
+
HSV = "hsv",
|
|
12
|
+
LAB = "lab",
|
|
13
|
+
RGB = "rgb",
|
|
14
|
+
XYZ = "xyz",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for color distance calculations
|
|
19
|
+
*/
|
|
20
|
+
export interface ColorDistanceOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Parameters for Delta E 94
|
|
23
|
+
*/
|
|
24
|
+
deltaE94?: {
|
|
25
|
+
kC?: number;
|
|
26
|
+
kH?: number;
|
|
27
|
+
kL?: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type of distance metric to use
|
|
32
|
+
*/
|
|
33
|
+
metric?: "deltaE2000" | "deltaE76" | "deltaE94" | "euclidean" | "weighted";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Weights for weighted RGB distance
|
|
37
|
+
*/
|
|
38
|
+
weights?: {
|
|
39
|
+
b?: number;
|
|
40
|
+
g?: number;
|
|
41
|
+
r?: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Color input type that can be parsed
|
|
47
|
+
*/
|
|
48
|
+
export type ColorInput = HSL | HSV | LAB | RGB | string | XYZ;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* HCT (Hue, Chroma, Tone) color representation
|
|
52
|
+
*/
|
|
53
|
+
export interface HCT {
|
|
54
|
+
c: number; // Chroma [0, ~150]
|
|
55
|
+
h: number; // Hue [0, 360)
|
|
56
|
+
t: number; // Tone [0, 100]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Hexadecimal color representation
|
|
61
|
+
*/
|
|
62
|
+
export type HEX = string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* HSL (Hue, Saturation, Lightness) color representation
|
|
66
|
+
* h: [0, 360] degrees
|
|
67
|
+
* s: [0, 100] percentage
|
|
68
|
+
* l: [0, 100] percentage
|
|
69
|
+
*/
|
|
70
|
+
export interface HSL {
|
|
71
|
+
h: number;
|
|
72
|
+
l: number;
|
|
73
|
+
s: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* HSV (Hue, Saturation, Value) color representation
|
|
78
|
+
* Also known as HSB (Hue, Saturation, Brightness)
|
|
79
|
+
* h: [0, 360] degrees
|
|
80
|
+
* s: [0, 100] percentage
|
|
81
|
+
* v: [0, 100] percentage
|
|
82
|
+
*/
|
|
83
|
+
export interface HSV {
|
|
84
|
+
h: number;
|
|
85
|
+
s: number;
|
|
86
|
+
v: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* LAB color space (CIE L*a*b*)
|
|
91
|
+
* L: [0, 100] lightness
|
|
92
|
+
* a: [-128, 127] green-red axis
|
|
93
|
+
* b: [-128, 127] blue-yellow axis
|
|
94
|
+
*/
|
|
95
|
+
export interface LAB {
|
|
96
|
+
a: number;
|
|
97
|
+
b: number;
|
|
98
|
+
l: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Color parsing result
|
|
103
|
+
*/
|
|
104
|
+
export interface ParsedColor {
|
|
105
|
+
format: ColorFormat;
|
|
106
|
+
value: HSL | HSV | LAB | RGB | XYZ;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* RGB color representation
|
|
111
|
+
* Values should be in range [0, 255]
|
|
112
|
+
*/
|
|
113
|
+
export interface RGB {
|
|
114
|
+
b: number;
|
|
115
|
+
g: number;
|
|
116
|
+
r: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* XYZ color space (CIE 1931)
|
|
121
|
+
* Reference white: D65 illuminant
|
|
122
|
+
*/
|
|
123
|
+
export interface XYZ {
|
|
124
|
+
x: number;
|
|
125
|
+
y: number;
|
|
126
|
+
z: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Constants for color space conversions
|
|
131
|
+
*/
|
|
132
|
+
export const ColorConstants = {
|
|
133
|
+
/**
|
|
134
|
+
* D65 illuminant reference white point
|
|
135
|
+
*/
|
|
136
|
+
D65: {
|
|
137
|
+
X: 95.047,
|
|
138
|
+
Y: 100.0,
|
|
139
|
+
Z: 108.883,
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Epsilon for LAB conversion
|
|
144
|
+
*/
|
|
145
|
+
EPSILON: 0.008856,
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Kappa for LAB conversion
|
|
149
|
+
*/
|
|
150
|
+
KAPPA: 903.3,
|
|
151
|
+
} as const;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2021 Google LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// This file is automatically generated. Do not modify it.
|
|
19
|
+
|
|
20
|
+
import * as mathUtils from "./math_utils.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Color science utilities.
|
|
24
|
+
*
|
|
25
|
+
* Utility methods for color science constants and color space
|
|
26
|
+
* conversions that aren't HCT or CAM16.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const SRGB_TO_XYZ = [
|
|
30
|
+
[0.41233895, 0.35762064, 0.18051042],
|
|
31
|
+
[0.2126, 0.7152, 0.0722],
|
|
32
|
+
[0.01932141, 0.11916382, 0.95034478],
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const XYZ_TO_SRGB = [
|
|
36
|
+
[3.2413774792388685, -1.5376652402851851, -0.49885366846268053],
|
|
37
|
+
[-0.9691452513005321, 1.8758853451067872, 0.04156585616912061],
|
|
38
|
+
[0.05562093689691305, -0.20395524564742123, 1.0571799111220335],
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const WHITE_POINT_D65 = [95.047, 100.0, 108.883];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns the alpha component of a color in ARGB format.
|
|
45
|
+
*/
|
|
46
|
+
export function alphaFromArgb(argb: number): number {
|
|
47
|
+
return (argb >> 24) & 255;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Converts a color represented in Lab color space into an ARGB
|
|
52
|
+
* integer.
|
|
53
|
+
*/
|
|
54
|
+
export function argbFromLab(l: number, a: number, b: number): number {
|
|
55
|
+
const whitePoint = WHITE_POINT_D65;
|
|
56
|
+
const fy = (l + 16.0) / 116.0;
|
|
57
|
+
const fx = a / 500.0 + fy;
|
|
58
|
+
const fz = fy - b / 200.0;
|
|
59
|
+
const xNormalized = labInvf(fx);
|
|
60
|
+
const yNormalized = labInvf(fy);
|
|
61
|
+
const zNormalized = labInvf(fz);
|
|
62
|
+
const x = xNormalized * whitePoint[0];
|
|
63
|
+
const y = yNormalized * whitePoint[1];
|
|
64
|
+
const z = zNormalized * whitePoint[2];
|
|
65
|
+
return argbFromXyz(x, y, z);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Converts a color from linear RGB components to ARGB format.
|
|
70
|
+
*/
|
|
71
|
+
export function argbFromLinrgb(linrgb: number[]): number {
|
|
72
|
+
const r = delinearized(linrgb[0]);
|
|
73
|
+
const g = delinearized(linrgb[1]);
|
|
74
|
+
const b = delinearized(linrgb[2]);
|
|
75
|
+
return argbFromRgb(r, g, b);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Converts an L* value to an ARGB representation.
|
|
80
|
+
*
|
|
81
|
+
* @param lstar L* in L*a*b*
|
|
82
|
+
* @return ARGB representation of grayscale color with lightness
|
|
83
|
+
* matching L*
|
|
84
|
+
*/
|
|
85
|
+
export function argbFromLstar(lstar: number): number {
|
|
86
|
+
const y = yFromLstar(lstar);
|
|
87
|
+
const component = delinearized(y);
|
|
88
|
+
return argbFromRgb(component, component, component);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Converts a color from RGB components to ARGB format.
|
|
93
|
+
*/
|
|
94
|
+
export function argbFromRgb(red: number, green: number, blue: number): number {
|
|
95
|
+
return (
|
|
96
|
+
((255 << 24) |
|
|
97
|
+
((red & 255) << 16) |
|
|
98
|
+
((green & 255) << 8) |
|
|
99
|
+
(blue & 255)) >>>
|
|
100
|
+
0
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Converts a color from ARGB to XYZ.
|
|
106
|
+
*/
|
|
107
|
+
export function argbFromXyz(x: number, y: number, z: number): number {
|
|
108
|
+
const matrix = XYZ_TO_SRGB;
|
|
109
|
+
const linearR = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z;
|
|
110
|
+
const linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z;
|
|
111
|
+
const linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z;
|
|
112
|
+
const r = delinearized(linearR);
|
|
113
|
+
const g = delinearized(linearG);
|
|
114
|
+
const b = delinearized(linearB);
|
|
115
|
+
return argbFromRgb(r, g, b);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns the blue component of a color in ARGB format.
|
|
120
|
+
*/
|
|
121
|
+
export function blueFromArgb(argb: number): number {
|
|
122
|
+
return argb & 255;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Delinearizes an RGB component.
|
|
127
|
+
*
|
|
128
|
+
* @param rgbComponent 0.0 <= rgb_component <= 100.0, represents
|
|
129
|
+
* linear R/G/B channel
|
|
130
|
+
* @return 0 <= output <= 255, color channel converted to regular
|
|
131
|
+
* RGB space
|
|
132
|
+
*/
|
|
133
|
+
export function delinearized(rgbComponent: number): number {
|
|
134
|
+
const normalized = rgbComponent / 100.0;
|
|
135
|
+
let delinearized = 0.0;
|
|
136
|
+
if (normalized <= 0.0031308) {
|
|
137
|
+
delinearized = normalized * 12.92;
|
|
138
|
+
} else {
|
|
139
|
+
delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055;
|
|
140
|
+
}
|
|
141
|
+
return mathUtils.clampInt(0, 255, Math.round(delinearized * 255.0));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Returns the green component of a color in ARGB format.
|
|
146
|
+
*/
|
|
147
|
+
export function greenFromArgb(argb: number): number {
|
|
148
|
+
return (argb >> 8) & 255;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns whether a color in ARGB format is opaque.
|
|
153
|
+
*/
|
|
154
|
+
export function isOpaque(argb: number): boolean {
|
|
155
|
+
return alphaFromArgb(argb) >= 255;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Converts a color from ARGB representation to L*a*b*
|
|
160
|
+
* representation.
|
|
161
|
+
*
|
|
162
|
+
* @param argb the ARGB representation of a color
|
|
163
|
+
* @return a Lab object representing the color
|
|
164
|
+
*/
|
|
165
|
+
export function labFromArgb(argb: number): number[] {
|
|
166
|
+
const linearR = linearized(redFromArgb(argb));
|
|
167
|
+
const linearG = linearized(greenFromArgb(argb));
|
|
168
|
+
const linearB = linearized(blueFromArgb(argb));
|
|
169
|
+
const matrix = SRGB_TO_XYZ;
|
|
170
|
+
const x =
|
|
171
|
+
matrix[0][0] * linearR + matrix[0][1] * linearG + matrix[0][2] * linearB;
|
|
172
|
+
const y =
|
|
173
|
+
matrix[1][0] * linearR + matrix[1][1] * linearG + matrix[1][2] * linearB;
|
|
174
|
+
const z =
|
|
175
|
+
matrix[2][0] * linearR + matrix[2][1] * linearG + matrix[2][2] * linearB;
|
|
176
|
+
const whitePoint = WHITE_POINT_D65;
|
|
177
|
+
const xNormalized = x / whitePoint[0];
|
|
178
|
+
const yNormalized = y / whitePoint[1];
|
|
179
|
+
const zNormalized = z / whitePoint[2];
|
|
180
|
+
const fx = labF(xNormalized);
|
|
181
|
+
const fy = labF(yNormalized);
|
|
182
|
+
const fz = labF(zNormalized);
|
|
183
|
+
const l = 116.0 * fy - 16;
|
|
184
|
+
const a = 500.0 * (fx - fy);
|
|
185
|
+
const b = 200.0 * (fy - fz);
|
|
186
|
+
return [l, a, b];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Linearizes an RGB component.
|
|
191
|
+
*
|
|
192
|
+
* @param rgbComponent 0 <= rgb_component <= 255, represents R/G/B
|
|
193
|
+
* channel
|
|
194
|
+
* @return 0.0 <= output <= 100.0, color channel converted to
|
|
195
|
+
* linear RGB space
|
|
196
|
+
*/
|
|
197
|
+
export function linearized(rgbComponent: number): number {
|
|
198
|
+
const normalized = rgbComponent / 255.0;
|
|
199
|
+
if (normalized <= 0.040449936) {
|
|
200
|
+
return (normalized / 12.92) * 100.0;
|
|
201
|
+
} else {
|
|
202
|
+
return Math.pow((normalized + 0.055) / 1.055, 2.4) * 100.0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Computes the L* value of a color in ARGB representation.
|
|
208
|
+
*
|
|
209
|
+
* @param argb ARGB representation of a color
|
|
210
|
+
* @return L*, from L*a*b*, coordinate of the color
|
|
211
|
+
*/
|
|
212
|
+
export function lstarFromArgb(argb: number): number {
|
|
213
|
+
const y = xyzFromArgb(argb)[1];
|
|
214
|
+
return 116.0 * labF(y / 100.0) - 16.0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Converts a Y value to an L* value.
|
|
219
|
+
*
|
|
220
|
+
* L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
|
|
221
|
+
*
|
|
222
|
+
* L* measures perceptual luminance, a linear scale. Y in XYZ
|
|
223
|
+
* measures relative luminance, a logarithmic scale.
|
|
224
|
+
*
|
|
225
|
+
* @param y Y in XYZ
|
|
226
|
+
* @return L* in L*a*b*
|
|
227
|
+
*/
|
|
228
|
+
export function lstarFromY(y: number): number {
|
|
229
|
+
return labF(y / 100.0) * 116.0 - 16.0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Returns the red component of a color in ARGB format.
|
|
234
|
+
*/
|
|
235
|
+
export function redFromArgb(argb: number): number {
|
|
236
|
+
return (argb >> 16) & 255;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Returns the standard white point; white on a sunny day.
|
|
241
|
+
*
|
|
242
|
+
* @return The white point
|
|
243
|
+
*/
|
|
244
|
+
export function whitePointD65(): number[] {
|
|
245
|
+
return WHITE_POINT_D65;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Converts a color from XYZ to ARGB.
|
|
250
|
+
*/
|
|
251
|
+
export function xyzFromArgb(argb: number): number[] {
|
|
252
|
+
const r = linearized(redFromArgb(argb));
|
|
253
|
+
const g = linearized(greenFromArgb(argb));
|
|
254
|
+
const b = linearized(blueFromArgb(argb));
|
|
255
|
+
return mathUtils.matrixMultiply([r, g, b], SRGB_TO_XYZ);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Converts an L* value to a Y value.
|
|
260
|
+
*
|
|
261
|
+
* L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
|
|
262
|
+
*
|
|
263
|
+
* L* measures perceptual luminance, a linear scale. Y in XYZ
|
|
264
|
+
* measures relative luminance, a logarithmic scale.
|
|
265
|
+
*
|
|
266
|
+
* @param lstar L* in L*a*b*
|
|
267
|
+
* @return Y in XYZ
|
|
268
|
+
*/
|
|
269
|
+
export function yFromLstar(lstar: number): number {
|
|
270
|
+
return 100.0 * labInvf((lstar + 16.0) / 116.0);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function labF(t: number): number {
|
|
274
|
+
const e = 216.0 / 24389.0;
|
|
275
|
+
const kappa = 24389.0 / 27.0;
|
|
276
|
+
if (t > e) {
|
|
277
|
+
return Math.pow(t, 1.0 / 3.0);
|
|
278
|
+
} else {
|
|
279
|
+
return (kappa * t + 16) / 116;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function labInvf(ft: number): number {
|
|
284
|
+
const e = 216.0 / 24389.0;
|
|
285
|
+
const kappa = 24389.0 / 27.0;
|
|
286
|
+
const ft3 = ft * ft * ft;
|
|
287
|
+
if (ft3 > e) {
|
|
288
|
+
return ft3;
|
|
289
|
+
} else {
|
|
290
|
+
return (116 * ft - 16) / kappa;
|
|
291
|
+
}
|
|
292
|
+
}
|